mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
fix(render): draw Turkish Ğ İ Ş ğ ş in non-embedded fonts (vendored hayro-interpret patch 26) (#1409)
A PDF whose fonts aren't embedded is drawn with PDFium's Foxit stand-ins for the standard 14 fonts. Those carry only the 230-odd glyphs of the Standard, WinAnsi and Mac Roman encodings, so a letter a font names beyond them in its /Differences mapped to .notdef and drew nothing. iText 2 writes Turkish exactly this way (Times New Roman left unembedded, /Gbreve /Idotaccent /Scedilla /gbreve /scedilla over Ð Ý Þ ð þ), and such letters showed blanks for Ğ, İ, Ş, ğ and ş while ı, ç, ö and ü drew. StandardKind now builds a Latin Extended-A letter the stand-in lacks from the base letter and accent it does have, as Type 1 seac composites and the standard-14 AFM composites are built: the accent is centred on the letter, raised over capitals and letters with ascenders (and moved along the slant in italics), cedillas hang below and ogoneks from the right edge. Composite glyph ids sit above any font's glyph count, one per character code; their width comes from the standard metrics of the name. Test: raster::tests::accented_letters_the_standard_stand_ins_lack_are_built_from_their_parts (fails without the patch: Gbreve drew no pixel). Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a4c53f706f
commit
a259af95c3
3 files changed
+267
-10
No files matched your search
@@ -4972,6 +4972,66 @@ trailer << /Root 1 0 R >>
|
||||
assert!(px.rgba[((50 * px.width + 25) * 4) as usize] < 64, "the glyph is drawn");
|
||||
}
|
||||
|
||||
/// Accented letters in non-embedded fonts (vendored hayro-interpret patch to
|
||||
/// `StandardKind`): the stand-ins for the standard fonts hold only the glyphs of the
|
||||
/// Standard, WinAnsi and Mac Roman encodings, so a letter a font names beyond those in its
|
||||
/// /Differences drew nothing. iText 2 writes Turkish this way (`/Gbreve`, `/Idotaccent`,
|
||||
/// `/Scedilla`, `/gbreve`, `/scedilla` over Ð, Ý, Þ, ð, þ), and a Turkish letter had blanks
|
||||
/// for Ğ, İ, Ş, ğ and ş. Each is now its base letter with the accent above or below it.
|
||||
#[test]
|
||||
fn accented_letters_the_standard_stand_ins_lack_are_built_from_their_parts() {
|
||||
let letters = ["G", "Gbreve", "S", "Scedilla", "I", "Idotaccent", "g", "gbreve", "s", "scedilla"];
|
||||
let mut content = String::new();
|
||||
for (i, _) in letters.iter().enumerate() {
|
||||
content.push_str(&format!("BT /F1 40 Tf {} 20 Td <{:02X}> Tj ET\n", 10 + 40 * i, 65 + i));
|
||||
}
|
||||
let differences = letters.iter().map(|n| format!("/{n}")).collect::<Vec<_>>().join(" ");
|
||||
let pdf = format!(
|
||||
"%PDF-1.4
|
||||
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 420 80] /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 /TrueType /BaseFont /TimesNewRomanPSMT /FirstChar 65 /LastChar 74
|
||||
/Widths [722 722 556 556 333 333 500 500 389 389] /FontDescriptor 6 0 R
|
||||
/Encoding << /Type /Encoding /Differences [65 {differences}] >> >> endobj
|
||||
6 0 obj << /Type /FontDescriptor /FontName /TimesNewRomanPSMT /Flags 32 /FontBBox [-568 -306 2045 1039]
|
||||
/ItalicAngle 0 /Ascent 693 /Descent -215 /CapHeight 662 /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, ..Default::default() });
|
||||
assert!(p.error.is_none(), "{:?}", p.error);
|
||||
// The dark pixels of the i-th letter: how many, and its top and bottom rows (y down).
|
||||
let ink = |i: u32| {
|
||||
let (mut count, mut top, mut bottom) = (0, u32::MAX, 0);
|
||||
for y in 0..p.height {
|
||||
for x in (8 + 40 * i)..(46 + 40 * i).min(p.width) {
|
||||
if p.rgba[((y * p.width + x) * 4) as usize] < 128 {
|
||||
count += 1;
|
||||
top = top.min(y);
|
||||
bottom = bottom.max(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
(count, top, bottom)
|
||||
};
|
||||
for (pair, above) in [(0, true), (1, false), (2, true), (3, true), (4, false)] {
|
||||
let (base, accented) = (ink(2 * pair), ink(2 * pair + 1));
|
||||
let name = letters[2 * pair as usize + 1];
|
||||
assert!(base.0 > 0, "{name}: the base letter draws");
|
||||
assert!(accented.0 > base.0, "{name} draws its letter and accent: {accented:?} vs {base:?}");
|
||||
if above {
|
||||
assert!(accented.1 + 2 < base.1, "{name}: the accent sits above the letter: {accented:?} vs {base:?}");
|
||||
} else {
|
||||
assert!(accented.2 > base.2 + 2, "{name}: the accent hangs below the letter: {accented:?} vs {base:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A page's `/UserUnit` (the size of its unit in points) scales the page and everything on
|
||||
/// it, after the crop box and before `/Rotate`, as Acrobat shows such pages: with
|
||||
/// `/UserUnit 2`, scale 1 renders pixel for pixel what scale 2 renders without it. As in
|
||||
|
||||
Reference in new issue
Block a user