From b52c9e958991783e3e71f2d1b9c5442bf5ea7751 Mon Sep 17 00:00:00 2001 From: Blake Bertuccelli-Booth <46652+bbertucc@users.noreply.github.com> Date: Sun, 4 Oct 2026 09:31:33 -0400 Subject: [PATCH 1/2] Drop --strict Nothing uses it, and every warning it failed on is already in the report, where a caller can act on it. Co-Authored-By: Claude Opus 5.5 --- README.md | 3 +-- src/cli.ts | 6 +++--- src/tag.ts | 7 ------- test/document.test.ts | 1 - test/tag.test.ts | 10 +++------- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a7c6422..d20bc14 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,6 @@ iris-pdf review --pdf out.pdf [--report review.json] # optional AI review, bel | `--password` | Open an encrypted PDF. The output keeps its encryption. | | `--allow-signed` | Tag a signed PDF. This breaks the signature, and the report says so. | | `--partial` | Leave a page untagged, instead of failing, when it has no way to place text. | -| `--strict` | Fail on any warning that means content went untagged or unmatched, or that the file was `repaired`. | ### pages.json @@ -92,7 +91,7 @@ The output declares PDF/UA-1 only when it has a title, every page is tagged, eve | Exit | When | |---|---| | 0 | Done. | -| 1 | Refused: `encrypted` (no or wrong password), `permissions_denied`, `too_many_pages` (over 25), `too_many_words` (over 4000 on a page), `xfa` (dynamic form), `signed`, `no_acroform_field`, `no_text_positions`, `strict`. From `review`: `review_failed` (the model or its API failed on a page). | +| 1 | Refused: `encrypted` (no or wrong password), `permissions_denied`, `too_many_pages` (over 25), `too_many_words` (over 4000 on a page), `xfa` (dynamic form), `signed`, `no_acroform_field`, `no_text_positions`. From `review`: `review_failed` (the model or its API failed on a page). | | 2 | A check failed: `pixels_changed`, `text_lost`. | | 3 | Bad input: `unreadable`, `bad_pages`, `no_document_language`, `bad_value`, `field_not_settable`, `bad_arguments`. From `review`: `not_tagged`, `no_readable_structure` (not tagged by this tool), `bad_structure` (nested over 64 levels), `no_credentials`. | diff --git a/src/cli.ts b/src/cli.ts index d6ea5ce..16157c0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,7 +12,7 @@ const USAGE = `iris-pdf ${VERSION} iris-pdf tag --pdf --pages --out [--values ] [--report ] [--lang ] [--title ] [--ocr auto|off|required] [--verify pixels,text|off] [--verify-dpi 150] - [--flatten] [--password ] [--allow-signed] [--partial] [--strict] + [--flatten] [--password ] [--allow-signed] [--partial] iris-pdf fields --pdf [--json] [--password ] iris-pdf check --pdf iris-pdf review --pdf [--report ] [--provider anthropic|bedrock] [--model ] [--password ]`; @@ -23,7 +23,7 @@ const OPTIONS = { verify: { type: "string" }, "verify-dpi": { type: "string" }, flatten: { type: "boolean" }, password: { type: "string" }, "allow-signed": { type: "boolean" }, partial: { type: "boolean" }, retag: { type: "boolean" }, // ignored, for older callers: tagging always replaces old tags - strict: { type: "boolean" }, provider: { type: "string" }, model: { type: "string" }, json: { type: "boolean" }, help: { type: "boolean", short: "h" }, + provider: { type: "string" }, model: { type: "string" }, json: { type: "boolean" }, help: { type: "boolean", short: "h" }, } as const; function badArgs(message: string): never { @@ -99,7 +99,7 @@ async function main(argv: string[]): Promise { values: args.values ? (readJson(args.values, "values") as TagOptions["values"]) : undefined, lang: args.lang, title: args.title, ocr: ocr as TagOptions["ocr"], verify: verify !== "off", verifyDpi: dpi, flatten: args.flatten, password: args.password, - allowSigned: args["allow-signed"], partial: args.partial, strict: args.strict, + allowSigned: args["allow-signed"], partial: args.partial, }; const report = newReport(); const pdf = readPdf(args.pdf); diff --git a/src/tag.ts b/src/tag.ts index d0bc7c4..f6d2450 100644 --- a/src/tag.ts +++ b/src/tag.ts @@ -28,11 +28,8 @@ export type TagOptions = OpenOptions & { verifyDpi?: number; flatten?: boolean; partial?: boolean; - strict?: boolean; }; -// With --strict these fail the run instead of only being reported. -const STRICT = ["no_title", "page_not_in_html", "unmatched_text", "missing_glyph", "missing_alt", "unmapped_element", "field_not_in_html", "field_not_in_pdf", "unmatched_link", "alignment_incomplete", "page_not_tagged", "repaired"]; // Throws IrisPdfError. `report` is filled in as far as the run got, either way. export function tag(pdf: Uint8Array, input: PagesInput, opts: TagOptions = {}, report: Report = newReport()): Uint8Array { @@ -121,10 +118,6 @@ export function tag(pdf: Uint8Array, input: PagesInput, opts: TagOptions = {}, r const out = save(doc, src.repaired); report.sizeIncreaseBytes = out.length - pdf.length; if (opts.verify !== false) verify(pdf, baseline, out, opts, filled.changed, overlayText, report); - const strict = report.warnings.filter((w) => STRICT.includes(w.code)); - if (opts.strict && strict.length) { - throw new IrisPdfError("strict", `--strict: ${[...new Set(strict.map((w) => w.code))].join(", ")}`); - } return out; } diff --git a/test/document.test.ts b/test/document.test.ts index 60e76d3..8c527f4 100644 --- a/test/document.test.ts +++ b/test/document.test.ts @@ -59,7 +59,6 @@ test("a damaged PDF is rewritten from its repair, checked, and keeps its encrypt assert.equal(report.verification.textPreserved, true); assert.equal(report.verification.differingPixels, 0); assert.ok(!new mupdf.PDFDocument(out).wasRepaired()); - assert.throws(() => tag(damage(readFixture("text-simple.pdf")), simple, { strict: true }), { code: "strict", message: /repaired/ }); const locked = tag(damage(readFixture("encrypted.pdf")), simple, { password: "open" }); const back = new mupdf.PDFDocument(locked); assert.ok(back.needsPassword() && back.authenticatePassword("open")); diff --git a/test/tag.test.ts b/test/tag.test.ts index e02bed1..f81821e 100644 --- a/test/tag.test.ts +++ b/test/tag.test.ts @@ -138,7 +138,6 @@ test("text Iris left out is kept and reported, not dropped", () => { assert.ok(report.pages[0].lost > 0); assert.ok(report.warnings.some((w) => w.code === "unmatched_text")); assert.match(readingOrder(structTree(doc)), /^Parking Permit Residents may apply/); - assert.throws(() => tag(readFixture("text-simple.pdf"), pages, { strict: true }), { code: "strict" }); }); test("the verification gate sees changed pixels and lost text", () => { @@ -308,7 +307,6 @@ test("a page missing from pages.json is left untouched, warned, and stops the PD assert.ok(page.get("StructParents").isNull()); assert.ok(report.warnings.some((w) => w.code === "page_not_in_html" && w.page === 2)); assert.doesNotMatch(doc.getTrailer().get("Root", "Metadata").readStream().asString(), /pdfuaid:part/); - assert.throws(() => tag(pdf, pagesOf("text-simple"), { strict: true }), { code: "strict" }); }); test("a page whose HTML holds nothing to tag is left as it was, not hidden as an artifact", () => { @@ -320,11 +318,10 @@ test("a page whose HTML holds nothing to tag is left as it was, not hidden as an assert.equal(doc.findPage(0).get("Contents").readStream().asString(), before); assert.ok(report.warnings.some((w) => w.code === "page_not_tagged")); assert.doesNotMatch(doc.getTrailer().get("Root", "Metadata").readStream().asString(), /pdfuaid:part/); - assert.throws(() => tag(pdf, pages, { strict: true }), { code: "strict" }); }); test("a blank page needs no HTML and does not cost the PDF/UA claim", () => { - const { doc, report } = tagFixture("blank-page", { strict: true }); + const { doc, report } = tagFixture("blank-page"); assert.ok(!report.warnings.some((w) => w.code === "page_not_in_html")); assert.match(doc.getTrailer().get("Root", "Metadata").readStream().asString(), /1/); @@ -350,7 +347,7 @@ test("a blank page needs no HTML and does not cost the PDF/UA claim", () => { edit(annots.get(0), d); annots.push(null); const quiet = newReport(); - tag(d.saveToBuffer("").asUint8Array().slice(), pagesOf("blank-page"), { strict: true }, quiet); + tag(d.saveToBuffer("").asUint8Array().slice(), pagesOf("blank-page"), {}, quiet); assert.ok(!quiet.warnings.some((w) => w.code === "page_not_in_html"), name); } }); @@ -383,7 +380,7 @@ test("an unmatched link is described by the words under it", () => { assert.equal(link.objr[0].get("Contents").asString(), "Fees"); }); -test("with no title there is no PDF/UA claim, no empty title, and --strict fails", () => { +test("with no title there is no PDF/UA claim, and no empty title", () => { const pages = { ...pagesOf("text-simple"), title: undefined }; const report = newReport(); const doc = new mupdf.PDFDocument(tag(readFixture("text-simple.pdf"), pages, {}, report)); @@ -391,7 +388,6 @@ test("with no title there is no PDF/UA claim, no empty title, and --strict fails assert.ok(report.warnings.some((w) => w.code === "no_title")); assert.ok(root.get("ViewerPreferences", "DisplayDocTitle").isNull()); assert.ok(root.get("Metadata").isNull(), "no packet to write"); - assert.throws(() => tag(readFixture("text-simple.pdf"), pages, { strict: true }), { code: "strict" }); const packet = xmp('Old1', "", false); assert.doesNotMatch(packet, /pdfuaid:part>|/); assert.match(packet, /Old/, "an old title stays when there is no new one"); From 4eb3ada033410eb12e754376245922ded539b369 Mon Sep 17 00:00:00 2001 From: Blake Bertuccelli-Booth <46652+bbertucc@users.noreply.github.com> Date: Sun, 4 Oct 2026 09:35:07 -0400 Subject: [PATCH 2/2] Remove a blank line Co-Authored-By: Claude Opus 5.5 --- src/tag.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tag.ts b/src/tag.ts index f6d2450..ca14708 100644 --- a/src/tag.ts +++ b/src/tag.ts @@ -30,7 +30,6 @@ export type TagOptions = OpenOptions & { partial?: boolean; }; - // Throws IrisPdfError. `report` is filled in as far as the run got, either way. export function tag(pdf: Uint8Array, input: PagesInput, opts: TagOptions = {}, report: Report = newReport()): Uint8Array { const src = openPdf(pdf, opts);