Merge pull request #4604 from Ridzzz0Alam/fix/3209-svg-css-units

Fix #3209: SVG export writes type sizes and spacing in CSS with px
This commit is contained in:
BFlat
2026-10-29 22:37:37 +07:00
committed by GitHub
3 changed files with 23 additions and 8 deletions
+7 -3
View File
@@ -292,14 +292,18 @@ impl Writer<'_> {
format!("matrix({} {} {} {} {} {})", self.num(c[0]), self.num(c[1]), self.num(c[2]), self.num(c[3]), self.num(c[4]), self.num(c[5]))
}
/// How style properties are written: the styling option, but SVG Tiny has presentation
/// attributes alone.
pub(super) fn styling(&self) -> Styling {
if self.tiny() { Styling::PresentationAttributes } else { self.opts.styling }
}
/// Style properties as attributes, an inline style, or a CSS class, per the styling option.
pub(super) fn attrs(&mut self, props: &Props) -> String {
if props.is_empty() {
return String::new();
}
// SVG Tiny has presentation attributes alone.
let styling = if self.tiny() { Styling::PresentationAttributes } else { self.opts.styling };
match styling {
match self.styling() {
Styling::PresentationAttributes => {
// CSS-only properties go in a style attribute (SVG Tiny has none: left out).
let (style, attrs): (Vec<_>, Vec<_>) = props.iter().partition(|(k, _)| {
+6 -3
View File
@@ -1,8 +1,8 @@
use super::output::xml_text;
use super::{
Affine, AppearanceItem, Arc, BTreeSet, BezPath, BlendMode, CharStyle, FillRule, FontDb, FontFace, GradientKind, HashMap, Justify, Node, NodeId,
NodeKind, Paint, PathData, Props, Rect, StrokeAlign, TextKind, TextObject, Writer, base64_encode, css, css_string, fmt_num, font_descriptor,
xml_escape,
NodeKind, Paint, PathData, Props, Rect, StrokeAlign, Styling, TextKind, TextObject, Writer, base64_encode, css, css_string, fmt_num,
font_descriptor, xml_escape,
};
/// `chars` (sorted) as a CSS `unicode-range` (`U+41-5A,U+61`).
@@ -36,7 +36,10 @@ impl Writer<'_> {
/// (as on the canvas).
fn char_props(&mut self, st: &CharStyle, space: (Rect, Affine)) -> Props {
let decimals = self.opts.decimals;
let len = |v: f64| fmt_num(v, decimals);
// Only presentation attributes take a bare number (in user units). Written as CSS, a
// length needs its unit, a user unit being a `px`: a browser drops `font-size:8.6` (#3209).
let unit = if self.styling() == Styling::PresentationAttributes { "" } else { "px" };
let len = |v: f64| format!("{}{unit}", fmt_num(v, decimals));
let mut p = css::font_props(st, &len);
// Character paints resolve in the text's space (`to_user` maps it into user space).
let (bounds, to_user) = space;
+10 -2
View File
@@ -228,8 +228,16 @@ fn shared_properties_read_as_svg_export_writes_them() {
let decl = format!("{k}:{v}").replace('"', "&quot;").replace('\'', "&apos;");
assert!(style.contains(&decl), "{decl} as in SVG export: {style}");
}
// Lengths are the same numbers, with units on the web.
assert!(style.contains("font-size:18") && prop(&r, "font-size") == Some("18px"), "{style}");
// Lengths are the same, with units: CSS takes a bare number only for 0, and a browser drops
// `font-size:18`, leaving the type at its default size (#3209). Presentation attributes take
// bare numbers (`text_layout`).
let spacing = prop(&r, "letter-spacing").unwrap_or_default();
assert!(prop(&r, "font-size") == Some("18px") && spacing.ends_with("px"), "{r:?}");
for decl in ["font-size:18px".to_string(), format!("letter-spacing:{spacing}")] {
assert!(style.contains(&decl), "{decl} as in SVG export: {style}");
}
let inline = export(&d, &ExportOptions { styling: Styling::InlineStyle, ..ExportOptions::default() });
assert!(inline.contains("font-size:18px") && inline.contains(&format!("letter-spacing:{spacing}")), "{inline}");
}
#[test]