Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions src/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function tagPage(page: mupdf.PDFPage, i: number, html: string, ctx: PageCtx): {
throw new IrisPdfError("too_many_words", `Page ${n} has more than ${MAX_WORDS} words.`);
const { match, complete } = align(ordered.map((o) => o.word.norm), tokens.map((t) => t.norm));
if (!complete) warn({ code: "alignment_incomplete", detail: "The page and the HTML differ too much to match every word in time." });
const blockOf = new Map<number, number>();
const blockOf = new Map<number, number>(), bounds = page.getBounds() as Box;
const matched = new Set<Word>();
match.forEach((t, k) => {
if (t < 0) {
Expand All @@ -197,10 +197,9 @@ function tagPage(page: mupdf.PDFPage, i: number, html: string, ctx: PageCtx): {
}
report.matched++;
matched.add(ordered[k].word);
ordered[k].word.at = placed(words[tokens[t].words[0]]);
ordered[k].word.at = placed(words[tokens[t].words[0]], bounds);
blockOf.set(t, ordered[k].block);
});
const bounds = page.getBounds();
// With no page words, each block starts a new line, so blocks do not run together.
const breaks = new Set(words.length ? [] : ordered.filter((o, k) => k && o.newLine).map((o) => o.word));
fillPositions(ordered.map((o) => o.word), bounds, (s) => ctx.fonts.width(s), breaks);
Expand All @@ -227,7 +226,7 @@ function tagPage(page: mupdf.PDFPage, i: number, html: string, ctx: PageCtx): {
}
report.lost++;
if (!run) lost.set(block, [...(lost.get(block) ?? []), (run = { words: [] })]);
run.words.push({ text: w.text, norm: "", at: placed(w) });
run.words.push({ text: w.text, norm: "", at: placed(w, bounds) });
}
});
for (const runs of lost.values()) {
Expand Down Expand Up @@ -275,7 +274,9 @@ type Link = { obj: mupdf.PDFObject; box: Box; uri: string; used: boolean };
type Emitter = PageCtx & { pageObj: mupdf.PDFObject; overlay: Overlay; links: Link[]; mcids: number };

const asP = (r: Run): Node => ({ type: "P", kids: [r] });
const placed = (w: PageWord): Placed => ({ box: w.box, baseline: w.baseline, size: w.size });
// A word running off the page is squeezed onto it: extractors drop glyphs off the page.
const placed = (w: PageWord, page: Box): Placed =>
({ box: [Math.max(w.box[0], page[0]), w.box[1], Math.min(w.box[2], page[2]), w.box[3]], baseline: w.baseline, size: w.size });

// An HTML word with no page word follows the word before it, at its natural
// width, wrapping at the page edge: extractors drop text off the page, and
Expand Down
12 changes: 12 additions & 0 deletions test/tag.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,18 @@ test("a word in a very narrow box keeps its repeated letters", () => {
assert.doesNotMatch(overlay.toString(), /NaN|Infinity/);
});

test("a word running off the page edge keeps every letter of its tagged text", () => {
// Extractors drop a glyph off the page. The source word is monospaced, so its
// last letter starts just inside; stretched to the same box, ours would not.
// So too at the left edge, for its first letter.
const doc = new mupdf.PDFDocument();
const res = doc.addObject({ Font: { F1: doc.addSimpleFont(new mupdf.Font("Helvetica")), F2: doc.addSimpleFont(new mupdf.Font("Courier")) } });
doc.insertPage(-1, doc.addPage([0, 0, 300, 300], 0, res, "BT /F1 12 Tf 10 200 Td (Thanks) Tj ET BT /F2 12 Tf 276.4 200 Td (Hail) Tj ET BT /F2 12 Tf -4 180 Td (iWall) Tj ET"));
const report = newReport();
tag(doc.saveToBuffer("").asUint8Array().slice(), { lang: "en", title: "t", pages: [{ sourcePage: 1, html: "<p>Thanks Hail</p><p>iWall</p>" }] }, {}, report);
assert.equal(report.verification.textPreserved, true);
});

test("a character no font has is reported and left out, not a verification failure", () => {
const report = newReport();
tag(readFixture("text-simple.pdf"), { lang: "en", pages: [{ sourcePage: 1, html: "<h1>Parking Permit 🦄 x🦄</h1>" }] }, {}, report);
Expand Down
Loading