From 5f570eab01032729c0959b9cf3f3524dd5d94aa5 Mon Sep 17 00:00:00 2001 From: Chen Tao Date: Fri, 9 Oct 2026 22:14:46 +0900 Subject: [PATCH] M2.6: non-embedded Mincho fonts draw half-width katakana (#484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since #368, a non-embedded Adobe-Japan1 font with a Mincho name (HeiseiMin-W3, MS-Mincho, ...) is drawn in document_japanese_font(), i.e. Shippori Mincho. Shippori Mincho has none of the 63 half-width katakana (U+FF61-U+FF9F), which are common in Japanese documents. hayro maps the CID through Adobe-Japan1-UCS2 to Unicode, finds no glyph in the face's cmap, and falls back to drawing the CID as a glyph index: half-width katakana came out as unrelated glyphs (ligatures such as fi and fl). The Mincho substitute is now BIZ UDMincho Regular, which covers half-width katakana (and was designed as a metric-compatible replacement for MS Mincho), falling back to document_japanese_font() when it isn't built in. The face used for Japanese text written into PDFs is unchanged. Without craft-fonts nothing changes. Tests: raster::tests::mincho_substitute_covers_half_width_katakana (with CRAFT_FONTS_DIR: the Mincho substitute's cmap maps U+FF61-U+FF9F and 日本; it lacked all 63 half-width katakana before; skips without craft-fonts). skrifa 0.44 is a dev-dependency of pdfcraft-render for it, the version pdfcraft-fonts already uses. The parity entry view.render-cid-fonts cites the test. Co-authored-by: Claude Opus 5.5 --- Cargo.lock | 1 + contributors/people.toml | 5 +++++ crates/render/Cargo.toml | 4 ++++ crates/render/src/raster.rs | 31 ++++++++++++++++++++++++++++++- parity/acrobat-features.toml | 4 ++-- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10531cb1f..7ce59c30a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3751,6 +3751,7 @@ dependencies = [ "log", "lopdf", "pdfcraft-fonts", + "skrifa 0.44.0", "thiserror 2.0.21", ] diff --git a/contributors/people.toml b/contributors/people.toml index 00beb6587..ade46dac1 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -59,3 +59,8 @@ sync_github_name = true [people.amitabhthakur] sync_github_name = true + +[people.chent11] +real_name = "Tao Chen" +display_name = "Tao Chen" +sync_github_name = false diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 2a5b974d4..fa68a0641 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -20,3 +20,7 @@ log = { workspace = true } # lopdf (bootstrap) draws random bytes for encryption; on the web they come from the browser. [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.4", features = ["wasm_js"] } + +[dev-dependencies] +# Checks the Japanese substitute's character coverage; the version pdfcraft-fonts already uses. +skrifa = "0.44" diff --git a/crates/render/src/raster.rs b/crates/render/src/raster.rs index 267be471d..e6b6e7ea2 100644 --- a/crates/render/src/raster.rs +++ b/crates/render/src/raster.rs @@ -73,7 +73,13 @@ fn japanese_fallback(query: &FontQuery) -> Option<(FontData, u32)> { return None; } let face = match japanese_face(f.post_script_name.as_deref().unwrap_or_default(), f.is_serif, f.is_bold || f.font_weight >= 600) { - JapaneseFace::Mincho => pdfcraft_fonts::document_japanese_font(), + // BIZ UDMincho before the document face (Shippori Mincho): it covers half-width katakana + // (U+FF61–U+FF9F), which Shippori Mincho lacks, and hayro draws a CID the substitute + // can't map by Unicode by glyph index, i.e. as an unrelated glyph (fi, fl, …). + JapaneseFace::Mincho => pdfcraft_fonts::ui_japanese_fonts() + .into_iter() + .find(|c| c.family == "BIZ UDMincho" && c.style == "Regular") + .or_else(pdfcraft_fonts::document_japanese_font), JapaneseFace::Gothic { bold } => { let faces = pdfcraft_fonts::ui_japanese_fonts(); let style = if bold { "Bold" } else { "Regular" }; @@ -1652,6 +1658,29 @@ trailer << /Root 1 0 R >> } } + /// Half-width katakana are common in Japanese documents set in a non-embedded Mincho font + /// such as HeiseiMin-W3. The Mincho substitute must have those glyphs: one that lacks them + /// (Shippori Mincho) draws each CID by glyph index instead, as unrelated glyphs (fi, fl). + #[test] + fn mincho_substitute_covers_half_width_katakana() { + use hayro::hayro_interpret::font::FallbackFontQuery; + use hayro::hayro_interpret::hayro_cmap::CharacterCollection; + use skrifa::MetadataProvider; + let query = FontQuery::Fallback(FallbackFontQuery { + post_script_name: Some("HeiseiMin-W3".into()), + character_collection: Some(CharacterCollection { family: CidFamily::AdobeJapan1, supplement: 2 }), + ..FallbackFontQuery::default() + }); + let Some((data, index)) = super::japanese_fallback(&query) else { + eprintln!("built without craft-fonts (CRAFT_FONTS_DIR unset): no Japanese face to check"); + return; + }; + let font = skrifa::FontRef::from_index((*data).as_ref(), index).expect("a craft-fonts face parses"); + let charmap = font.charmap(); + let missing: Vec = ('\u{FF61}'..='\u{FF9F}').chain(['日', '本']).filter(|&c| charmap.map(c).is_none()).collect(); + assert!(missing.is_empty(), "the Mincho substitute lacks {missing:?}"); + } + #[test] fn japanese_font_names_pick_mincho_or_gothic() { use super::JapaneseFace::{Gothic, Mincho}; diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index 6cc6eaef0..627fe94bf 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -746,8 +746,8 @@ milestone = "M2" status = "partial" commands = [] tools = [] -evidence = ["crates/render/src/raster.rs::huge_cid_width_ranges_terminate", "crates/render/src/raster.rs::non_embedded_japanese_cid_fonts_draw_with_a_craft_fonts_face"] -notes = "CID-to-Unicode tables are M2.4; a non-embedded Adobe-Japan1 font is drawn in a craft-fonts Japanese face (Mincho or Gothic by name) when built with craft-fonts; non-embedded GB1, CNS1 and Korea1 fonts still draw nothing (no allowed face)" +evidence = ["crates/render/src/raster.rs::huge_cid_width_ranges_terminate", "crates/render/src/raster.rs::non_embedded_japanese_cid_fonts_draw_with_a_craft_fonts_face", "crates/render/src/raster.rs::mincho_substitute_covers_half_width_katakana"] +notes = "CID-to-Unicode tables are M2.4; a non-embedded Adobe-Japan1 font is drawn in a craft-fonts Japanese face (Mincho or Gothic by name; BIZ UDMincho for Mincho, which covers half-width katakana) when built with craft-fonts; non-embedded GB1, CNS1 and Korea1 fonts still draw nothing (no allowed face)" [[feature]] id = "view.render-image-codecs"