mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-09 12:32:52 +00:00
M2: a loca table with padding after short offsets stays short (hayro-interpret patch 79)
Room for numGlyphs + 1 long offsets means long, room only for short ones means short (padding after them allowed), and a table too short for either keeps the field as it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
This commit is contained in:
1 parent
02f0eaa8f0
commit
abc348894c
2 files changed
+22
-11
No files matched your search
@@ -18,8 +18,9 @@ fn be32(out: &mut Vec<u8>, n: u32) {
|
||||
out.extend_from_slice(&n.to_be_bytes());
|
||||
}
|
||||
|
||||
/// The font, its `loca` offsets long when `long`, its `head.indexToLocFormat` set to `format`.
|
||||
fn square_font(long: bool, format: u16) -> Vec<u8> {
|
||||
/// The font, its `loca` offsets long when `long` and followed by `padding` zero bytes, its
|
||||
/// `head.indexToLocFormat` set to `format`.
|
||||
fn square_font(long: bool, padding: usize, format: u16) -> Vec<u8> {
|
||||
// Glyph 1: one contour, the square 0..1000 on both axes (glyph 0 is empty).
|
||||
let mut glyf = Vec::new();
|
||||
for n in [1u16, 0, 0, 1000, 1000] {
|
||||
@@ -38,6 +39,7 @@ fn square_font(long: bool, format: u16) -> Vec<u8> {
|
||||
for offset in [0u32, 0, glyf.len() as u32] {
|
||||
if long { be32(&mut loca, offset) } else { be16(&mut loca, (offset / 2) as u16) }
|
||||
}
|
||||
loca.resize(loca.len() + padding, 0);
|
||||
|
||||
let mut head = vec![0; 54];
|
||||
head[0..4].copy_from_slice(&[0, 1, 0, 0]);
|
||||
@@ -130,14 +132,16 @@ fn page_with_font(font: &[u8]) -> Vec<u8> {
|
||||
#[test]
|
||||
fn a_truetype_font_with_an_out_of_range_loca_format_draws_its_glyphs() {
|
||||
// The square covers (5, 5)–(35, 35) in page space: row 20 from the top is inside it.
|
||||
let square_drawn = |long: bool, format: u16| {
|
||||
let page = render_first_page(page_with_font(&square_font(long, format)));
|
||||
let square_drawn = |long: bool, padding: usize, format: u16| {
|
||||
let page = render_first_page(page_with_font(&square_font(long, padding, format)));
|
||||
let at = |x: u32, y: u32| page.rgba.get(((y * page.width + x) * 4) as usize).copied().unwrap_or(255);
|
||||
assert_eq!(at(2, 2), 255, "nothing is drawn outside the square");
|
||||
at(20, 20) < 128
|
||||
};
|
||||
assert!(square_drawn(true, 1), "long offsets, marked long");
|
||||
assert!(square_drawn(false, 0), "short offsets, marked short");
|
||||
assert!(square_drawn(true, 0x0100), "long offsets marked 256: the table's length says long");
|
||||
assert!(square_drawn(false, 0x0100), "short offsets marked 256: the table's length says short");
|
||||
assert!(square_drawn(true, 0, 1), "long offsets, marked long");
|
||||
assert!(square_drawn(false, 0, 0), "short offsets, marked short");
|
||||
assert!(square_drawn(true, 0, 0x0100), "long offsets marked 256: the table's length says long");
|
||||
assert!(square_drawn(false, 0, 0x0100), "short offsets marked 256: the table's length says short");
|
||||
// Three short offsets are 6 bytes and three long ones 12: 8 bytes is short ones and padding.
|
||||
assert!(square_drawn(false, 2, 0x0100), "short offsets and padding marked 256: still short");
|
||||
}
|
||||
+10
-3
@@ -200,8 +200,9 @@ impl Debug for OpenTypeFontBlob {
|
||||
/// it needs no change. The glyph outline reader takes only 1 as long, so a font whose field reads
|
||||
/// 256 (the value's bytes swapped) had its long `loca` read as short offsets: every glyph came
|
||||
/// out wrong, and a line of text set in it drew boxes (pdf.js `issue2537r.pdf`). The `loca`
|
||||
/// table's length decides (`numGlyphs + 1` offsets of 4 bytes or of 2); a length that fits
|
||||
/// neither keeps the rule that any value other than 0 means long offsets.
|
||||
/// table's length decides: room for `numGlyphs + 1` offsets of 4 bytes is long, room for only
|
||||
/// that many of 2 bytes short (a table may carry padding after its offsets), and a table too
|
||||
/// short for either is left as it is.
|
||||
fn with_loca_format_fixed(data: &[u8], index: u32) -> Option<Vec<u8>> {
|
||||
use skrifa::raw::types::Tag;
|
||||
const HEAD: Tag = Tag::new(b"head");
|
||||
@@ -215,7 +216,13 @@ fn with_loca_format_fixed(data: &[u8], index: u32) -> Option<Vec<u8>> {
|
||||
}
|
||||
let offsets = usize::from(f.maxp().ok()?.num_glyphs()).checked_add(1)?;
|
||||
let loca_len = f.table_data(LOCA)?.len();
|
||||
let long = loca_len != offsets.checked_mul(2)? || loca_len == offsets.checked_mul(4)?;
|
||||
let long = if loca_len >= offsets.checked_mul(4)? {
|
||||
true
|
||||
} else if loca_len >= offsets.checked_mul(2)? {
|
||||
false
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let head_at = f.table_directory.table_records().iter().find(|r| r.tag() == HEAD)?.offset() as usize;
|
||||
let at = head_at.checked_add(FORMAT_AT)?;
|
||||
let mut fixed = data.to_vec();
|
||||
|
||||
Reference in new issue
Block a user