M2: a character a substitute font lacks draws .notdef, not an unrelated glyph (#661)
A Type 0 font that isn't embedded is drawn with a substitute (a craft-fonts Japanese face for Adobe-Japan1). When hayro couldn't find a CID's character in the substitute by Unicode, it took the CID for a glyph index of the substitute, which numbers its glyphs its own way: 𠮷 (CID 13706) drew as "Q" in BIZ UDPGothic, ㈠ and the squared katakana words as unrelated kanji. A CID of an Adobe character collection that the substitute can't find now draws .notdef, unless the substitute is a CID-keyed CFF font, which is looked up by CID as before. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
d67e79dd15
commit
d63436ed10
|
|
@ -83,8 +83,8 @@ fn japanese_fallback(query: &FontQuery) -> Option<(FontData, u32)> {
|
|||
}
|
||||
let face = match japanese_face(f.post_script_name.as_deref().unwrap_or_default(), f.is_serif, f.is_bold || f.font_weight >= 600) {
|
||||
// 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, …).
|
||||
// (U+FF61–U+FF9F), which Shippori Mincho lacks, and a character the substitute lacks is
|
||||
// drawn as .notdef.
|
||||
JapaneseFace::Mincho => pdfcraft_fonts::ui_japanese_fonts()
|
||||
.into_iter()
|
||||
.find(|c| c.family == "BIZ UDMincho" && c.style == "Regular")
|
||||
|
|
@ -3427,7 +3427,7 @@ 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).
|
||||
/// (Shippori Mincho) draws them as .notdef.
|
||||
#[test]
|
||||
fn mincho_substitute_covers_half_width_katakana() {
|
||||
use hayro::hayro_interpret::font::FallbackFontQuery;
|
||||
|
|
@ -3448,6 +3448,49 @@ trailer << /Root 1 0 R >>
|
|||
assert!(missing.is_empty(), "the Mincho substitute lacks {missing:?}");
|
||||
}
|
||||
|
||||
/// 𠮷 (U+20BB7, Adobe-Japan1 CID 13706) in a non-embedded Japanese font: neither craft-fonts
|
||||
/// substitute has it, and hayro drew the substitute's glyph 13706 instead, which is
|
||||
/// unrelated (in BIZ UDPGothic, "Q"). It draws the substitute's .notdef now, while 吉, which
|
||||
/// the substitutes have, still draws.
|
||||
#[test]
|
||||
fn cids_a_substitute_lacks_are_not_drawn_as_other_glyphs() {
|
||||
if pdfcraft_fonts::document_japanese_font().is_none() {
|
||||
eprintln!("built without craft-fonts (CRAFT_FONTS_DIR unset): no Japanese face to check");
|
||||
return;
|
||||
}
|
||||
let render = |base_font: &str, encoding: &str, code: &str| {
|
||||
let content = format!("BT /F1 40 Tf 5 15 Td <{code}> Tj ET");
|
||||
let pdf = format!(
|
||||
"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 60 60] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj
|
||||
4 0 obj << /Length {} >> stream
|
||||
{content}
|
||||
endstream endobj
|
||||
5 0 obj << /Type /Font /Subtype /Type0 /BaseFont /{base_font} /Encoding /{encoding} /DescendantFonts [6 0 R] >> endobj
|
||||
6 0 obj << /Type /Font /Subtype /CIDFontType0 /BaseFont /{base_font} /CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 6 >>
|
||||
/FontDescriptor 7 0 R /DW 1000 >> endobj
|
||||
7 0 obj << /Type /FontDescriptor /FontName /{base_font} /Flags 6 /FontBBox [0 -141 1000 859] /ItalicAngle 0 /Ascent 859 /Descent -141 /CapHeight 700 /StemV 80 >> endobj
|
||||
trailer << /Root 1 0 R >>
|
||||
%%EOF",
|
||||
content.len()
|
||||
);
|
||||
let mut r = PageRenderer::new(Arc::new(pdf.into_bytes()), RenderConfig::default());
|
||||
let p = r.render(RenderRequest { page: 0, kind: RequestKind::Pixels, tile: None, scale: 1.0, tag: 0 });
|
||||
assert!(p.error.is_none(), "{base_font} <{code}>: {:?}", p.error);
|
||||
p.rgba
|
||||
};
|
||||
let inked = |rgba: &[u8]| rgba.as_chunks::<4>().0.iter().filter(|c| c[0] < 128).count();
|
||||
for base_font in ["HeiseiMin-W3", "HeiseiKakuGo-W5"] {
|
||||
let notdef = render(base_font, "Identity-H", "0000");
|
||||
let missing = render(base_font, "UniJIS-UTF16-H", "D842DFB7");
|
||||
let covered = render(base_font, "UniJIS-UTF16-H", "5409");
|
||||
assert!(missing == notdef, "{base_font}: 𠮷 draws .notdef ({} dark pixels, .notdef {})", inked(&missing), inked(¬def));
|
||||
assert!(inked(&covered) > 300 && covered != notdef, "{base_font}: 吉 is drawn ({} dark pixels)", inked(&covered));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_font_names_pick_mincho_or_gothic() {
|
||||
use super::JapaneseFace::{Gothic, Mincho};
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -41,6 +41,9 @@ pub(crate) struct Type0Font {
|
|||
/// Whether the `to_unicode` map is a UCS2 `CMap` (CID-indexed) rather than
|
||||
/// a `ToUnicode` `CMap` (code-indexed).
|
||||
to_unicode_is_cid_indexed: bool,
|
||||
/// PdfCraft patch: whether the CIDs belong to an Adobe character collection (Japan1, GB1,
|
||||
/// CNS1, Korea1), whose glyph numbering no substitute other than a CID-keyed CFF font shares.
|
||||
adobe_collection: bool,
|
||||
}
|
||||
|
||||
impl Type0Font {
|
||||
|
|
@ -129,6 +132,10 @@ impl Type0Font {
|
|||
}
|
||||
}
|
||||
|
||||
let adobe_collection = character_collection
|
||||
.as_ref()
|
||||
.is_some_and(|cc| cc.family.ucs2_cmap().is_some());
|
||||
|
||||
let postscript_name = dict
|
||||
.get::<Name<'_>>(BASE_FONT)
|
||||
.map(|n| strip_subset_prefix(n.as_str()).to_string());
|
||||
|
|
@ -153,6 +160,7 @@ impl Type0Font {
|
|||
font_flags,
|
||||
fallback,
|
||||
to_unicode_is_cid_indexed,
|
||||
adobe_collection,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -176,6 +184,16 @@ impl Type0Font {
|
|||
return glyph;
|
||||
}
|
||||
|
||||
// PdfCraft patch: a substitute that isn't CID-keyed numbers its glyphs its own way, so
|
||||
// the CID of an Adobe character collection taken as its glyph index draws an unrelated
|
||||
// glyph. Draw .notdef, as for a CID the font has no glyph for.
|
||||
if self.fallback
|
||||
&& self.adobe_collection
|
||||
&& !matches!(&self.font_type, FontType::Cff(c) if c.is_cid())
|
||||
{
|
||||
return GlyphId::NOTDEF;
|
||||
}
|
||||
|
||||
// At this point, not much we can do anymore. Just hope that the
|
||||
// selected font has the right glyph order, and map via that.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue