From eec304dde16639ad0c0f1df629c878ec0a9b8afd Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 18 Oct 2026 18:58:14 +0700 Subject: [PATCH] =?UTF-8?q?SVG=20export:=20a=20Japanese=20face's=20numbere?= =?UTF-8?q?d=20weight=20(W0=E2=80=93W9)=20is=20its=20font-weight,=20W6=206?= =?UTF-8?q?00=20(#2873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit style_weight reads W0–W9 style names (Hiragino, Morisawa…) as their weight classes before the English words, so SVG and CSS export write font-weight 600 for W6, and nearest-style matching weighs them too. --- crates/svg/tests/font_weights.rs | 7 +++++++ crates/text/src/fontdb.rs | 10 +++++++++- docs/mcp.md | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/svg/tests/font_weights.rs b/crates/svg/tests/font_weights.rs index f642930c5..31d358c61 100644 --- a/crates/svg/tests/font_weights.rs +++ b/crates/svg/tests/font_weights.rs @@ -55,6 +55,13 @@ fn every_style_is_written_with_its_numeric_weight() { ("Extrabold", Some("800")), ("Black", Some("900")), ("Heavy", Some("900")), + // Japanese faces' numbered weights (#2873); W4 is the regular weight. + ("W0", Some("100")), + ("W3", Some("300")), + ("W4", None), + ("W6", Some("600")), + ("W9", Some("900")), + ("Wide", None), ] { let want = want.map(str::to_string); assert_eq!(weights(style), (want.clone(), want), "{style}"); diff --git a/crates/text/src/fontdb.rs b/crates/text/src/fontdb.rs index 3acf0fd04..f56eaa371 100644 --- a/crates/text/src/fontdb.rs +++ b/crates/text/src/fontdb.rs @@ -1568,8 +1568,16 @@ fn norm(s: &str) -> String { norm_chars(s).collect() } -/// Weight implied by a style name (400 = regular). +/// Weight implied by a style name (400 = regular). Japanese faces number their weights W0–W9 +/// (Hiragino, Morisawa…), W6 weighing 600 as its weight class does (#2873). pub fn style_weight(style: &str) -> f32 { + let numbered = style.split(|c: char| !c.is_ascii_alphanumeric()).find_map(|part| match part.as_bytes() { + [b'W' | b'w', d @ b'0'..=b'9'] => Some(f32::from(d - b'0') * 100.0), + _ => None, + }); + if let Some(w) = numbered { + return w.max(100.0); + } let s = norm(style); const TABLE: &[(&str, f32)] = &[ ("extralight", 200.0), diff --git a/docs/mcp.md b/docs/mcp.md index 5082f849e..24482fa25 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -467,7 +467,7 @@ lists every option with its default. `encoding` is `utf8`, `utf16` (big-endian a (presentation attributes only; filters, masks, blend modes and embedded fonts left out, symbols as art, each with a warning). `embedFonts: true` embeds the fonts type uses as `@font-face` rules subset to the characters used; a font whose licence (OS/2 `fsType`) forbids subsetting is embedded whole, one that forbids embedding is left out -with a warning. Type names its faces by numeric `font-weight` (600 for Semibold, 300 for Light, `bold` for 700), +with a warning. Type names its faces by numeric `font-weight` (600 for Semibold and for a Japanese face's W6, 300 for Light, `bold` for 700), embedded or not. `svgz` takes the same options and writes the SVG gzipped (`.svgz` files also open, place and paste). `run_command document.save {path: "x.svg", svg: {…}}` saves as SVG (or `.svgz`).