Files
vectorcraft/crates/svg/tests/font_weights.rs
T
bflatasticandClaude Opus 5.5 30550f038a Fix #136: SVG export and CSS write each style's numeric font-weight
font_props wrote `bold` for any style whose name contained bold, black
or heavy and nothing for the others unless fonts were embedded, so
Semibold came back Bold and Light or Medium came back Regular. It now
writes the style's numeric weight (font_descriptor, from style_weight)
for every writer: 600 for Semibold, 300 for Light, 900 for Black, `bold`
for 700 and nothing for 400. The SVG import already reads these numbers
back to the matching face.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:36:29 +07:00

79 lines
3.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Font weights in SVG and CSS: every style is written with its numeric weight (Semibold 600,
//! Light 300, Black 900…; Bold as `bold`), and SVG import reads the numbers back to the same face.
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use vectorcraft_doc::{CharStyle, Document, Node, NodeKind, TextObject};
use vectorcraft_geom::Point;
use vectorcraft_svg::{CssOptions, ExportOptions, css_rules, export, import};
use vectorcraft_text::FontDb;
/// A document with one text in `family` `style`.
fn doc(family: &str, style: &str) -> (Document, vectorcraft_doc::NodeId) {
let mut d = Document::new(300.0, 100.0);
let st = CharStyle { font_family: family.into(), font_style: style.into(), size: 24.0, ..CharStyle::default() };
let n = Node::new(d.alloc_id(), NodeKind::Text(Box::new(TextObject::point(Point::new(10.0, 50.0), "Weight", st))));
let (l, id) = (d.layers[0].id, n.id);
d.insert(Some(l), 0, n).unwrap();
(d, id)
}
/// The `font-weight` the SVG export and the CSS of the text write.
fn weights(style: &str) -> (Option<String>, Option<String>) {
let (d, id) = doc("Source Sans 3", style);
let svg = export(&d, &ExportOptions::default());
let attr = svg.split("font-weight=\"").nth(1).map(|s| s.split('"').next().unwrap().to_string());
let sheet = css_rules(&d, &[id], &CssOptions::default());
let css = sheet.rules[0].props.iter().find(|(k, _)| *k == "font-weight").map(|(_, v)| v.clone());
(attr, css)
}
fn style_back(family: &str, style: &str) -> String {
let svg = export(&doc(family, style).0, &ExportOptions::default());
let mut found = None;
import(&svg).unwrap().walk(|n| {
if let NodeKind::Text(t) = &n.kind {
found = Some(t.runs[0].style.font_style.clone());
}
});
found.expect("the text comes back")
}
#[test]
fn every_style_is_written_with_its_numeric_weight() {
for (style, want) in [
("Thin", Some("100")),
("ExtraLight", Some("200")),
("Light", Some("300")),
("Regular", None),
("Italic", None),
("Medium", Some("500")),
("Semibold", Some("600")),
("Demibold", Some("600")),
("Bold", Some("bold")),
("Bold Italic", Some("bold")),
("Extrabold", Some("800")),
("Black", Some("900")),
("Heavy", Some("900")),
] {
let want = want.map(str::to_string);
assert_eq!(weights(style), (want.clone(), want), "{style}");
}
}
#[test]
fn numeric_weights_read_back_to_the_style() {
let db = FontDb::global();
for (family, style) in
[("Source Sans 3", "Semibold"), ("Source Sans 3", "Bold"), ("Source Sans 3", "Italic"), ("Inter", "Medium"), ("Inter", "SemiBold")]
{
let back = style_back(family, style);
let face = |s: &str| db.face(family, s).map(|f| f.style.clone());
assert_eq!(face(&back), face(style), "{family} {style} came back as {back}");
}
// Styles the family doesn't have keep their weight.
assert_eq!(style_back("Source Sans 3", "Light"), "Light");
assert_eq!(style_back("Source Sans 3", "Black Italic"), "Black Italic");
assert_eq!(style_back("Source Sans 3", "Heavy"), "Black");
}