Fix #3209: SVG export writes type sizes and spacing in CSS with px

With Styling set to Internal CSS, Inline Style or Style Entities, font-size
and letter-spacing were written as bare numbers. Only presentation attributes
take a bare number; in CSS a browser drops them, so the type was drawn at its
default size. They are now written in px, SVG's user unit, and presentation
attributes keep bare numbers.
This commit is contained in:
Ridzzz0Alam
2026-10-29 22:12:07 +07:00
committed by bflatastic
parent 2f9e72b419
commit e110c6c770
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]