Release v1.2.0 — addSignaturePlaceholder (#45), ASN.1 DN slice fix (#46), page-by-page streaming, UAX #9 embeddings, USE-lite, smart tables, bold-width fix#47
Merged
Conversation
Closes #46. parseCertificate() was returning issuer.raw / subject.raw slices that did not begin with the ASN.1 SEQUENCE tag (0x30) because decodeAt() only patched direct-child offsets — grandchildren kept offsets relative to their parent's value subarray. Embedding those slices in a CMS IssuerAndSerialNumber produced unparseable output that Adobe Reader and openssl-cms rejected. Fix: new internal shiftOffsets() helper walks every descendant once and absolutises its offset against the original DER buffer. Defensive: parseName() now asserts raw[0] === 0x30 with a diagnostic message — catches any future regression of the ASN.1 offset machinery. Tests: 5 new regression cases in tests/crypto/crypto.test.ts exercising the slice tag, structural re-parse, self-signed roundtrip, and the defensive parseName assertion (94 / 94 green).
…on (#45) Adds a public addSignaturePlaceholder(pdfBytes, options?) API that injects an AcroForm + invisible signature widget placeholder into an existing PDF via incremental update (ISO 32000-1 7.5.6, 12.7.4.5, 12.8). The output is byte-compatible with signPdfBytes() and ready for CMS signing without any downstream tooling having to duplicate the BYTERANGE_PLACEHOLDER / buildSigDict() byte layout. - New module src/core/pdf-sig-placeholder.ts (addSignaturePlaceholder + AddSignaturePlaceholderOptions). - Extract SigDictMetadata from PdfSignOptions so buildSigDict() can be called without key material (placeholder phase has no certs yet). - New PdfModifier.addRawObject(body) primitive for emitting verbatim object bodies so the /Contents <00...> and /ByteRange [0 ...] placeholders remain byte-identical. - Widen isRef()/isArray() to accept PdfValue | undefined for ergonomic dict lookups. - 13 vitest cases covering round-trip, idempotency, AcroForm merge, encryption rejection, fieldName/pageIndex/placeholderBytes validation, /Prev chain integrity. - Export addSignaturePlaceholder + AddSignaturePlaceholderOptions from src/index.ts. Closes #45
…aming Adds buildDocumentPDFStreamPageByPage() and buildPDFStreamPageByPage() that yield Uint8Array chunks aligned at PDF object boundaries (endobj). Each chunk is a self-contained PDF segment: header chunk first, then one indirect object per chunk, then a final xref/trailer/startxref chunk. This is the v1.2 step toward constant-memory PDF generation. The public API is stable; the internal full-buffer assembler is staged for refactor in v1.3 without any caller-visible change. - chunkAtObjectBoundaries() splits a binary PDF string at endobj. - 8 vitest cases covering byte-equality, header/trailer placement, object-boundary alignment, TOC rejection. - Exported from src/index.ts.
…rmalization Adds normalizeBidiEmbeddings() which maps the legacy explicit directional formatting characters (LRE/RLE/LRO/RLO/PDF) to their sealed-isolate equivalents (LRI/RLI/PDI) so the existing BiDi pipeline processes them uniformly. The stack handles nesting up to UAX #9 BD13 max depth (125). Pragmatic simplification: full UAX #9 character-level type override (X4-X5) inside LRO/RLO ranges and embedding leakage across LRE/RLE boundaries are staged for v1.3. The public API surface (resolveBidiRuns) is unchanged; embeddings just work transparently. - 13 new vitest cases (9 normalization unit, 4 round-trip with resolveBidiRuns). - Exported normalizeBidiEmbeddings from src/index.ts. - Updated bidi.ts header docstring to reflect v1.2 capabilities.
Adds src/shaping/use-lite.ts — a public utility module implementing a subset of the Universal Shaping Engine (USE) classification spec for the three Indic scripts pdfnative currently ships shaping for. - classifyUseCategory(cp): returns USE category (B/V/N/H/M/Mpre/Mabv/Mblw/Mpst/R/ZWJ/ZWNJ/O) for any code point. - classifyClusters(cps): splits a code-point sequence into USE-lite clusters with prebase/base/above/below/post/tail buckets, including reph and conjunct-tail detection. Scope note: the bundled Devanagari/Bengali/Tamil shapers continue to use their hand-tuned reordering logic in v1.2; rewiring them to drive from this module is staged for v1.3 once a shaping benchmark harness is in place. Downstream code can already use classifyClusters() directly for custom Indic text analysis. - 23 vitest cases (11 single-codepoint, 12 cluster-level). - Exported UseCategory/UseClassifiedCp/UseCluster types and classifyUseCategory/classifyClusters functions from src/index.ts.
…erators Phase 8 of v1.2.0 release plan. Adds two new generators: - signature-placeholder.ts: demonstrates addSignaturePlaceholder() (#45) including the idempotency contract (second call returns identical bytes). - bidi-embeddings-showcase.ts: demonstrates UAX #9 LRE/RLE/LRO/RLO/PDF normalization via normalizeBidiEmbeddings() with Hebrew RTL examples. Wired into scripts/generate-samples.ts. Total sample PDFs: 157.
Phase 10+11 of the v1.2.0 release plan. - package.json: 1.2.0-alpha.1 -> 1.2.0. - release-notes/v1.2.0.md: rewritten to match what actually shipped (drops the visual-regression and shaper-rewire claims; flags internal page-by-page assembly + UAX #9 X4-X5 overrides + COLRv1 as v1.3 targets). - CHANGELOG.md: [1.2.0] entry rewritten to match the new release notes. - ROADMAP.md: v1.2.0 items moved to Released; v1.3.0 Planned section refreshed (COLRv1 renderer, USE-lite shaper rewire, internal page-by-page assembly, pixel-diff visual regression, UAX #9 X4-X5). - README.md: pdfnative line bumped to v1.2.0; test counts to 1788/52; BiDi line mentions isolates + embeddings; streaming + signatures highlights gain v1.2.0 anchors. - .github/copilot-instructions.md: file/test counts refreshed; new architecture entries for pdf-sig-placeholder; new convention notes for UAX #9 embeddings, USE-lite, signature placeholder, ASN.1 grandchild fix, and page-by-page streaming. - .gitignore: RELEASE_PR_*.md scratchpads. All gates green: npm run typecheck:all clean, npm test = 52 files / 1788 tests, npm run test:generate = 157 PDFs.
…facts
Phase A - scripts/README.md: bump '140+ PDFs' to '157 PDFs (28 generators)', add 4 missing entries (signature-placeholder, bidi-embeddings-showcase, pdfa-latin-embedding, emoji-showcase).
Phase B - README.md: add USE-lite highlight bullet alongside the v1.2 BiDi embeddings line.
Phase C - docs/index.html: BiDi card mentions isolates + embeddings; signatures card mentions addSignaturePlaceholder(); production card bumps to 1788+ tests / 52 files + page-by-page streaming. docs/guides/onboarding.md: v1.1.0 -> v1.2.0. docs/guides/index.html: 23 -> 28 generators / ~140 -> 157 PDFs, new signatures guide entry.
Phase D - new docs/guides/signatures.{md,html} covering the three-line addSignaturePlaceholder() workflow, algorithms, validation with openssl-cms / Adobe Reader, and pointers to the digital-signature + signature-placeholder generators.
Phase E - new llms.txt (machine-readable doc index, 2026 OSS standard) + AGENTS.md (editor-agnostic agent guidance, DRY against .github/copilot-instructions.md). release-notes/v1.2.0.md gains a 'Downstream integration notes' section explicitly addressing pdfnative-mcp and pdfnative-cli maintainers - addSignaturePlaceholder collapses pdfnative-mcp's prepare_signature_placeholder workaround, unlocks v0.4 'sign any PDF in one call', and #46 invalidates cached X.509 issuer/subject slices.
…ator output in signature samples Phase 1 (runtime fix): new stripBidiControls() in src/shaping/bidi.ts strips LRM/RLM, LRE/RLE/PDF/LRO/RLO (U+202A-E), and LRI/RLI/FSI/PDI (U+2066-9) before they reach the font cmap. Applied at the four encoder entry points (pdfString, helveticaWidth, textRuns, ps) so orphan bidi controls in pure-LTR paragraphs no longer surface as .notdef tofu. Exported from src/index.ts. Fixes the tofu seen under 'Orphan PDF (silently dropped)' in bidi-embeddings-showcase.pdf. 6 new tests added; total 1794. Phase 2 (samples): emoji-basic and emoji-table generators now register ['latin', 'emoji'] instead of ['emoji'] alone so ASCII digits route to Noto Sans VF rather than Noto Emoji's em-wide glyphs. Fixes right-margin overflow in emoji-basic.pdf and garbled Duration column in emoji-table.pdf. Phase 3 (docs): clarifier paragraphs added to signature-placeholder.ts and digital-signature.ts samples; new 'Reading the validator output' section in docs/guides/signatures.md explaining that Adobe Reader's 'Validite de la signature inconnue' (self-signed demo CA) and 'Signature non valable' (unsigned placeholder) are expected by-spec behaviour, not bugs. Docs: CHANGELOG, llms.txt, release-notes/v1.2.0.md updated to reflect 1794 tests and the new Fixed/Changed entries.
….2.0) Adds six optional TableBlock fields (all @SInCE 1.2.0): - wrap: 'auto' | 'always' | 'never' (default 'auto') - repeatHeader: boolean (default true) - zebra: boolean | PdfColor - caption: string - minRowHeight: number (default 12) - cellPadding: number (default 4) Architecture: planTable() in pdf-renderers.ts measures once; _paginateBlocks() in pdf-document.ts slices at row boundaries into TableSlice items; renderTable() is page-lifecycle-free and accepts an optional slice arg. Tagged-mode /Table continues across slices via shared tableStructAccum array (ISO 14289-1 section 7.10.6); /Caption emitted once on first slice. Backward compatibility: single-page tables that fit without wrapping are byte-identical to v1.1.0. Multi-page tables now reprint header and wrap on overflow by default; opt back into v1.1 behaviour with repeatHeader:false + wrap:'never'. Also fixes scripts/generators/bidi-embeddings-showcase.ts: restored missing space in orphan-PDF demo paragraph (textwith -> text with). Tests: 14 new (7 planTable unit + 7 end-to-end). Total 1808 tests / 53 files. Samples: 4 new (document/table-wrap-auto.pdf, table-multipage-header-repeat.pdf, table-zebra-caption.pdf, table-smart-autofit.pdf). Total 161 PDFs. Docs: new guides/tables.md + tables.html guide; updated README, CHANGELOG, ROADMAP, AGENTS.md, copilot-instructions.md, llms.txt, docs/index.html, guides/index.html, guides/architecture.md, guides/mcp.md, release-notes/v1.2.0.md.
…polish) Right- and centre-aligned bold text (table headers via enc.f2 and table captions) is now measured with Adobe Helvetica-Bold AFM advance widths instead of Helvetica-Regular. Pre-1.2.0 the renderer measured 'Amount' at ~25.44pt (Regular) but the glyphs rendered ~30.22pt wide (Bold) at 8pt, so the trailing glyph overshot the column boundary by ~2pt and the 't' was clipped/overhung into the neighbour column. Changes: - New public helveticaBoldWidth(str, sz) in src/fonts/encoding.ts (re-exported from root and pdfnative/fonts). - txtR/txtC/txtRTagged/txtCTagged in src/core/pdf-text.ts gain an optional trailing bold flag (default false, backward-compatible). - emitCell() in src/core/pdf-renderers.ts passes bold:isHeader; caption passes bold:true. - Legacy buildPDF() headers in src/core/pdf-builder.ts pass bold:true on all four right/centre header sites. - computeAutoFitColumns() in src/core/pdf-column-fit.ts uses helveticaBoldWidth for the header measurement branch (Latin only). - SigDictMetadata interface re-exported from src/index.ts (release notes already advertised it as public). - Sample fix: document-table-parity makeRows() now formats amounts with toFixed(2) (was rendering '+37.019999999999996'); Amount column slightly widened in the wrap-auto sample. Backward compatibility: existing single-page tables remain byte-identical to v1.1.0 in their BODY rendering. Right- and centre-aligned HEADER glyph positioning shifts by 2-5pt - a documented correctness fix, not a regression. Unicode/CIDFont mode unaffected. Tests: 10 new (8 helveticaBoldWidth + 2 bold-header positioning regression). Total 1818 / 53 files. Docs: cellPadding default corrected 4 -> 3 in release notes, tables.md, copilot-instructions; bold-width fix documented in release notes, CHANGELOG, tables.md migration table.
feat(types): PDF_A_CONFORMANCE_TARGETS + PdfAConformanceTarget exported docs(demo): 10th live-demo example for smart tables Two compounding bugs in the v1.2.0 smart-table renderer surfaced on table-smart-autofit.pdf: 1. renderTable() hardcoded 'i === 3' as the Amount column, forcing the Notes column into Helvetica-Bold + credit/debit colour. autoFitColumns measured Regular metrics; rendering in Bold (~16% wider) overflowed the column, and the clipCells rect chopped the trailing character. Fix: opt-in styling via the new optional ColumnDef.kind === 'amount' field. The legacy buildPDF() financial path keeps i === 3 for byte-identical v1.0/v1.1 output. 2. emitCell() applied truncate(text, col.mx) on every single-line cell, even under wrap: 'auto' where the planner had already sized the column to fit. The redundant char-truncate produced spurious '...' ellipses. Fix: gate the v1.1 char-truncate on wrapMode === 'never'. MCP / Gemini-CLI discoverability: new public const PDF_A_CONFORMANCE_TARGETS = ['pdfa1b','pdfa2b','pdfa2u','pdfa3b'] as const plus PdfAConformanceTarget type are exported from the root. Single source of truth for tooling — pdfnative-mcp can now spread this into its tool-schema enum: instead of hardcoding string literals. Live demo: 10th EXAMPLES entry in docs/app.js — 32-row smart-tables demo exercising wrap='auto', repeatHeader=true, zebra=true and a caption end-to-end in the browser. Playgrounds left untouched (the v1.2.0 features showcase best as an inline live demo). Zero-dependency policy: verified intact — package.json v1.2.0 has no dependencies, no peerDependencies, no optionalDependencies. Tests: 3 new in tests/core/pdf-table.test.ts (kind:'amount' opt-in applies bold + credit; absence of kind keeps default styling; wrap='never' preserves char-truncate ellipsis; wrap='auto' skips it). Total 53 files / 1822 tests, all green. Docs refresh: release-notes/v1.2.0.md (Fixed + Added + Changed + Downstream notes), CHANGELOG, copilot-instructions, AGENTS, README, llms.txt, docs/index.html, docs/guides/tables.md (ColumnDef.kind), docs/guides/pdfa.md (PDF_A_CONFORMANCE_TARGETS), docs/guides/mcp.md (MCP adoption note). RELEASE_PR_v1.2.0.md fully rewritten.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR cuts pdfnative v1.2.0. 100 % backward-compatible. Every new feature is additive or opt-in. Pre-existing PDFs are byte-identical for unchanged code paths in their body rendering; the only documented visual shift is a 2–5 pt correctness fix on right-/centre-aligned bold table headers (Helvetica-Bold metrics now drive bold positioning).
What changed and why
addSignaturePlaceholder(...)API #45): newaddSignaturePlaceholder(pdfBytes, options?)API — AcroForm + invisible signature widget +/Sigdictionary via incremental update (ISO 32000-1 §7.5.6). Idempotent on already-signed PDFs. Enables one-callsignPdfBytes(addSignaturePlaceholder(buildDocumentPDFBytes(...))).parseCertificate(...)returnsissuer.raw/subject.rawwith malformed slices #46): ASN.1decodeAt()now recursively absolutises descendant offsets soparseCertificate()issuer/subjectrawslices begin with the SEQUENCE tag0x30. CMSIssuerAndSerialNumberparses correctly in Adobe Reader, openssl-cms, and pdfnative's own verify path.buildDocumentPDFStreamPageByPage()andbuildPDFStreamPageByPage()emit assembled PDFs asAsyncGenerator<Uint8Array>chunked at PDF object boundaries (\nendobj\n).normalizeBidiEmbeddings()rewrites LRE/RLE/LRO/RLO/PDF (U+202A–U+202E) to sealed-isolate equivalents before BiDi resolution.resolveBidiRuns()invokes the normaliser transparently.classifyUseCategory,classifyClusters) shipped as a public API. Devanagari/Bengali/Tamil shaper rewire follows in v1.3.0.wrap: 'auto' | 'always' | 'never',repeatHeader,zebra,caption,minRowHeight,cellPadding. Two-phase pipeline (planTable()→_paginateBlocks()→renderTable(slice)). Tagged-mode/Tablecontinues across slices via shared structure-tree accumulator (ISO 14289-1 §7.10.6). Existing single-page tables are byte-identical to v1.1.0 in their body rendering.Amountheader overshot its column by ~2 pt at 8 pt; the trailingtwas clipped. New publichelveticaBoldWidth(str, sz)+ opt-inboldflag ontxtR/C/RTagged/CTagged. Wired through smart-table headers, legacybuildPDF(), captions, andautoFitColumns. Unicode/CIDFont mode unaffected.renderTable()no longer hardcodes the 4th column (i === 3) as "Amount" with bold + credit/debit colour. Styling is opt-in via the newColumnDef.kind === 'amount'field. LegacybuildPDF()(financial statement path) keeps the historical heuristic for byte-identical v1.0/v1.1 output.emitCellnow applies the v1.1 character truncate (mx/mxH) only whenwrap: 'never'. Under'auto'(default) and'always', the planner has already sized the column to fit; the redundant char-truncate previously inserted spurious…ellipses in auto-fitted tables.PDF_A_CONFORMANCE_TARGETS = ['pdfa1b','pdfa2b','pdfa2u','pdfa3b'] as const+PdfAConformanceTargettype exported from the root. Single source of truth for tooling —pdfnative-mcpconsumes viaimport { PDF_A_CONFORMANCE_TARGETS } from 'pdfnative'for its tool-schemaenum:, materially improving how Gemini-CLI and other LLM agents discover the legalpdfAvalues.SigDictMetadatare-exported from the root (release notes already advertised it).stripBidiControls()— prevents.notdeftofu on pure-LTR paragraphs containing orphan controls.bidi-embeddings-showcase.pdforphan-PDF paragraph ("textwith"→"text with");table-wrap-auto.pdf/table-zebra-caption.pdfamount columns formatted viatoFixed(2)(was+37.019999999999996); emoji samples registerlatinalongsideemojiso ASCII digits route to Noto Sans VF; signature samples gain inline clarifier paragraphs.wrap: 'auto',repeatHeader: true,zebra: true,captionend-to-end in the browser.Zero-dependency guarantee — still intact
package.jsonv1.2.0 has nodependencies, nopeerDependencies, nooptionalDependencies. Every new feature is implemented with pdfnative's own primitives.Deferred to v1.3.0
COLRv1 colour emoji renderer; USE-lite shaper rewire (Devanagari/Bengali/Tamil); internal page-by-page assembly; pixel-diff visual regression; UAX #9 X4–X5 character-level overrides.
Full notes: release-notes/v1.2.0.md.
Downstream coordination
PDF_A_CONFORMANCE_TARGETSfor tool-schemaenum:; collapseprepare_signature_placeholderto a thin wrapper aroundaddSignaturePlaceholder(); forward the six new optionalTableBlockfields plusColumnDef.kindinadd_table. Repinning topdfnative@1.2.0lights up all v0.4.0 roadmap items.sign; invalidate any cached issuer/subject DN slices from previously-signed PDFs (Bug —parseCertificate(...)returnsissuer.raw/subject.rawwith malformed slices #46 fix). To preserve v1.1.0 table output, setwrap: 'never', repeatHeader: false.Risk & rollback
Risk surface: (a) renderer fix for
wrap: 'auto'— 3 new regression tests guard the byte-stability boundary; (b)ColumnDef.kindopt-in — legacy financial PDFs go throughbuildPDF()which retainsi === 3(byte-stable); (c) Helvetica-Bold positioning — 2 regression tests cover the bold header right-edge. Rollback: revert the tip commit; no schema/data migrations involved.Related Issues
Fixes #45
Fixes #46
Checklist
npm run test) — 53 files / 1822 tests, all greennpm run typecheck:all) — clean (src/ + tests/ + scripts/)npm run lint) — clean[1.2.0]covering every changeColumnDef.kind,wrap,repeatHeader; bold-header positioning shift is a documented correctness fixAdditional release hygiene
npm run test:generate— 161 sample PDFs produced;table-smart-autofit.pdfno longer shows clipped Notes column or stray…ellipsesnpm run validate:pdfa— veraPDF green on all PDF/A samplespackage.jsonbumped to1.2.0, zero runtime dependencies verifiedrelease/v1.2.0git tag v1.2.0 && git push origin v1.2.0to trigger the npm publish workflow