pdf-codec 4.2.0 → 4.4.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.
package/README.md CHANGED
@@ -234,7 +234,7 @@ 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, and stroke `style` becoming real dash/line-cap state), `write.ts` (the full object graph, cross-reference table, trailer, and embedded font groups).
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
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.
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`).
@@ -272,9 +272,9 @@ Dependency direction is strictly downward and checkable: `layout` imports only `
272
272
  - **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.
273
273
  - **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`.
274
274
  - **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).
275
- - **`GPOS` pair kerning is read and applied for an embedded face; no other OpenType layout feature is.** `GSUB` (ligatures, contextual alternates, small caps) is never applied — a face's `fi` ligature is drawn as two separate glyphs. 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`.
275
+ - **`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
276
  - **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.
277
- - **`font-substitutes.ts` maps both `Calibri` and `Calibri Light` onto the same ordinary-weight Carlito face** — Carlito ships only one weight per style axis, 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.
277
+ - **`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).
278
278
  - **`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.
279
279
 
280
280
  ## Text extraction and font encodings
@@ -315,7 +315,7 @@ A differential test pins the _set_ of template positions and offsets but not the
315
315
 
316
316
  **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.
317
317
 
318
- **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 — measured and drawn from one shared computation. What separates this from line-identical: `GSUB` ligatures/contextual alternates are never applied, and kerning is within each shown string rather than across whitespace boundaries a wrap decision measures separately.
318
+ **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
319
 
320
320
  **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.
321
321
 
@@ -313,16 +313,38 @@ function writeContentStream(items, context) {
313
313
  const writer = new require_bytes_writer.ByteWriter();
314
314
  const substitutions = [];
315
315
  const missingGlyphs = [];
316
- for (const item of items) if (item.kind === "text") writeText(writer, item, context, substitutions, missingGlyphs);
317
- else if (item.kind === "image") writeImage(writer, item, context);
318
- else if (item.kind === "rect") writeRect(writer, item);
319
- else if (item.kind === "line") writeLine(writer, item);
320
- else if (item.kind === "ellipse") writeEllipse(writer, item);
321
- else if (item.kind === "path") writePath(writer, item);
316
+ const markedStructure = [];
317
+ for (const item of items) {
318
+ const itemLayer = "layer" in item ? item.layer : void 0;
319
+ const itemStructure = "structure" in item ? item.structure : void 0;
320
+ const layerObjectNum = itemLayer !== void 0 ? context.layerObjectNumberOf?.(itemLayer) : void 0;
321
+ const marked = itemStructure !== void 0 || layerObjectNum !== void 0;
322
+ if (marked) {
323
+ writer.writeAscii("/P <<");
324
+ if (itemStructure !== void 0 && context.nextMcid !== void 0) {
325
+ const mcid = context.nextMcid();
326
+ markedStructure.push({
327
+ mcid,
328
+ structureId: itemStructure
329
+ });
330
+ writer.writeAscii(` /MCID ${mcid}`);
331
+ }
332
+ if (layerObjectNum !== void 0) writer.writeAscii(` /OC ${layerObjectNum} 0 R`);
333
+ writer.writeAscii(" >> BDC\n");
334
+ }
335
+ if (item.kind === "text") writeText(writer, item, context, substitutions, missingGlyphs);
336
+ else if (item.kind === "image") writeImage(writer, item, context);
337
+ else if (item.kind === "rect") writeRect(writer, item);
338
+ else if (item.kind === "line") writeLine(writer, item);
339
+ else if (item.kind === "ellipse") writeEllipse(writer, item);
340
+ else if (item.kind === "path") writePath(writer, item);
341
+ if (marked) writer.writeAscii("EMC\n");
342
+ }
322
343
  return {
323
344
  bytes: writer.toBytes(),
324
345
  substitutions,
325
- missingGlyphs
346
+ missingGlyphs,
347
+ markedStructure
326
348
  };
327
349
  }
328
350
  //#endregion
@@ -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-BU6Jgcof.cjs";
3
+ import { r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-oc65VGlr.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
@@ -20,12 +20,19 @@ interface ContentWriteContext {
20
20
  readonly measurer: TextMeasurer;
21
21
  resolveFont(font: LayoutFont): ResolvedFontResource;
22
22
  resolveImage(imageId: string): ResolvedImageResource;
23
+ readonly layerObjectNumberOf?: (name: string) => number | undefined;
24
+ readonly nextMcid?: () => number;
23
25
  }
24
26
  interface ContentStreamResult {
25
27
  readonly bytes: Uint8Array<ArrayBuffer>;
28
+ readonly markedStructure: readonly MarkedStructureItem[];
26
29
  readonly substitutions: readonly WinAnsiSubstitution[];
27
30
  readonly missingGlyphs: readonly EmbeddedFaceSubstitution[];
28
31
  }
29
32
  declare function writeContentStream(items: readonly LayoutItem[], context: ContentWriteContext): ContentStreamResult;
33
+ interface MarkedStructureItem {
34
+ readonly mcid: number;
35
+ readonly structureId: string;
36
+ }
30
37
  //#endregion
31
- export { ContentStreamResult, ContentWriteContext, ResolvedFontResource, ResolvedImageResource, writeContentStream };
38
+ export { ContentStreamResult, ContentWriteContext, MarkedStructureItem, ResolvedFontResource, ResolvedImageResource, writeContentStream };
@@ -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-fREdbxnr.js";
3
+ import { r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-B4p9a3O3.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
@@ -20,12 +20,19 @@ interface ContentWriteContext {
20
20
  readonly measurer: TextMeasurer;
21
21
  resolveFont(font: LayoutFont): ResolvedFontResource;
22
22
  resolveImage(imageId: string): ResolvedImageResource;
23
+ readonly layerObjectNumberOf?: (name: string) => number | undefined;
24
+ readonly nextMcid?: () => number;
23
25
  }
24
26
  interface ContentStreamResult {
25
27
  readonly bytes: Uint8Array<ArrayBuffer>;
28
+ readonly markedStructure: readonly MarkedStructureItem[];
26
29
  readonly substitutions: readonly WinAnsiSubstitution[];
27
30
  readonly missingGlyphs: readonly EmbeddedFaceSubstitution[];
28
31
  }
29
32
  declare function writeContentStream(items: readonly LayoutItem[], context: ContentWriteContext): ContentStreamResult;
33
+ interface MarkedStructureItem {
34
+ readonly mcid: number;
35
+ readonly structureId: string;
36
+ }
30
37
  //#endregion
31
- export { ContentStreamResult, ContentWriteContext, ResolvedFontResource, ResolvedImageResource, writeContentStream };
38
+ export { ContentStreamResult, ContentWriteContext, MarkedStructureItem, ResolvedFontResource, ResolvedImageResource, writeContentStream };
@@ -312,16 +312,38 @@ function writeContentStream(items, context) {
312
312
  const writer = new ByteWriter();
313
313
  const substitutions = [];
314
314
  const missingGlyphs = [];
315
- for (const item of items) if (item.kind === "text") writeText(writer, item, context, substitutions, missingGlyphs);
316
- else if (item.kind === "image") writeImage(writer, item, context);
317
- else if (item.kind === "rect") writeRect(writer, item);
318
- else if (item.kind === "line") writeLine(writer, item);
319
- else if (item.kind === "ellipse") writeEllipse(writer, item);
320
- else if (item.kind === "path") writePath(writer, item);
315
+ const markedStructure = [];
316
+ for (const item of items) {
317
+ const itemLayer = "layer" in item ? item.layer : void 0;
318
+ const itemStructure = "structure" in item ? item.structure : void 0;
319
+ const layerObjectNum = itemLayer !== void 0 ? context.layerObjectNumberOf?.(itemLayer) : void 0;
320
+ const marked = itemStructure !== void 0 || layerObjectNum !== void 0;
321
+ if (marked) {
322
+ writer.writeAscii("/P <<");
323
+ if (itemStructure !== void 0 && context.nextMcid !== void 0) {
324
+ const mcid = context.nextMcid();
325
+ markedStructure.push({
326
+ mcid,
327
+ structureId: itemStructure
328
+ });
329
+ writer.writeAscii(` /MCID ${mcid}`);
330
+ }
331
+ if (layerObjectNum !== void 0) writer.writeAscii(` /OC ${layerObjectNum} 0 R`);
332
+ writer.writeAscii(" >> BDC\n");
333
+ }
334
+ if (item.kind === "text") writeText(writer, item, context, substitutions, missingGlyphs);
335
+ else if (item.kind === "image") writeImage(writer, item, context);
336
+ else if (item.kind === "rect") writeRect(writer, item);
337
+ else if (item.kind === "line") writeLine(writer, item);
338
+ else if (item.kind === "ellipse") writeEllipse(writer, item);
339
+ else if (item.kind === "path") writePath(writer, item);
340
+ if (marked) writer.writeAscii("EMC\n");
341
+ }
321
342
  return {
322
343
  bytes: writer.toBytes(),
323
344
  substitutions,
324
- missingGlyphs
345
+ missingGlyphs,
346
+ markedStructure
325
347
  };
326
348
  }
327
349
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { t as SfntFont } from "./sfnt-V08bb5ut.js";
2
+ import { t as GsubShaper } from "./gsub-table-BRTQ8EUW.js";
2
3
  //#region src/embedded-font.d.ts
3
4
  declare const NOTDEF_GLYPH_ID = 0;
4
5
  interface EmbeddedFaceMetrics {
@@ -27,6 +28,7 @@ interface EmbeddedFace {
27
28
  glyphId(codePoint: number): number | undefined;
28
29
  glyphSpaceWidth(glyphId: number): number;
29
30
  kernGlyphSpace(leftGlyphId: number, rightGlyphId: number): number;
31
+ readonly gsubShaper: GsubShaper | undefined;
30
32
  }
31
33
  declare function loadEmbeddedFace(font: SfntFont): EmbeddedFace | undefined;
32
34
  interface EmbeddedFaceSubstitution {
@@ -43,6 +45,6 @@ interface EmbeddedShow {
43
45
  readonly kerns: readonly EmbeddedKern[];
44
46
  }
45
47
  declare function encodeForShowEmbedded(text: string, face: EmbeddedFace): EmbeddedShow;
46
- declare function collectEmbeddedGlyphs(texts: Iterable<string>, face: EmbeddedFace): ReadonlyMap<number, number>;
48
+ declare function collectEmbeddedGlyphs(texts: Iterable<string>, face: EmbeddedFace): ReadonlyMap<number, readonly number[]>;
47
49
  //#endregion
48
50
  export { EmbeddedShow as a, encodeForShowEmbedded as c, EmbeddedKern as i, loadEmbeddedFace as l, EmbeddedFaceMetrics as n, NOTDEF_GLYPH_ID as o, EmbeddedFaceSubstitution as r, collectEmbeddedGlyphs as s, EmbeddedFace as t };
@@ -1,4 +1,5 @@
1
1
  import { t as SfntFont } from "./sfnt-V08bb5ut.cjs";
2
+ import { t as GsubShaper } from "./gsub-table-URczLd85.cjs";
2
3
  //#region src/embedded-font.d.ts
3
4
  declare const NOTDEF_GLYPH_ID = 0;
4
5
  interface EmbeddedFaceMetrics {
@@ -27,6 +28,7 @@ interface EmbeddedFace {
27
28
  glyphId(codePoint: number): number | undefined;
28
29
  glyphSpaceWidth(glyphId: number): number;
29
30
  kernGlyphSpace(leftGlyphId: number, rightGlyphId: number): number;
31
+ readonly gsubShaper: GsubShaper | undefined;
30
32
  }
31
33
  declare function loadEmbeddedFace(font: SfntFont): EmbeddedFace | undefined;
32
34
  interface EmbeddedFaceSubstitution {
@@ -43,6 +45,6 @@ interface EmbeddedShow {
43
45
  readonly kerns: readonly EmbeddedKern[];
44
46
  }
45
47
  declare function encodeForShowEmbedded(text: string, face: EmbeddedFace): EmbeddedShow;
46
- declare function collectEmbeddedGlyphs(texts: Iterable<string>, face: EmbeddedFace): ReadonlyMap<number, number>;
48
+ declare function collectEmbeddedGlyphs(texts: Iterable<string>, face: EmbeddedFace): ReadonlyMap<number, readonly number[]>;
47
49
  //#endregion
48
50
  export { EmbeddedShow as a, encodeForShowEmbedded as c, EmbeddedKern as i, loadEmbeddedFace as l, EmbeddedFaceMetrics as n, NOTDEF_GLYPH_ID as o, EmbeddedFaceSubstitution as r, collectEmbeddedGlyphs as s, EmbeddedFace as t };
@@ -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-BU6Jgcof.cjs";
2
+ import { t as EmbeddedFace } from "./embedded-font-oc65VGlr.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;
@@ -17,6 +17,6 @@ interface EmbeddedFontObjects {
17
17
  readonly fontFile: PdfObject;
18
18
  readonly toUnicode: PdfObject;
19
19
  }
20
- declare function buildEmbeddedFontObjects(face: EmbeddedFace, subset: SfntSubsetResult, usedGlyphs: ReadonlyMap<number, number>, refs: EmbeddedFontObjectRefs, compress: boolean): EmbeddedFontObjects;
20
+ declare function buildEmbeddedFontObjects(face: EmbeddedFace, subset: SfntSubsetResult, usedGlyphs: ReadonlyMap<number, readonly number[]>, refs: EmbeddedFontObjectRefs, compress: boolean): EmbeddedFontObjects;
21
21
  //#endregion
22
22
  export { EmbeddedFontObjectRefs, EmbeddedFontObjects, buildEmbeddedFontObjects, embeddedSubsetTag };
@@ -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-fREdbxnr.js";
2
+ import { t as EmbeddedFace } from "./embedded-font-B4p9a3O3.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;
@@ -17,6 +17,6 @@ interface EmbeddedFontObjects {
17
17
  readonly fontFile: PdfObject;
18
18
  readonly toUnicode: PdfObject;
19
19
  }
20
- declare function buildEmbeddedFontObjects(face: EmbeddedFace, subset: SfntSubsetResult, usedGlyphs: ReadonlyMap<number, number>, refs: EmbeddedFontObjectRefs, compress: boolean): EmbeddedFontObjects;
20
+ declare function buildEmbeddedFontObjects(face: EmbeddedFace, subset: SfntSubsetResult, usedGlyphs: ReadonlyMap<number, readonly number[]>, refs: EmbeddedFontObjectRefs, compress: boolean): EmbeddedFontObjects;
21
21
  //#endregion
22
22
  export { EmbeddedFontObjectRefs, EmbeddedFontObjects, buildEmbeddedFontObjects, embeddedSubsetTag };
@@ -4,6 +4,7 @@ const require_cmap_table = require("./cmap-table.cjs");
4
4
  const require_font_tables = require("./font-tables.cjs");
5
5
  const require_glyf = require("./glyf.cjs");
6
6
  const require_gpos_table = require("./gpos-table.cjs");
7
+ const require_gsub_table = require("./gsub-table.cjs");
7
8
  const require_hmtx_table = require("./hmtx-table.cjs");
8
9
  //#region src/embedded-font.ts
9
10
  const GLYPH_SPACE_UNITS_PER_EM = 1e3;
@@ -57,10 +58,12 @@ function readEmbeddedFace(font) {
57
58
  const scale = (designUnits) => scaleToGlyphSpace(designUnits, unitsPerEm);
58
59
  const capHeight = resolveCapHeight(font, cmap, os2?.sCapHeight, hhea.ascent);
59
60
  const kern = require_gpos_table.buildGposKernLookup(font);
61
+ const gsubShaper = require_gsub_table.buildGsubShaper(font);
60
62
  return {
61
63
  font,
62
64
  postScriptName: name.postScriptName,
63
65
  numGlyphs: maxp.numGlyphs,
66
+ gsubShaper,
64
67
  metrics: {
65
68
  unitsPerEm,
66
69
  ascentGlyphSpace: scale(hhea.ascent),
@@ -109,30 +112,31 @@ function parseHhea(font) {
109
112
  }
110
113
  const CID_BYTE_LENGTH = 2;
111
114
  function encodeForShowEmbedded(text, face) {
112
- const glyphIds = [];
115
+ const resolvedGlyphIds = [];
113
116
  const substitutions = [];
114
- const kerns = [];
115
- let width1000 = 0;
116
117
  for (const character of text) {
117
118
  const glyphId = face.glyphId(character.codePointAt(0));
118
119
  if (glyphId === void 0) substitutions.push({ from: character });
119
- const shown = glyphId ?? 0;
120
- const previous = glyphIds[glyphIds.length - 1];
121
- if (previous !== void 0) {
122
- const adjustment1000 = face.kernGlyphSpace(previous, shown);
120
+ resolvedGlyphIds.push(glyphId ?? 0);
121
+ }
122
+ const shownGlyphIds = face.gsubShaper === void 0 ? resolvedGlyphIds : face.gsubShaper(resolvedGlyphIds).glyphIds;
123
+ const kerns = [];
124
+ let width1000 = 0;
125
+ for (const [index, shown] of shownGlyphIds.entries()) {
126
+ if (index > 0) {
127
+ const adjustment1000 = face.kernGlyphSpace(shownGlyphIds[index - 1], shown);
123
128
  if (adjustment1000 !== 0) {
124
129
  kerns.push({
125
- codeOffset: glyphIds.length * CID_BYTE_LENGTH,
130
+ codeOffset: index * CID_BYTE_LENGTH,
126
131
  adjustment1000
127
132
  });
128
133
  width1000 += adjustment1000;
129
134
  }
130
135
  }
131
- glyphIds.push(shown);
132
136
  width1000 += face.glyphSpaceWidth(shown);
133
137
  }
134
- const codes = new Uint8Array(glyphIds.length * CID_BYTE_LENGTH);
135
- glyphIds.forEach((glyphId, index) => {
138
+ const codes = new Uint8Array(shownGlyphIds.length * CID_BYTE_LENGTH);
139
+ shownGlyphIds.forEach((glyphId, index) => {
136
140
  codes[index * CID_BYTE_LENGTH] = glyphId >> 8 & 255;
137
141
  codes[index * CID_BYTE_LENGTH + 1] = glyphId & 255;
138
142
  });
@@ -145,10 +149,37 @@ function encodeForShowEmbedded(text, face) {
145
149
  }
146
150
  function collectEmbeddedGlyphs(texts, face) {
147
151
  const used = /* @__PURE__ */ new Map();
148
- for (const text of texts) for (const character of text) {
149
- const codePoint = character.codePointAt(0);
150
- const glyphId = face.glyphId(codePoint);
151
- if (glyphId !== void 0 && !used.has(glyphId)) used.set(glyphId, codePoint);
152
+ for (const text of texts) {
153
+ const resolvedGlyphIds = [];
154
+ const codePoints = [];
155
+ for (const character of text) {
156
+ const codePoint = character.codePointAt(0);
157
+ const glyphId = face.glyphId(codePoint);
158
+ if (glyphId === void 0) {
159
+ resolvedGlyphIds.push(0);
160
+ codePoints.push(void 0);
161
+ } else {
162
+ resolvedGlyphIds.push(glyphId);
163
+ codePoints.push(codePoint);
164
+ }
165
+ }
166
+ const shaping = face.gsubShaper === void 0 ? {
167
+ glyphIds: resolvedGlyphIds,
168
+ spans: resolvedGlyphIds.map(() => 1)
169
+ } : face.gsubShaper(resolvedGlyphIds);
170
+ let cursor = 0;
171
+ for (const [index, glyphId] of shaping.glyphIds.entries()) {
172
+ const span = shaping.spans[index];
173
+ const sequence = [];
174
+ for (let s = 0; s < span; s++) {
175
+ const codePoint = codePoints[cursor + s];
176
+ if (codePoint !== void 0) sequence.push(codePoint);
177
+ }
178
+ cursor += span;
179
+ if (glyphId !== 0 && sequence.length > 0) {
180
+ if (used.get(glyphId) === void 0) used.set(glyphId, sequence);
181
+ }
182
+ }
152
183
  }
153
184
  return used;
154
185
  }
@@ -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-BU6Jgcof.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-oc65VGlr.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-fREdbxnr.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-B4p9a3O3.js";
2
2
  export { EmbeddedFace, EmbeddedFaceMetrics, EmbeddedFaceSubstitution, EmbeddedKern, EmbeddedShow, NOTDEF_GLYPH_ID, collectEmbeddedGlyphs, encodeForShowEmbedded, loadEmbeddedFace };
@@ -3,6 +3,7 @@ import { buildCmapLookup } from "./cmap-table.js";
3
3
  import { parseHead, parseMaxp, parseName, parseOs2, parsePost } from "./font-tables.js";
4
4
  import { parseGlyf } from "./glyf.js";
5
5
  import { buildGposKernLookup } from "./gpos-table.js";
6
+ import { buildGsubShaper } from "./gsub-table.js";
6
7
  import { parseHmtx } from "./hmtx-table.js";
7
8
  //#region src/embedded-font.ts
8
9
  const GLYPH_SPACE_UNITS_PER_EM = 1e3;
@@ -56,10 +57,12 @@ function readEmbeddedFace(font) {
56
57
  const scale = (designUnits) => scaleToGlyphSpace(designUnits, unitsPerEm);
57
58
  const capHeight = resolveCapHeight(font, cmap, os2?.sCapHeight, hhea.ascent);
58
59
  const kern = buildGposKernLookup(font);
60
+ const gsubShaper = buildGsubShaper(font);
59
61
  return {
60
62
  font,
61
63
  postScriptName: name.postScriptName,
62
64
  numGlyphs: maxp.numGlyphs,
65
+ gsubShaper,
63
66
  metrics: {
64
67
  unitsPerEm,
65
68
  ascentGlyphSpace: scale(hhea.ascent),
@@ -108,30 +111,31 @@ function parseHhea(font) {
108
111
  }
109
112
  const CID_BYTE_LENGTH = 2;
110
113
  function encodeForShowEmbedded(text, face) {
111
- const glyphIds = [];
114
+ const resolvedGlyphIds = [];
112
115
  const substitutions = [];
113
- const kerns = [];
114
- let width1000 = 0;
115
116
  for (const character of text) {
116
117
  const glyphId = face.glyphId(character.codePointAt(0));
117
118
  if (glyphId === void 0) substitutions.push({ from: character });
118
- const shown = glyphId ?? 0;
119
- const previous = glyphIds[glyphIds.length - 1];
120
- if (previous !== void 0) {
121
- const adjustment1000 = face.kernGlyphSpace(previous, shown);
119
+ resolvedGlyphIds.push(glyphId ?? 0);
120
+ }
121
+ const shownGlyphIds = face.gsubShaper === void 0 ? resolvedGlyphIds : face.gsubShaper(resolvedGlyphIds).glyphIds;
122
+ const kerns = [];
123
+ let width1000 = 0;
124
+ for (const [index, shown] of shownGlyphIds.entries()) {
125
+ if (index > 0) {
126
+ const adjustment1000 = face.kernGlyphSpace(shownGlyphIds[index - 1], shown);
122
127
  if (adjustment1000 !== 0) {
123
128
  kerns.push({
124
- codeOffset: glyphIds.length * CID_BYTE_LENGTH,
129
+ codeOffset: index * CID_BYTE_LENGTH,
125
130
  adjustment1000
126
131
  });
127
132
  width1000 += adjustment1000;
128
133
  }
129
134
  }
130
- glyphIds.push(shown);
131
135
  width1000 += face.glyphSpaceWidth(shown);
132
136
  }
133
- const codes = new Uint8Array(glyphIds.length * CID_BYTE_LENGTH);
134
- glyphIds.forEach((glyphId, index) => {
137
+ const codes = new Uint8Array(shownGlyphIds.length * CID_BYTE_LENGTH);
138
+ shownGlyphIds.forEach((glyphId, index) => {
135
139
  codes[index * CID_BYTE_LENGTH] = glyphId >> 8 & 255;
136
140
  codes[index * CID_BYTE_LENGTH + 1] = glyphId & 255;
137
141
  });
@@ -144,10 +148,37 @@ function encodeForShowEmbedded(text, face) {
144
148
  }
145
149
  function collectEmbeddedGlyphs(texts, face) {
146
150
  const used = /* @__PURE__ */ new Map();
147
- for (const text of texts) for (const character of text) {
148
- const codePoint = character.codePointAt(0);
149
- const glyphId = face.glyphId(codePoint);
150
- if (glyphId !== void 0 && !used.has(glyphId)) used.set(glyphId, codePoint);
151
+ for (const text of texts) {
152
+ const resolvedGlyphIds = [];
153
+ const codePoints = [];
154
+ for (const character of text) {
155
+ const codePoint = character.codePointAt(0);
156
+ const glyphId = face.glyphId(codePoint);
157
+ if (glyphId === void 0) {
158
+ resolvedGlyphIds.push(0);
159
+ codePoints.push(void 0);
160
+ } else {
161
+ resolvedGlyphIds.push(glyphId);
162
+ codePoints.push(codePoint);
163
+ }
164
+ }
165
+ const shaping = face.gsubShaper === void 0 ? {
166
+ glyphIds: resolvedGlyphIds,
167
+ spans: resolvedGlyphIds.map(() => 1)
168
+ } : face.gsubShaper(resolvedGlyphIds);
169
+ let cursor = 0;
170
+ for (const [index, glyphId] of shaping.glyphIds.entries()) {
171
+ const span = shaping.spans[index];
172
+ const sequence = [];
173
+ for (let s = 0; s < span; s++) {
174
+ const codePoint = codePoints[cursor + s];
175
+ if (codePoint !== void 0) sequence.push(codePoint);
176
+ }
177
+ cursor += span;
178
+ if (glyphId !== 0 && sequence.length > 0) {
179
+ if (used.get(glyphId) === void 0) used.set(glyphId, sequence);
180
+ }
181
+ }
151
182
  }
152
183
  return used;
153
184
  }
@@ -1,5 +1,5 @@
1
1
  import { r as StandardFontName } from "./afm-widths-CneRyu5l.cjs";
2
- import { t as EmbeddedFace } from "./embedded-font-BU6Jgcof.cjs";
2
+ import { t as EmbeddedFace } from "./embedded-font-oc65VGlr.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-fREdbxnr.js";
2
+ import { t as EmbeddedFace } from "./embedded-font-B4p9a3O3.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,10 @@
1
+ import { t as SfntFont } from "./sfnt-V08bb5ut.js";
2
+ //#region src/gsub-table.d.ts
3
+ interface GsubShaping {
4
+ readonly glyphIds: number[];
5
+ readonly spans: number[];
6
+ }
7
+ type GsubShaper = (glyphIds: readonly number[]) => GsubShaping;
8
+ declare function buildGsubShaper(font: SfntFont): GsubShaper | undefined;
9
+ //#endregion
10
+ export { GsubShaping as n, buildGsubShaper as r, GsubShaper as t };
@@ -0,0 +1,10 @@
1
+ import { t as SfntFont } from "./sfnt-V08bb5ut.cjs";
2
+ //#region src/gsub-table.d.ts
3
+ interface GsubShaping {
4
+ readonly glyphIds: number[];
5
+ readonly spans: number[];
6
+ }
7
+ type GsubShaper = (glyphIds: readonly number[]) => GsubShaping;
8
+ declare function buildGsubShaper(font: SfntFont): GsubShaper | undefined;
9
+ //#endregion
10
+ export { GsubShaping as n, buildGsubShaper as r, GsubShaper as t };