pdf-parser.js 4.5.6 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +5 -5
  2. package/dist/codec.d.cts +8 -0
  3. package/dist/codec.d.ts +8 -0
  4. package/dist/content-write.d.cts +1 -1
  5. package/dist/content-write.d.ts +1 -1
  6. package/dist/{embedded-font-B4p9a3O3.d.ts → embedded-font-BRhZfEN2.d.ts} +1 -1
  7. package/dist/{embedded-font-oc65VGlr.d.cts → embedded-font-DJUn9kMH.d.cts} +1 -1
  8. package/dist/embedded-font-write.d.cts +1 -1
  9. package/dist/embedded-font-write.d.ts +1 -1
  10. package/dist/embedded-font.cjs +1 -1
  11. package/dist/embedded-font.d.cts +1 -1
  12. package/dist/embedded-font.d.ts +1 -1
  13. package/dist/embedded-font.js +1 -1
  14. package/dist/filters.cjs +14 -3
  15. package/dist/filters.d.cts +4 -0
  16. package/dist/filters.d.ts +4 -0
  17. package/dist/filters.js +14 -3
  18. package/dist/font-registry.d.cts +1 -1
  19. package/dist/font-registry.d.ts +1 -1
  20. package/dist/gdef-table.cjs +48 -0
  21. package/dist/gdef-table.d.cts +15 -0
  22. package/dist/gdef-table.d.ts +15 -0
  23. package/dist/gdef-table.js +42 -0
  24. package/dist/gsub-table-Bm9kSMzk.d.cts +16 -0
  25. package/dist/gsub-table-CdfESH2-.d.ts +16 -0
  26. package/dist/gsub-table.cjs +404 -87
  27. package/dist/gsub-table.d.cts +2 -2
  28. package/dist/gsub-table.d.ts +2 -2
  29. package/dist/gsub-table.js +404 -89
  30. package/dist/images-read.cjs +12 -2
  31. package/dist/images-read.d.cts +5 -0
  32. package/dist/images-read.d.ts +5 -0
  33. package/dist/images-read.js +12 -2
  34. package/dist/index.d.cts +1 -1
  35. package/dist/index.d.ts +1 -1
  36. package/dist/layout.cjs +6 -1
  37. package/dist/layout.d.cts +16 -0
  38. package/dist/layout.d.ts +16 -0
  39. package/dist/layout.js +6 -1
  40. package/dist/read.cjs +9 -4
  41. package/dist/read.js +9 -4
  42. package/dist/tounicode.cjs +1 -1
  43. package/dist/tounicode.js +1 -1
  44. package/dist/write.cjs +50 -5
  45. package/dist/write.d.cts +1 -1
  46. package/dist/write.d.ts +1 -1
  47. package/dist/write.js +50 -5
  48. package/package.json +3 -3
  49. package/dist/gsub-table-BRTQ8EUW.d.ts +0 -10
  50. package/dist/gsub-table-URczLd85.d.cts +0 -10
package/README.md CHANGED
@@ -234,8 +234,8 @@ The package is layered from generic primitives outward to the codec itself:
234
234
  - **`src/util/`** — two small independently-duplicated copies of family-shared logic: `base64.ts` (verbatim copy of `odf.js`'s own, replacing a former `ooxml.js` dependency for this one helper) and `abort.ts` (`throwIfAborted`, called at every page loop boundary — there is no `await` point in this synchronous pipeline for cancellation to hook into implicitly).
235
235
  - **`src/crypto/`** — MD5, SHA-256/384/512, RC4, and AES-CBC, hand-written with zero local imports, plus a thin `random.ts` wrapper around `globalThis.crypto.getRandomValues` for the writer's own salts/file keys/IVs. Not a preference: ISO 32000-1's key-derivation algorithms name MD5 and RC4 directly, neither offered by any portable platform crypto API, and `crypto.subtle` is asynchronous where this codec's read and write paths are both synchronous end to end — `getRandomValues` itself, unlike `crypto.subtle`, is synchronous and portable, so it is used directly rather than hand-written. Reaching for `node:crypto` would break the browser bundle. Each hash/cipher module cites its specification (RFC 1321, FIPS 180-4, FIPS 197) and is tested against published conformance vectors.
236
236
  - **The codec itself, importing only `layout`/`bytes`/`image`/`crypto`/`util` plus `document-schema.js`'s port types (no OOXML or ODF knowledge):**
237
- - **Write**: `objects.ts` (the `PdfObject` discriminated union), `afm-widths.ts`/`encoding.ts`/`winansi.ts`/`fonts.ts` (standard-14 metrics, WinAnsi encoding, family resolution), `font-registry.ts` (resolution port plus `resolveFaceWithRegistry`, the one step both measurer and writer resolve through so they can never disagree about which face a `LayoutFont` means), `font-face.ts` (`readFontFace`, reading a standalone font file's family/bold/italic triple off its `name`/`OS/2`/`head` tables), `measure.ts`/`text-layout.ts` (greedy line-wrapping against either standard-14 AFM widths plus per-family correction or a resolved face's own real `hmtx` advances — never both), `content-write.ts` (`LayoutItem[]` → content-stream operators, with text branching on standard-14 vs embedded face encoding, pair-kerning split into `TJ` arrays, stroke `style` becoming real dash/line-cap state, and `/P <</MCID n /OC R>> BDC`…`EMC` spans around items carrying a structure owner or an optional-content layer — the one marked-content spelling both read-side channels resolve through), `write.ts` (the full object graph, cross-reference table, trailer, and embedded font groups, plus the document-level round-trip surfaces: the `/Names /EmbeddedFiles` attachments tree and `/Outlines` bookmark tree, `/OCProperties` optional-content groups with each layer stated explicitly into the default configuration's `/ON` or `/OFF` list, the `/AcroForm` field tree with the merged-field/widget spelling (a single widget merges into its field dict; further widgets are separate `/Subtype /Widget` kids, and fully-qualified names decompose back into the `/T` chain), the `/StructTreeRoot` element tree with its `/ParentTree` number tree associating each marked item's MCID to its owning element, and the package-level residue rows restored inline onto the Catalog or trailer — the XMP packet as an uncompressed `/Metadata` stream, and any row whose serialisation references source-file objects skipped rather than emitted as a dangling reference).
238
- - **sfnt font tables**: `sfnt.ts` (bounds-checked table-directory reader), `cmap-table.ts` (character code → glyph ID, formats 0/4/6/12, exposed both as the best Unicode lookup and as every subtable individually, since a (3, 0) or (1, 0) subtable is keyed by a font's own codes rather than by code points), `hmtx-table.ts` (per-glyph advance widths), `font-tables.ts` (`head`/`maxp`/`OS/2`/`post`/`name`, including `post`'s own glyph names where a font still carries them), `glyf.ts` (`loca` offset index, per-glyph headers, composite component records, `glyphInkBounds`), `math-table.ts` (OpenType `MATH` constants/glyph-info/variants subtables). `ot-layout-common.ts` (Coverage/ClassDef, stored as sorted glyph ranges searched by bisection). `gpos-table.ts` reads `GPOS` for exactly one thing: pair-advance kerning through the `kern` feature, both PairPos formats and LookupType 9 Extension indirection — mark attachment, cursive joining, and contextual positioning have no consumer here. Every parser degrades to `undefined` on a missing/truncated table rather than throwing.
237
+ - **Write**: `objects.ts` (the `PdfObject` discriminated union), `afm-widths.ts`/`encoding.ts`/`winansi.ts`/`fonts.ts` (standard-14 metrics, WinAnsi encoding, family resolution), `font-registry.ts` (resolution port plus `resolveFaceWithRegistry`, the one step both measurer and writer resolve through so they can never disagree about which face a `LayoutFont` means), `font-face.ts` (`readFontFace`, reading a standalone font file's family/bold/italic triple off its `name`/`OS/2`/`head` tables), `measure.ts`/`text-layout.ts` (greedy line-wrapping against either standard-14 AFM widths plus per-family correction or a resolved face's own real `hmtx` advances — never both), `content-write.ts` (`LayoutItem[]` → content-stream operators, with text branching on standard-14 vs embedded face encoding, pair-kerning split into `TJ` arrays, stroke `style` becoming real dash/line-cap state, and `/P <</MCID n /OC R>> BDC`…`EMC` spans around items carrying a structure owner or an optional-content layer — the one marked-content spelling both read-side channels resolve through), `write.ts` (the full object graph, cross-reference table, trailer, and embedded font groups, plus the document-level round-trip surfaces: the `/Names /EmbeddedFiles` attachments tree and `/Outlines` bookmark tree, `/OCProperties` optional-content groups with each layer stated explicitly into the default configuration's `/ON` or `/OFF` list, the `/AcroForm` field tree with the merged-field/widget spelling (a single widget merges into its field dict; a multi-widget field's widgets are separate `/Subtype /Widget` kids, each also referenced from its page's `/Annots` — the same annotation object in both places, so a viewer reading only page-level annotations still renders every widget — and fully-qualified names decompose back into the `/T` chain), the `/StructTreeRoot` element tree with its `/ParentTree` number tree associating each marked item's MCID to its owning element, and the package-level residue rows restored inline onto the Catalog or trailer — the XMP packet as an uncompressed `/Metadata` stream, and any row whose serialisation references source-file objects skipped rather than emitted as a dangling reference).
238
+ - **sfnt font tables**: `sfnt.ts` (bounds-checked table-directory reader), `cmap-table.ts` (character code → glyph ID, formats 0/4/6/12, exposed both as the best Unicode lookup and as every subtable individually, since a (3, 0) or (1, 0) subtable is keyed by a font's own codes rather than by code points), `hmtx-table.ts` (per-glyph advance widths), `font-tables.ts` (`head`/`maxp`/`OS/2`/`post`/`name`, including `post`'s own glyph names where a font still carries them), `glyf.ts` (`loca` offset index, per-glyph headers, composite component records, `glyphInkBounds`), `math-table.ts` (OpenType `MATH` constants/glyph-info/variants subtables). `ot-layout-common.ts` (Coverage/ClassDef, stored as sorted glyph ranges searched by bisection). `gpos-table.ts` reads `GPOS` for exactly one thing: pair-advance kerning through the `kern` feature, both PairPos formats and LookupType 9 Extension indirection — mark attachment, cursive joining, and contextual positioning have no consumer here. `gsub-table.ts` reads `GSUB` for the default-on feature set (`liga`/`rlig`/`calt`/`clig`) through Single, Ligature, Contextual, and Chaining Contextual substitutions (LookupTypes 1, 4, 5, 6, and the 7 Extension wrapper), with each lookup's `lookupFlag` skip semantics — ignore-base/ligature/marks, mark attachment classes, mark filtering sets — evaluated against `gdef-table.ts`'s reading of the font's `GDEF`; `smcp`/`dlig` stay opt-in through `GsubShaperOptions`, the switch a real shaper's caller flips. Every parser degrades to `undefined` on a missing/truncated table rather than throwing.
239
239
  - **sfnt subsetting**: `sfnt-subset.ts` — a TrueType-outline glyph subsetter (Unicode code points → glyph IDs via `cmap`, transitive closure over `glyf` composite components, rebuilt sfnt carrying only used outlines). **Glyph IDs are preserved, never renumbered**, keeping composite component references valid and making CID == GID trivially true. Output rebuilds `head`/`hhea`/`maxp`/`loca`/`glyf`/`hmtx`, copies hinting programs verbatim, stubs `post`, omits `cmap`/`name`/`OS/2`/`GSUB`/`GPOS`/`kern` (none read through a `CIDFontType2` program per ISO 32000-1 9.9). Applies to `glyf`-flavoured fonts only; CFF returns `undefined`.
240
240
  - **Embedded math font**: `math-font.ts` (parses/caches the vendored STIX Two Math font, exposing size-specific `MathFontMetrics` and stretchy-glyph entry points), `math-stretch.ts` (OpenType MATH two-stage stretching: pick smallest pre-built variant reaching target, else assemble from repeated parts with seams overlapped), `math-font-write.ts` (builds the `/Type0`/`/CIDFontType0`/`/FontDescriptor`/`/FontFile3`/ToUnicode group), `math-content-write.ts` (`PositionedFormula[]` → content-stream bytes, Identity-H CIDs for text, `re`/`m`/`l` operators for rules, glyph-ID-addressed text objects for stretched constructions wrapped in `/ActualText`).
241
241
  - **Embedded text faces**: `embedded-font.ts` (parses one TrueType-outline face's metrics and `GPOS` pair kerning, and `encodeForShowEmbedded` — the single code path both measurement and text-showing go through so encoding and measuring cannot disagree). Every geometry field is converted into PDF's 1000-units-per-em glyph space. `embedded-font-write.ts` builds the `/Type0`/`/CIDFontType2`/`/FontDescriptor`/`/FontFile2`/ToUnicode group, with `/CIDToGIDMap /Identity` written explicitly and `/Length1` set to the **uncompressed** subset length. Its subset tag is a CRC32 over the face's PostScript name and glyph-ID list, so identical input yields byte-identical output.
@@ -264,7 +264,7 @@ Dependency direction is strictly downward and checkable: `layout` imports only `
264
264
  - **Nothing in this codec's reader accepts, prompts for, or guesses a password.** Authenticating as owner is a permissions escalation, not a way to read a file you were already allowed to read.
265
265
  - **`writePdf` produces real, spec-conformant encryption for all four of the schemes above** (`src/encrypt-write.ts`, `WritePdfOptions.encryption`) — genuine `/O`/`/U`/`/OE`/`/UE`/`/Perms` computation per ISO 32000-2 7.6.4.4's Algorithms 3, 8, 9, and 10, a real user and (optional, defaulting to the user password) owner password, and per-`/P`-bit permission flags. Unlike the reader, the writer's own password is not scoped to empty: a genuinely non-empty user password is fully supported and round-trips against the published algorithm (see `encrypt-write.test.ts`), even though this package's own reader — scoped as above — can only ever open the result back up if the user password used to encrypt it was itself empty. Two boundaries are deliberately narrower than the full spec rather than approximated silently: a revision 2-4 (rc4-40/rc4-128/aes-128) password must be plain printable ASCII, not the full 8-bit PDFDocEncoding table, and a revision 6 (aes-256) password is Unicode-normalised with NFKC rather than the full SASLPrep/stringprep profile (prohibited-character tables and the bidirectional-text rule are not implemented). Both throw `PdfEncryptionError` for a password outside that scope rather than silently mis-encoding it into one that will not open the file it was meant to protect.
266
266
  - **`CCITTFaxDecode`, `JBIG2Decode` and `JPXDecode` images all decode for real** via hand-written decoders (`src/image/ccitt.ts` for ITU-T T.4/T.6 fax; `src/image/jbig2*.ts` for ITU-T T.88; `src/image/jpeg2000*.ts` for ISO/IEC 15444-1). JPEG images (`DCTDecode`) pass through losslessly in both directions. See [JBIG2 scope](#jbig2-scope) and [JPEG 2000 scope](#jpeg-2000-scope) for exactly what is and is not implemented.
267
- - **The write side re-encodes bilevel images as CCITT Group 4** (`src/image/ccitt-encode.ts`, a hand-written ITU-T T.6 encoder sharing the decoder's own code-table transcription): a grayscale image whose samples are all 0 or 255, with no soft mask, is written as `/CCITTFaxDecode` with `/K -1` whenever the G4 encoding comes out smaller than Flate over the same pixels — which for vertically coherent bilevel content (a scan's edges and text baselines) it does by an order of magnitude. Whichever encoding is smaller wins, deterministically, so decorrelated or repetitive bilevel content where Flate happens to win keeps Flate. **JBIG2 and JPEG 2000 re-encoding are permanent boundaries, not gaps**: a hand-written JBIG2 encoder is research-grade symbol-dictionary design and a JPEG 2000 encoder is the full EBCOT/wavelet stack, and re-encoding lossy JPX back is not fidelity-preserving anyway. Verbatim passthrough of a source's original JBIG2/JPX compressed bytes would need `LayoutImageAsset` and `document-schema.js`'s own `ContentImageBlock` to carry the original filter and bytes (the model normalises to decoded pixels at read time), a cross-package schema design change recorded in #975's scorecard rather than smuggled into this feature.
267
+ - **The write side re-encodes bilevel images as CCITT Group 4** (`src/image/ccitt-encode.ts`, a hand-written ITU-T T.6 encoder sharing the decoder's own code-table transcription): a grayscale image whose samples are all 0 or 255, with no soft mask, is written as `/CCITTFaxDecode` with `/K -1` whenever the G4 encoding comes out smaller than Flate over the same pixels — which for vertically coherent bilevel content (a scan's edges and text baselines) it does by an order of magnitude. Whichever encoding is smaller wins, deterministically, so decorrelated or repetitive bilevel content where Flate happens to win keeps Flate. **JBIG2 and JPEG 2000 re-encoding remain permanent boundaries, not gaps**: a hand-written JBIG2 encoder is research-grade symbol-dictionary design and a JPEG 2000 encoder is the full EBCOT/wavelet stack, and re-encoding lossy JPX back is not fidelity-preserving anyway. **What no longer follows from that boundary is generation loss on a pdf-to-pdf round trip**: `LayoutImageAsset.original` (the layout twin of `document-schema.js`'s `ContentImageOriginal`, landed through the #975-scorecard schema change it named) carries a source's own JBIG2/JPX compressed stream beside the decoded canonical, captured at read time together with the decoded `/JBIG2Globals` segments a symbol-dictionary stream needs, and the writer re-embeds those bytes verbatim under their own filter — the decoded canonical never reaches the file at all for such an image, a source soft mask still re-emits as a generated `/SMask`, and the round trip pays zero generation loss for exactly the two filters this package cannot re-encode.
268
268
  - **`interpret.ts` tracks general vector paths, not just axis-aligned `re` rectangles.** `m`/`l`/`c`/`v`/`y`/`h` (and `re` itself) accumulate real subpaths — CTM-transformed line/cubic segments, open or closed — and any paint operator emits an item built from them. A recovered path matching one of three characteristic shape patterns comes back as that shape's own kind, not a generic `LayoutPath`: an axis-aligned closed four-corner subpath is a `LayoutRect`; a closed subpath of four cubic segments meeting its bounding box at cardinal points with kappa-ratio control points is a `LayoutEllipse`; an open single-straight-segment stroke-only subpath is a `LayoutLine` (tolerance: `max(1e-3pt, 1e-4 × extent)`). **These are deliberate, bounded heuristics** — a false positive changes an item's kind, never its geometry, since every detected shape reproduces its source path's own points exactly. Off-axis rotations, non-quadrant curves, polygons, and multi-subpath figures stay a `LayoutPath`.
269
269
  - **`writePdf`/`readPdf` round-trip a page's own `notes` field via a hidden `/Subtype /Text` annotation** with the `Hidden` flag set so it never renders or prints, distinguished from a genuine third-party sticky note by an internal author marker. This is a round-trip mechanism specific to this package's own writer/reader pair. `documents.js` uses this to carry pptx/odp speaker notes through PDF.
270
270
  - **STIX Two Math is a CFF-flavoured OpenType font, not TrueType/glyf** — the **entire** `CFF ` table is embedded verbatim as a single `/FontFile3` `/Subtype /CIDFontType0C` stream (a real, correct, working embedded font, just not glyph-subsetted). Everything else genuinely IS built from a targeted parse of only what's used: `cmap` resolves exactly the Unicode code points a document's formulas reference, and the emitted `/W` widths array covers only drawn glyph IDs. A CID-keyed composite font built this way needs no `/CIDToGIDMap` — per ISO 32000-1 9.7.4.2, a `/CIDFontType0` whose `/FontFile3` is a non-CID-keyed CFF program is read with CID directly indexing `CharStrings` by glyph order (CID == GID).
@@ -273,7 +273,7 @@ Dependency direction is strictly downward and checkable: `layout` imports only `
273
273
  - **Real per-glyph ink bounds are measured from the outline (`inkAscentPt`/`inkDescentPt`), computed by walking each glyph's Type 2 charstring.** `ascentPerEm`/`descentPerEm` remain alongside them as the uniform face-wide figure, still the right measure for anything sized against the font rather than particular characters, and the fallback for a glyph with no outline to measure. An ink box is genuinely tight, which for a math font is often _larger_ than the nominal metrics (over a tenth of STIX Two Math's repertoire draws above its nominal ascent). `inkDescentPt` is negative where the glyph's lowest ink sits above the baseline.
274
274
  - **A `LayoutLine`/`LayoutPath` `style` of `dashed`/`dotted` becomes a real dash-array (`d`) operator scaled to the stroke's own width; `double` becomes two genuinely separate offset strokes.** Dash lengths are stroke-width multiples so a hairline and a thick rule both read as recognisably dashed: `dashed` emits `[3w 3w] 0 d`, `dotted` emits `[0 2w] 0 d` with a `1 J` round cap (the zero on-length under a round cap paints a filled circle — exactly a dot; under PDF's default butt cap it paints nothing). Both are reset immediately after the paint operator (`[] 0 d`, `0 J`) since the graphics state persists for the whole content stream. `double` has no PDF operator and is drawn as geometry: width `w` splits into three equal bands, each rule `w/3` wide with centreline `w/3` offset, outer edges matching the single stroke's. The read side tracks the `d` operator's own dash array as graphics state and recovers `dashed`/`dotted` back onto a stroked `LayoutLine`/`LayoutPath` from it — a zero on-length reads as `dotted`, any other non-empty array as `dashed`, an empty array (or none at all) as `solid`. `double` has no operator of its own to recover from, so it reads back as whatever geometry its two offset strokes actually are (typically two plain solid lines or paths), not as `double`.
275
275
  - **An embedded `CIDFontType2` program needs no `cmap` table of its own**, and `sfnt-subset.ts`'s output doesn't carry one — character code → CID goes through the `Type0` font's `/Encoding` (Identity-H, so CID == character code), and CID → GID through `/CIDToGIDMap /Identity` (matching the GID-preserving design). Both happen inside the PDF's object graph, before the embedded font program is consulted (ISO 32000-1 9.7.4.2).
276
- - **`GPOS` pair kerning and `GSUB`'s default ligature features are read and applied for an embedded face; the contextual and opt-in layout features are not.** An `fi` in a face whose `liga` feature declares the ligature draws as that real ligature glyph (advances, kerning, subsetting, and the ToUnicode CMap all describing the one substituted sequence — a ligature's ToUnicode entry maps back to its whole character run, so copy/paste recovers `fi`, not one glyph's worth of it). Still refused by name: the contextual features (`calt`, `clig`), which reach through Contextual/Chaining Contextual lookup machinery out of proportion to apply; the opt-in features (`smcp`, `dlig` and friends), which no real shaper turns on until a caller asks; and lookups with a nonzero `lookupFlag`, whose behaviour depends on GDEF glyph classes this package does not read. The legacy `kern` table is not read either (neither Carlito nor Caladea ships one). A kerned run is shown with `TJ`, and the sign of a `TJ` number is the opposite of the adjustment it expresses (ISO 32000-1 9.4.3: a positive number moves the next glyph closer). A run with no kerning pairs stays as one unsplit hex string with `Tj`.
276
+ - **`GPOS` pair kerning and `GSUB`'s default-on feature set (`liga`/`rlig`/`calt`/`clig`) are read and applied for an embedded face; the opt-in layout features are not — until a caller names them.** An `fi` in a face whose `liga` feature declares the ligature draws as that real ligature glyph (advances, kerning, subsetting, and the ToUnicode CMap all describing the one substituted sequence — a ligature's ToUnicode entry maps back to its whole character run, so copy/paste recovers `fi`, not one glyph's worth of it). The contextual features apply for real: Carlito's `calt` is a pair of Chaining Contextual format 3 lookups carrying the Greek uppercase accent discipline, and they shape `ΆΙ` to Alpha + Iotadieresis exactly as HarfBuzz does. Every lookup's `lookupFlag` is honoured against the font's `GDEF` (which `gdef-table.ts` reads for glyph classes, mark attachment classes, and mark glyph sets): Caladea Italic's one `liga` lookup carries the ignore-marks flag, so its `fi`/`fl` ligatures form across combining marks, the mark drawing after the ligature glyph and its text joining the ligature's ToUnicode destination — the cluster merge a real shaper performs. Still refused by default: the opt-in features (`smcp`, `dlig` and friends), which no real shaper turns on until a caller asks — `buildGsubShaper`'s `optInFeatures` option is that ask. The legacy `kern` table is not read either (neither Carlito nor Caladea ships one). A kerned run is shown with `TJ`, and the sign of a `TJ` number is the opposite of the adjustment it expresses (ISO 32000-1 9.4.3: a positive number moves the next glyph closer). A run with no kerning pairs stays as one unsplit hex string with `Tj`.
277
277
  - **Kerning applies to whole shown strings, so a wrap decision does not see a pair straddling the boundary between two separately-measured words.** The width a line reports is the width the page draws; making the wrap decision itself exact would mean widening the `TextMeasurer` port for a sub-point difference that only ever errs towards breaking a line early.
278
278
  - **`font-substitutes.ts` maps both `Calibri` and `Calibri Light` onto the same ordinary-weight Carlito face** — Carlito ships only one weight per style axis (no Light face exists upstream to vendor), so `Calibri Light` substitutes to standard Carlito rather than a genuinely lighter face. An honest, documented approximation: width metrics match, visibly thinner strokes do not — and a REPORTED one, never silent: the registry's `onSubstitution` channel fires for it like every vendored substitution, naming `Calibri Light` → `carlito` so a caller can act on the weight mismatch (pinned by test in write-embedded-font.test.ts).
279
279
  - **`cff-probe.ts`'s CID-keyed CFF guard exists for a source-embedded-font phase this package hasn't built yet** — it is not wired into any write path today. Every face currently embedded is `glyf`-flavoured TrueType, and `sfnt-subset.ts` already refuses anything else before this guard would run.
@@ -316,7 +316,7 @@ A differential test pins the _set_ of template positions and offsets but not the
316
316
 
317
317
  **Supply a `FontRegistry` and Calibri/Cambria stop being an approximation at all** — the vendored Carlito/Caladea faces are the real metric-compatible TrueType families, resolved automatically. Aptos still has no vendored substitute. A resolved face is measured at its own real `hmtx` advances (never the width-correction table — applying both would silently draw text narrower than measured), subsetted to used glyphs, and embedded as a real `/Type0` + `/CIDFontType2` + `/FontFile2` group. Remaining limits: only TrueType (`glyf`) outlines can be embedded; a character with no glyph is drawn as `.notdef` and reported through `onMissingGlyph`; and vertical-metric policy is caller-chosen.
318
318
 
319
- **The acceptance bar for embedded-font fidelity is "no page-count drift on a real corpus", not "line-identical".** An embedded run is placed at real `hmtx` advances adjusted by real `GPOS` pair kerning, shaped through real `GSUB` default ligatures — measured and drawn from one shared computation. What separates this from line-identical: contextual alternates and the opt-in layout features are never applied, and kerning is within each shown string rather than across whitespace boundaries a wrap decision measures separately.
319
+ **The acceptance bar for embedded-font fidelity is "no page-count drift on a real corpus", not "line-identical".** An embedded run is placed at real `hmtx` advances adjusted by real `GPOS` pair kerning, shaped through real `GSUB` ligatures and contextual alternates — measured and drawn from one shared computation. What separates this from line-identical: the opt-in layout features are never applied unless a caller names them, and kerning is within each shown string rather than across whitespace boundaries a wrap decision measures separately.
320
320
 
321
321
  **The one exception is math-formula rendering (`WritePdfOptions.formulas`): this genuinely embeds a real, hand-parsed font.** Real box-model glyph runs through the embedded STIX Two Math font with genuine per-glyph metrics and font-wide layout constants parsed directly from the `MATH` table. Stretchy constructions are real: a `MathVariants` variant or assembly resolved, measured against actual outlines, and drawn by glyph ID.
322
322
 
package/dist/codec.d.cts CHANGED
@@ -253,6 +253,14 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
253
253
  base64: z.ZodString;
254
254
  widthPx: z.ZodNumber;
255
255
  heightPx: z.ZodNumber;
256
+ original: z.ZodOptional<z.ZodObject<{
257
+ filter: z.ZodEnum<{
258
+ jbig2: "jbig2";
259
+ jpeg2000: "jpeg2000";
260
+ }>;
261
+ base64: z.ZodString;
262
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
263
+ }, z.core.$strip>>;
256
264
  }, z.core.$strip>>;
257
265
  destinations: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
266
  name: z.ZodString;
package/dist/codec.d.ts CHANGED
@@ -253,6 +253,14 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
253
253
  base64: z.ZodString;
254
254
  widthPx: z.ZodNumber;
255
255
  heightPx: z.ZodNumber;
256
+ original: z.ZodOptional<z.ZodObject<{
257
+ filter: z.ZodEnum<{
258
+ jbig2: "jbig2";
259
+ jpeg2000: "jpeg2000";
260
+ }>;
261
+ base64: z.ZodString;
262
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
263
+ }, z.core.$strip>>;
256
264
  }, z.core.$strip>>;
257
265
  destinations: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
266
  name: z.ZodString;
@@ -1,6 +1,6 @@
1
1
  import { r as StandardFontName } from "./afm-widths-CneRyu5l.cjs";
2
2
  import { LayoutItem } from "./layout.cjs";
3
- import { r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-oc65VGlr.cjs";
3
+ import { r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-DJUn9kMH.cjs";
4
4
  import { WinAnsiSubstitution } from "./winansi.cjs";
5
5
  import { LayoutFont, TextMeasurer } from "document-schema.js";
6
6
  //#region src/content-write.d.ts
@@ -1,6 +1,6 @@
1
1
  import { r as StandardFontName } from "./afm-widths-CneRyu5l.js";
2
2
  import { LayoutItem } from "./layout.js";
3
- import { r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-B4p9a3O3.js";
3
+ import { r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-BRhZfEN2.js";
4
4
  import { WinAnsiSubstitution } from "./winansi.js";
5
5
  import { LayoutFont, TextMeasurer } from "document-schema.js";
6
6
  //#region src/content-write.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as SfntFont } from "./sfnt-V08bb5ut.js";
2
- import { t as GsubShaper } from "./gsub-table-BRTQ8EUW.js";
2
+ import { i as GsubShaper } from "./gsub-table-CdfESH2-.js";
3
3
  //#region src/embedded-font.d.ts
4
4
  declare const NOTDEF_GLYPH_ID = 0;
5
5
  interface EmbeddedFaceMetrics {
@@ -1,5 +1,5 @@
1
1
  import { t as SfntFont } from "./sfnt-V08bb5ut.cjs";
2
- import { t as GsubShaper } from "./gsub-table-URczLd85.cjs";
2
+ import { i as GsubShaper } from "./gsub-table-Bm9kSMzk.cjs";
3
3
  //#region src/embedded-font.d.ts
4
4
  declare const NOTDEF_GLYPH_ID = 0;
5
5
  interface EmbeddedFaceMetrics {
@@ -1,5 +1,5 @@
1
1
  import { n as PdfDict, r as PdfObject } from "./objects-B7HdMeWS.cjs";
2
- import { t as EmbeddedFace } from "./embedded-font-oc65VGlr.cjs";
2
+ import { t as EmbeddedFace } from "./embedded-font-DJUn9kMH.cjs";
3
3
  import { SfntSubsetResult } from "./sfnt-subset.cjs";
4
4
  //#region src/embedded-font-write.d.ts
5
5
  declare function embeddedSubsetTag(postScriptName: string, glyphIds: readonly number[]): string;
@@ -1,5 +1,5 @@
1
1
  import { n as PdfDict, r as PdfObject } from "./objects-B7HdMeWS.js";
2
- import { t as EmbeddedFace } from "./embedded-font-B4p9a3O3.js";
2
+ import { t as EmbeddedFace } from "./embedded-font-BRhZfEN2.js";
3
3
  import { SfntSubsetResult } from "./sfnt-subset.js";
4
4
  //#region src/embedded-font-write.d.ts
5
5
  declare function embeddedSubsetTag(postScriptName: string, glyphIds: readonly number[]): string;
@@ -176,7 +176,7 @@ function collectEmbeddedGlyphs(texts, face) {
176
176
  if (codePoint !== void 0) sequence.push(codePoint);
177
177
  }
178
178
  cursor += span;
179
- if (glyphId !== 0 && sequence.length > 0) {
179
+ if (glyphId !== 0) {
180
180
  if (used.get(glyphId) === void 0) used.set(glyphId, sequence);
181
181
  }
182
182
  }
@@ -1,2 +1,2 @@
1
- import { a as EmbeddedShow, c as encodeForShowEmbedded, i as EmbeddedKern, l as loadEmbeddedFace, n as EmbeddedFaceMetrics, o as NOTDEF_GLYPH_ID, r as EmbeddedFaceSubstitution, s as collectEmbeddedGlyphs, t as EmbeddedFace } from "./embedded-font-oc65VGlr.cjs";
1
+ import { a as EmbeddedShow, c as encodeForShowEmbedded, i as EmbeddedKern, l as loadEmbeddedFace, n as EmbeddedFaceMetrics, o as NOTDEF_GLYPH_ID, r as EmbeddedFaceSubstitution, s as collectEmbeddedGlyphs, t as EmbeddedFace } from "./embedded-font-DJUn9kMH.cjs";
2
2
  export { EmbeddedFace, EmbeddedFaceMetrics, EmbeddedFaceSubstitution, EmbeddedKern, EmbeddedShow, NOTDEF_GLYPH_ID, collectEmbeddedGlyphs, encodeForShowEmbedded, loadEmbeddedFace };
@@ -1,2 +1,2 @@
1
- import { a as EmbeddedShow, c as encodeForShowEmbedded, i as EmbeddedKern, l as loadEmbeddedFace, n as EmbeddedFaceMetrics, o as NOTDEF_GLYPH_ID, r as EmbeddedFaceSubstitution, s as collectEmbeddedGlyphs, t as EmbeddedFace } from "./embedded-font-B4p9a3O3.js";
1
+ import { a as EmbeddedShow, c as encodeForShowEmbedded, i as EmbeddedKern, l as loadEmbeddedFace, n as EmbeddedFaceMetrics, o as NOTDEF_GLYPH_ID, r as EmbeddedFaceSubstitution, s as collectEmbeddedGlyphs, t as EmbeddedFace } from "./embedded-font-BRhZfEN2.js";
2
2
  export { EmbeddedFace, EmbeddedFaceMetrics, EmbeddedFaceSubstitution, EmbeddedKern, EmbeddedShow, NOTDEF_GLYPH_ID, collectEmbeddedGlyphs, encodeForShowEmbedded, loadEmbeddedFace };
@@ -175,7 +175,7 @@ function collectEmbeddedGlyphs(texts, face) {
175
175
  if (codePoint !== void 0) sequence.push(codePoint);
176
176
  }
177
177
  cursor += span;
178
- if (glyphId !== 0 && sequence.length > 0) {
178
+ if (glyphId !== 0) {
179
179
  if (used.get(glyphId) === void 0) used.set(glyphId, sequence);
180
180
  }
181
181
  }
package/dist/filters.cjs CHANGED
@@ -10,6 +10,7 @@ function decodeStream(raw, dict, sink, resolve) {
10
10
  const filters = filterNames(dict);
11
11
  const parms = decodeParmsList(dict, filters.length);
12
12
  let bytes = raw;
13
+ let jbig2;
13
14
  for (let i = 0; i < filters.length; i++) {
14
15
  const filter = filters[i];
15
16
  const parm = parms[i];
@@ -27,7 +28,11 @@ function decodeStream(raw, dict, sink, resolve) {
27
28
  bytes,
28
29
  remainingFilter: "JBIG2Decode"
29
30
  };
30
- bytes = decoded;
31
+ jbig2 = {
32
+ encoded: bytes,
33
+ globals: decoded.globals
34
+ };
35
+ bytes = decoded.bytes;
31
36
  } else if (filter === "DCTDecode" || filter === "DCT") return {
32
37
  bytes,
33
38
  remainingFilter: "DCTDecode"
@@ -48,7 +53,10 @@ function decodeStream(raw, dict, sink, resolve) {
48
53
  };
49
54
  }
50
55
  }
51
- return { bytes };
56
+ return jbig2 === void 0 ? { bytes } : {
57
+ bytes,
58
+ jbig2
59
+ };
52
60
  }
53
61
  function applyPredictorIfPresent(data, parm, sink) {
54
62
  return require_predictors.applyPredictor(data, require_predictors.readPredictorParams(parm), sink);
@@ -94,7 +102,10 @@ function jbig2Decode(data, parm, dict, sink, resolve) {
94
102
  });
95
103
  }
96
104
  });
97
- return Uint8Array.from(image.bytes, (byte) => byte ^ 255);
105
+ return {
106
+ bytes: Uint8Array.from(image.bytes, (byte) => byte ^ 255),
107
+ globals
108
+ };
98
109
  } catch (error) {
99
110
  sink({
100
111
  code: "pdf/jbig2-undecodable",
@@ -4,6 +4,10 @@ import { n as PdfDict, r as PdfObject } from "./objects-B7HdMeWS.cjs";
4
4
  interface DecodedStream {
5
5
  readonly bytes: Uint8Array<ArrayBuffer>;
6
6
  readonly remainingFilter?: string;
7
+ readonly jbig2?: {
8
+ readonly encoded: Uint8Array<ArrayBuffer>;
9
+ readonly globals?: Uint8Array<ArrayBuffer>;
10
+ };
7
11
  }
8
12
  type PdfIndirectResolver = (obj: PdfObject | undefined) => PdfObject | undefined;
9
13
  declare function decodeStream(raw: Uint8Array<ArrayBuffer>, dict: PdfDict, sink: PdfDiagnosticSink, resolve?: PdfIndirectResolver): DecodedStream;
package/dist/filters.d.ts CHANGED
@@ -4,6 +4,10 @@ import { n as PdfDict, r as PdfObject } from "./objects-B7HdMeWS.js";
4
4
  interface DecodedStream {
5
5
  readonly bytes: Uint8Array<ArrayBuffer>;
6
6
  readonly remainingFilter?: string;
7
+ readonly jbig2?: {
8
+ readonly encoded: Uint8Array<ArrayBuffer>;
9
+ readonly globals?: Uint8Array<ArrayBuffer>;
10
+ };
7
11
  }
8
12
  type PdfIndirectResolver = (obj: PdfObject | undefined) => PdfObject | undefined;
9
13
  declare function decodeStream(raw: Uint8Array<ArrayBuffer>, dict: PdfDict, sink: PdfDiagnosticSink, resolve?: PdfIndirectResolver): DecodedStream;
package/dist/filters.js CHANGED
@@ -9,6 +9,7 @@ function decodeStream(raw, dict, sink, resolve) {
9
9
  const filters = filterNames(dict);
10
10
  const parms = decodeParmsList(dict, filters.length);
11
11
  let bytes = raw;
12
+ let jbig2;
12
13
  for (let i = 0; i < filters.length; i++) {
13
14
  const filter = filters[i];
14
15
  const parm = parms[i];
@@ -26,7 +27,11 @@ function decodeStream(raw, dict, sink, resolve) {
26
27
  bytes,
27
28
  remainingFilter: "JBIG2Decode"
28
29
  };
29
- bytes = decoded;
30
+ jbig2 = {
31
+ encoded: bytes,
32
+ globals: decoded.globals
33
+ };
34
+ bytes = decoded.bytes;
30
35
  } else if (filter === "DCTDecode" || filter === "DCT") return {
31
36
  bytes,
32
37
  remainingFilter: "DCTDecode"
@@ -47,7 +52,10 @@ function decodeStream(raw, dict, sink, resolve) {
47
52
  };
48
53
  }
49
54
  }
50
- return { bytes };
55
+ return jbig2 === void 0 ? { bytes } : {
56
+ bytes,
57
+ jbig2
58
+ };
51
59
  }
52
60
  function applyPredictorIfPresent(data, parm, sink) {
53
61
  return applyPredictor(data, readPredictorParams(parm), sink);
@@ -93,7 +101,10 @@ function jbig2Decode(data, parm, dict, sink, resolve) {
93
101
  });
94
102
  }
95
103
  });
96
- return Uint8Array.from(image.bytes, (byte) => byte ^ 255);
104
+ return {
105
+ bytes: Uint8Array.from(image.bytes, (byte) => byte ^ 255),
106
+ globals
107
+ };
97
108
  } catch (error) {
98
109
  sink({
99
110
  code: "pdf/jbig2-undecodable",
@@ -1,5 +1,5 @@
1
1
  import { r as StandardFontName } from "./afm-widths-CneRyu5l.cjs";
2
- import { t as EmbeddedFace } from "./embedded-font-oc65VGlr.cjs";
2
+ import { t as EmbeddedFace } from "./embedded-font-DJUn9kMH.cjs";
3
3
  import { FontRegistryOptions, LayoutFont } from "document-schema.js";
4
4
  //#region src/font-registry.d.ts
5
5
  type ResolvedFace = {
@@ -1,5 +1,5 @@
1
1
  import { r as StandardFontName } from "./afm-widths-CneRyu5l.js";
2
- import { t as EmbeddedFace } from "./embedded-font-B4p9a3O3.js";
2
+ import { t as EmbeddedFace } from "./embedded-font-BRhZfEN2.js";
3
3
  import { FontRegistryOptions, LayoutFont } from "document-schema.js";
4
4
  //#region src/font-registry.d.ts
5
5
  type ResolvedFace = {
@@ -0,0 +1,48 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_sfnt = require("./sfnt.cjs");
3
+ const require_ot_layout_common = require("./ot-layout-common.cjs");
4
+ //#region src/gdef-table.ts
5
+ const GLYPH_CLASS_UNCLASSIFIED = 0;
6
+ const GLYPH_CLASS_BASE = 1;
7
+ const GLYPH_CLASS_LIGATURE = 2;
8
+ const GLYPH_CLASS_MARK = 3;
9
+ const GLYPH_CLASS_COMPONENT = 4;
10
+ const GDEF_HEADER_SIZE_1_0 = 12;
11
+ const GDEF_HEADER_SIZE_1_2 = 14;
12
+ const MARK_GLYPH_SETS_HEADER_SIZE = 4;
13
+ function parseGdefTable(font) {
14
+ const bytes = require_sfnt.sfntTableBytes(font, "GDEF");
15
+ if (bytes === void 0 || !require_sfnt.hasBytes(bytes, 0, GDEF_HEADER_SIZE_1_0) || require_sfnt.u16(bytes, 0) !== 1) return;
16
+ const minorVersion = require_sfnt.u16(bytes, 2);
17
+ const glyphClassDefOffset = require_sfnt.u16(bytes, 4);
18
+ const glyphClassDef = glyphClassDefOffset === 0 ? void 0 : require_ot_layout_common.parseClassDef(bytes, glyphClassDefOffset);
19
+ const markAttachClassDefOffset = require_sfnt.u16(bytes, 10);
20
+ const markAttachClassDef = markAttachClassDefOffset === 0 ? void 0 : require_ot_layout_common.parseClassDef(bytes, markAttachClassDefOffset);
21
+ const markGlyphSets = [];
22
+ if (minorVersion >= 2 && require_sfnt.hasBytes(bytes, 0, GDEF_HEADER_SIZE_1_2)) {
23
+ const markGlyphSetsDefOffset = require_sfnt.u16(bytes, GDEF_HEADER_SIZE_1_0);
24
+ if (markGlyphSetsDefOffset !== 0 && require_sfnt.hasBytes(bytes, markGlyphSetsDefOffset, MARK_GLYPH_SETS_HEADER_SIZE) && require_sfnt.u16(bytes, markGlyphSetsDefOffset) === 1) {
25
+ const setCount = require_sfnt.u16(bytes, markGlyphSetsDefOffset + 2);
26
+ const coverageOffsetsOffset = markGlyphSetsDefOffset + MARK_GLYPH_SETS_HEADER_SIZE;
27
+ if (require_sfnt.hasBytes(bytes, coverageOffsetsOffset, setCount * 4)) for (let i = 0; i < setCount; i++) markGlyphSets.push(require_ot_layout_common.parseCoverage(bytes, markGlyphSetsDefOffset + require_sfnt.u32(bytes, coverageOffsetsOffset + i * 4)));
28
+ }
29
+ }
30
+ return {
31
+ glyphClass(glyphId) {
32
+ return glyphClassDef === void 0 ? 0 : glyphClassDef(glyphId);
33
+ },
34
+ markAttachClass(glyphId) {
35
+ return markAttachClassDef === void 0 ? 0 : markAttachClassDef(glyphId);
36
+ },
37
+ markFilteringSetCovers(setIndex, glyphId) {
38
+ return markGlyphSets[setIndex]?.coverageIndex(glyphId) !== void 0;
39
+ }
40
+ };
41
+ }
42
+ //#endregion
43
+ exports.GLYPH_CLASS_BASE = GLYPH_CLASS_BASE;
44
+ exports.GLYPH_CLASS_COMPONENT = GLYPH_CLASS_COMPONENT;
45
+ exports.GLYPH_CLASS_LIGATURE = GLYPH_CLASS_LIGATURE;
46
+ exports.GLYPH_CLASS_MARK = GLYPH_CLASS_MARK;
47
+ exports.GLYPH_CLASS_UNCLASSIFIED = GLYPH_CLASS_UNCLASSIFIED;
48
+ exports.parseGdefTable = parseGdefTable;
@@ -0,0 +1,15 @@
1
+ import { t as SfntFont } from "./sfnt-V08bb5ut.cjs";
2
+ //#region src/gdef-table.d.ts
3
+ declare const GLYPH_CLASS_UNCLASSIFIED = 0;
4
+ declare const GLYPH_CLASS_BASE = 1;
5
+ declare const GLYPH_CLASS_LIGATURE = 2;
6
+ declare const GLYPH_CLASS_MARK = 3;
7
+ declare const GLYPH_CLASS_COMPONENT = 4;
8
+ interface GdefTable {
9
+ glyphClass(glyphId: number): number;
10
+ markAttachClass(glyphId: number): number;
11
+ markFilteringSetCovers(setIndex: number, glyphId: number): boolean;
12
+ }
13
+ declare function parseGdefTable(font: SfntFont): GdefTable | undefined;
14
+ //#endregion
15
+ export { GLYPH_CLASS_BASE, GLYPH_CLASS_COMPONENT, GLYPH_CLASS_LIGATURE, GLYPH_CLASS_MARK, GLYPH_CLASS_UNCLASSIFIED, GdefTable, parseGdefTable };
@@ -0,0 +1,15 @@
1
+ import { t as SfntFont } from "./sfnt-V08bb5ut.js";
2
+ //#region src/gdef-table.d.ts
3
+ declare const GLYPH_CLASS_UNCLASSIFIED = 0;
4
+ declare const GLYPH_CLASS_BASE = 1;
5
+ declare const GLYPH_CLASS_LIGATURE = 2;
6
+ declare const GLYPH_CLASS_MARK = 3;
7
+ declare const GLYPH_CLASS_COMPONENT = 4;
8
+ interface GdefTable {
9
+ glyphClass(glyphId: number): number;
10
+ markAttachClass(glyphId: number): number;
11
+ markFilteringSetCovers(setIndex: number, glyphId: number): boolean;
12
+ }
13
+ declare function parseGdefTable(font: SfntFont): GdefTable | undefined;
14
+ //#endregion
15
+ export { GLYPH_CLASS_BASE, GLYPH_CLASS_COMPONENT, GLYPH_CLASS_LIGATURE, GLYPH_CLASS_MARK, GLYPH_CLASS_UNCLASSIFIED, GdefTable, parseGdefTable };
@@ -0,0 +1,42 @@
1
+ import { hasBytes, sfntTableBytes, u16, u32 } from "./sfnt.js";
2
+ import { parseClassDef, parseCoverage } from "./ot-layout-common.js";
3
+ //#region src/gdef-table.ts
4
+ const GLYPH_CLASS_UNCLASSIFIED = 0;
5
+ const GLYPH_CLASS_BASE = 1;
6
+ const GLYPH_CLASS_LIGATURE = 2;
7
+ const GLYPH_CLASS_MARK = 3;
8
+ const GLYPH_CLASS_COMPONENT = 4;
9
+ const GDEF_HEADER_SIZE_1_0 = 12;
10
+ const GDEF_HEADER_SIZE_1_2 = 14;
11
+ const MARK_GLYPH_SETS_HEADER_SIZE = 4;
12
+ function parseGdefTable(font) {
13
+ const bytes = sfntTableBytes(font, "GDEF");
14
+ if (bytes === void 0 || !hasBytes(bytes, 0, GDEF_HEADER_SIZE_1_0) || u16(bytes, 0) !== 1) return;
15
+ const minorVersion = u16(bytes, 2);
16
+ const glyphClassDefOffset = u16(bytes, 4);
17
+ const glyphClassDef = glyphClassDefOffset === 0 ? void 0 : parseClassDef(bytes, glyphClassDefOffset);
18
+ const markAttachClassDefOffset = u16(bytes, 10);
19
+ const markAttachClassDef = markAttachClassDefOffset === 0 ? void 0 : parseClassDef(bytes, markAttachClassDefOffset);
20
+ const markGlyphSets = [];
21
+ if (minorVersion >= 2 && hasBytes(bytes, 0, GDEF_HEADER_SIZE_1_2)) {
22
+ const markGlyphSetsDefOffset = u16(bytes, GDEF_HEADER_SIZE_1_0);
23
+ if (markGlyphSetsDefOffset !== 0 && hasBytes(bytes, markGlyphSetsDefOffset, MARK_GLYPH_SETS_HEADER_SIZE) && u16(bytes, markGlyphSetsDefOffset) === 1) {
24
+ const setCount = u16(bytes, markGlyphSetsDefOffset + 2);
25
+ const coverageOffsetsOffset = markGlyphSetsDefOffset + MARK_GLYPH_SETS_HEADER_SIZE;
26
+ if (hasBytes(bytes, coverageOffsetsOffset, setCount * 4)) for (let i = 0; i < setCount; i++) markGlyphSets.push(parseCoverage(bytes, markGlyphSetsDefOffset + u32(bytes, coverageOffsetsOffset + i * 4)));
27
+ }
28
+ }
29
+ return {
30
+ glyphClass(glyphId) {
31
+ return glyphClassDef === void 0 ? 0 : glyphClassDef(glyphId);
32
+ },
33
+ markAttachClass(glyphId) {
34
+ return markAttachClassDef === void 0 ? 0 : markAttachClassDef(glyphId);
35
+ },
36
+ markFilteringSetCovers(setIndex, glyphId) {
37
+ return markGlyphSets[setIndex]?.coverageIndex(glyphId) !== void 0;
38
+ }
39
+ };
40
+ }
41
+ //#endregion
42
+ export { GLYPH_CLASS_BASE, GLYPH_CLASS_COMPONENT, GLYPH_CLASS_LIGATURE, GLYPH_CLASS_MARK, GLYPH_CLASS_UNCLASSIFIED, parseGdefTable };
@@ -0,0 +1,16 @@
1
+ import { t as SfntFont } from "./sfnt-V08bb5ut.cjs";
2
+ //#region src/gsub-table.d.ts
3
+ declare const DEFAULT_GSUB_FEATURE_TAGS: readonly ["liga", "rlig", "calt", "clig"];
4
+ declare const GSUB_OPT_IN_FEATURE_TAGS: readonly ["smcp", "dlig"];
5
+ type GsubOptInFeatureTag = (typeof GSUB_OPT_IN_FEATURE_TAGS)[number];
6
+ interface GsubShaperOptions {
7
+ readonly optInFeatures?: readonly GsubOptInFeatureTag[];
8
+ }
9
+ interface GsubShaping {
10
+ readonly glyphIds: number[];
11
+ readonly spans: number[];
12
+ }
13
+ type GsubShaper = (glyphIds: readonly number[]) => GsubShaping;
14
+ declare function buildGsubShaper(font: SfntFont, options?: GsubShaperOptions): GsubShaper | undefined;
15
+ //#endregion
16
+ export { GsubShaperOptions as a, GsubShaper as i, GSUB_OPT_IN_FEATURE_TAGS as n, GsubShaping as o, GsubOptInFeatureTag as r, buildGsubShaper as s, DEFAULT_GSUB_FEATURE_TAGS as t };
@@ -0,0 +1,16 @@
1
+ import { t as SfntFont } from "./sfnt-V08bb5ut.js";
2
+ //#region src/gsub-table.d.ts
3
+ declare const DEFAULT_GSUB_FEATURE_TAGS: readonly ["liga", "rlig", "calt", "clig"];
4
+ declare const GSUB_OPT_IN_FEATURE_TAGS: readonly ["smcp", "dlig"];
5
+ type GsubOptInFeatureTag = (typeof GSUB_OPT_IN_FEATURE_TAGS)[number];
6
+ interface GsubShaperOptions {
7
+ readonly optInFeatures?: readonly GsubOptInFeatureTag[];
8
+ }
9
+ interface GsubShaping {
10
+ readonly glyphIds: number[];
11
+ readonly spans: number[];
12
+ }
13
+ type GsubShaper = (glyphIds: readonly number[]) => GsubShaping;
14
+ declare function buildGsubShaper(font: SfntFont, options?: GsubShaperOptions): GsubShaper | undefined;
15
+ //#endregion
16
+ export { GsubShaperOptions as a, GsubShaper as i, GSUB_OPT_IN_FEATURE_TAGS as n, GsubShaping as o, GsubOptInFeatureTag as r, buildGsubShaper as s, DEFAULT_GSUB_FEATURE_TAGS as t };