M2.6: non-embedded Mincho fonts draw half-width katakana (#484)

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 <[email protected]>
This commit is contained in:
Chen TaoandClaude Opus 5.5 authored and GitHub committed 2026-10-09 09:14:46 -04:00
1 parent 271139bd3b
commit 5f570eab01
5 files changed
+42 -3

No files matched your search

Generated
+1
View File
@@ -3751,6 +3751,7 @@ dependencies = [
"log",
"lopdf",
"pdfcraft-fonts",
"skrifa 0.44.0",
"thiserror 2.0.21",
]
+5
View File
@@ -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
+4
View File
@@ -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"
+30 -1
View File
@@ -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<char> = ('\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};
+2 -2
View File
@@ -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"