pdf-codec 1.11.3 → 1.11.5
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 +10 -1
- package/dist/codec.d.cts +2 -2
- package/dist/codec.d.ts +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -139,6 +139,15 @@ const pdfBytes = writePdf(doc, { fonts, onMissingGlyph: (m) => console.warn('no
|
|
|
139
139
|
|
|
140
140
|
Every text run whose family resolves to a real face is subsetted to the glyphs the document actually uses and embedded as its own `/Type0` + `/CIDFontType2` + `/FontFile2` group; every run that resolves to a standard-14 face is written exactly as it always was. **Omit `fonts` and nothing changes at all** — no font program is embedded, every family resolves through `resolveStandardFont`, and the output is byte-identical to a build with no embedded-font support (asserted against golden digests in `src/write-embedded-font.test.ts`).
|
|
141
141
|
|
|
142
|
+
Inspecting a standalone font file before handing it to a `FontRegistry` as a `ProvidedFont`: a caller holding raw `.ttf`/`.otf` bytes (a user-supplied house font, say) rarely already knows the file's own family/bold/italic triple, and guessing one from a filename is unreliable. `readFontFace` reads it straight off the font's own `name`/`OS/2`/`head` tables — exactly the family+style pair `ProvidedFont` needs to resolve correctly, and a structurally different thing from `EmbeddedFace.postScriptName` (a naming convention like `"ArialMT"`, not a structured triple):
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { createFontRegistry, readFontFace } from 'pdf-codec';
|
|
146
|
+
|
|
147
|
+
const { family, bold, italic } = readFontFace(brandSansTtfBytes, 'BrandSans-Bold.ttf'); // throws FontFaceParseError, naming the source, for a .ttc/.woff file or one with no family name
|
|
148
|
+
const fonts = createFontRegistry({ fonts: [{ family, bold, italic, bytes: brandSansTtfBytes }] });
|
|
149
|
+
```
|
|
150
|
+
|
|
142
151
|
`createFontMeasurer`'s second argument carries `verticalMetrics`, a `VerticalMetricPolicy` of `'hhea'` (the default) / `'os2Typo'` / `'os2Win'`, deciding which of the three competing ascent/descent/line-gap sets an sfnt declares should drive line height for an embedded face. It is an explicit, overridable option rather than a baked-in rule because no specification settles the question — see `src/measure.ts` for what each policy reads and why `'hhea'` is the default.
|
|
143
152
|
|
|
144
153
|
Reading a JPEG 2000 image directly, either as pixels or as metadata alone:
|
|
@@ -175,7 +184,7 @@ The package is layered from generic primitives outward to the codec itself:
|
|
|
175
184
|
- **`src/util/`** — two small, independently-duplicated copies of logic that lives elsewhere in the family for a reason narrow enough not to warrant a shared dependency: `base64.ts` (isomorphic base64 ⇄ `Uint8Array`, a verbatim copy of `odf.js`'s own `src/util/base64.ts`, replacing a dependency this codec used to have on `ooxml.js` purely for this one helper pair) and `abort.ts` (`throwIfAborted`, a four-line signal-check helper called at every page loop boundary in `write.ts`/`read.ts` — there is no `await` point in this package's synchronous reader/writer pipeline for cancellation to hook into implicitly, so every long-running loop checks explicitly instead; a duplicate of `documents.js`'s own `src/ports/abort.ts`, which stays there since other, non-PDF consumers still depend on it in that repository).
|
|
176
185
|
- **`src/crypto/`** — MD5, SHA-256/384/512, RC4, and AES-CBC, hand-written with zero local imports, exactly like `src/bytes/`. Not a preference: ISO 32000-1's own key-derivation algorithms name MD5 and RC4 directly, neither of which any platform crypto API this package could portably reach still offers, and `crypto.subtle` is asynchronous where this codec's read path is synchronous end to end. Reaching for `node:crypto` would put a Node builtin inside a `src/` tree that deliberately has none and is built with `platform: 'neutral'`, breaking the browser bundle its downstream consumer depends on. Each module cites the specification it implements (RFC 1321, FIPS 180-4, FIPS 197) and is tested against that specification's own published conformance vectors. MD5 and RC4 are cryptographically broken and are here solely to *read* files that already exist and whose format mandates them.
|
|
177
186
|
- **The codec itself, importing only `math-types`/`formula`/`bytes`/`image`/`crypto` (no OOXML or ODF knowledge at all):**
|
|
178
|
-
- **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` (the source-document → caller-supplied → vendored-substitute → standard-14 resolution port sitting in front of `resolveStandardFont`, plus `resolveFaceWithRegistry`, the one step both the measurer and the writer resolve through so they can never disagree about which face a `LayoutFont` means), `measure.ts`/`text-layout.ts` (greedy line-wrapping, measured against either a standard-14 face's AFM widths plus a per-family correction or a resolved face's own real `hmtx` advances — never both, see [Fidelity](#fidelity)), `matrix.ts`, `content-write.ts` (`LayoutItem[]` → content-stream operators, with text branching on whether its font resolved to a standard-14 face shown as a WinAnsi byte string with `Tj` or an embedded one shown as Identity-H 2-byte CIDs, split at that face's own pair-kerning adjustments into a `TJ` array where it has any to apply and shown as a single unsplit `Tj` string where it does not, and with a stroked line's or path's own `style` becoming either a real dash-array/line-cap state pair around the paint operator or, for `double`, two perpendicular-offset copies of the geometry — see [Gotchas](#gotchas-and-quirks)), `write.ts` (the full object graph, classic cross-reference table, trailer, and — when `WritePdfOptions.formulas` is non-empty — one embedded math composite font group, plus one embedded text font group per face `WritePdfOptions.fonts` resolved, each allocated once in a fixed sorted order and shared across pages).
|
|
187
|
+
- **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` (the source-document → caller-supplied → vendored-substitute → standard-14 resolution port sitting in front of `resolveStandardFont`, plus `resolveFaceWithRegistry`, the one step both the measurer and the writer resolve through so they can never disagree about which face a `LayoutFont` means), `font-face.ts` (`readFontFace`, a thin public wrapper over `sfnt.ts`'s `parseSfnt` and `font-tables.ts`'s `parseName`/`parseOs2`/`parseHead` reading a standalone font *file*'s own family/bold/italic triple straight off its `name`/`OS/2`/`head` tables — what a caller assembling `ProvidedFont` candidates for `FontRegistry` needs, and a different job from `embedded-font.ts`'s already-public `EmbeddedFace.postScriptName`, which is a naming convention rather than a structured family+style pair), `measure.ts`/`text-layout.ts` (greedy line-wrapping, measured against either a standard-14 face's AFM widths plus a per-family correction or a resolved face's own real `hmtx` advances — never both, see [Fidelity](#fidelity)), `matrix.ts`, `content-write.ts` (`LayoutItem[]` → content-stream operators, with text branching on whether its font resolved to a standard-14 face shown as a WinAnsi byte string with `Tj` or an embedded one shown as Identity-H 2-byte CIDs, split at that face's own pair-kerning adjustments into a `TJ` array where it has any to apply and shown as a single unsplit `Tj` string where it does not, and with a stroked line's or path's own `style` becoming either a real dash-array/line-cap state pair around the paint operator or, for `double`, two perpendicular-offset copies of the geometry — see [Gotchas](#gotchas-and-quirks)), `write.ts` (the full object graph, classic cross-reference table, trailer, and — when `WritePdfOptions.formulas` is non-empty — one embedded math composite font group, plus one embedded text font group per face `WritePdfOptions.fonts` resolved, each allocated once in a fixed sorted order and shared across pages).
|
|
179
188
|
- **sfnt font tables**: `sfnt.ts` (a bounds-checked sfnt table-directory reader, big-endian primitive readers, and the `hasBytes` range check every table parser below pre-checks with), `cmap-table.ts` (Unicode → glyph ID, formats 4/12/6), `hmtx-table.ts` (per-glyph advance widths), `font-tables.ts` (`head`/`maxp`/`OS/2`/`post`/`name` — design grid, glyph count, vertical metrics and style bits, italic angle and underline geometry, PostScript and family names), `glyf.ts` (the `loca` offset index, per-glyph headers, a composite glyph's own component records — a composite refers to its base letter and combining marks by glyph ID, and those references nest, so subsetting one safely means taking the transitive closure over this walk — and `glyphInkBounds`, a glyph's own tight ink box, read straight out of a simple glyph's header where the format already states it and unioned from a composite's own transformed, placed components where it does not), and `math-table.ts` (the OpenType `MATH` table's constants, glyph-info, and variants subtables). `ot-layout-common.ts` holds the two Common Table Formats every OpenType Layout table indexes glyphs through — Coverage and ClassDef — stored as sorted glyph ranges and searched by bisection rather than expanded into a glyph-keyed map, since six bytes of a format 2 record can legitimately declare a 65536-glyph range and expanding every range of every subtable turns a small untrusted font into a large allocation. `gpos-table.ts` reads a face's own `GPOS` table for exactly one thing: how much the font wants the advance of glyph A adjusted when glyph B follows it. It resolves the `kern` feature through the ScriptList (rather than sweeping the FeatureList for every feature tagged `kern`, which is what would apply a font's Cyrillic or Greek kerning lookups to Latin text), handles both PairPos subtable formats and the LookupType 9 Extension indirection — all three are real code paths, since Carlito reaches its kerning only through Extension-wrapped format 2 subtables while Caladea uses LookupType 2 directly and mixes formats inside one lookup — and reads only the first glyph's XAdvance, the one field a horizontal left-to-right run's next glyph position can depend on. Mark attachment, cursive joining, and contextual positioning have no consumer in a codec that positions glyphs itself, so they are not parsed. Every one of these parsers degrades to `undefined` on a missing or truncated table rather than throwing: the vendored fonts are trusted, but a font extracted from an arbitrary source document is not.
|
|
180
189
|
- **sfnt subsetting**: `sfnt-subset.ts` (a TrueType-outline glyph subsetter: Unicode code points → glyph IDs through `cmap-table.ts`, the transitive closure over `glyf.ts`'s composite-component walk, then a rebuilt sfnt container carrying only those glyphs' outlines). **Glyph IDs are preserved, never renumbered** — an unused ID below the highest used one survives as an empty `loca` entry rather than being squeezed out — which keeps every composite's own component references correct inside bytes copied verbatim, keeps a caller's already-resolved glyph IDs valid against the subset, and makes CID == GID trivially true for the embedded program (a `/CIDFontType2` then needs only `/CIDToGIDMap /Identity`). The output rebuilds `head`/`hhea`/`maxp`/`loca`/`glyf`/`hmtx` (with `indexToLocFormat` forced long, one always-legal code path), copies the hinting programs (`cvt `/`fpgm`/`prep`) verbatim, stubs `post` as a version 3.0 "no glyph names" header, and omits `cmap`/`name`/`OS/2`/`GSUB`/`GPOS`/`kern` — none of which an embedded `CIDFontType2` program is read through (ISO 32000-1 9.9). Dropping `GPOS` costs the document no kerning even though its pairs are now genuinely applied: the adjustments are resolved at write time and written into the page's own `TJ` array, so a consumer reads them off the content stream rather than out of the font program. The honest cost of preserving IDs: `loca` and `hmtx` stay proportional to the highest used glyph ID rather than to the number of glyphs kept, so for a document touching one glyph near the end of a large font's glyph order, most of the (already much smaller) output is those two index tables rather than outline data. Applies to `glyf`-flavoured fonts only; a CFF-flavoured one returns `undefined`, the same scope boundary [Fidelity](#fidelity) states for the embedded math font.
|
|
181
190
|
- **Embedded math font**: `math-font.ts` (parses and caches the vendored STIX Two Math font once per process, exposing a size-specific `MathFontMetrics` implementation and a points-in/points-out stretchy-glyph entry point), `math-stretch.ts` (the OpenType MATH two-stage stretching model over that parsed `MathVariants` data: pick the smallest pre-built variant that reaches the target, else assemble from repeated parts with every seam overlapped inside both sides' own declared connector lengths -- unit-agnostic, so it works in design units or points alike), `math-font-write.ts` (builds the `/Type0`/`/CIDFontType0`/`/FontDescriptor`/`/FontFile3`/ToUnicode object group), `math-content-write.ts` (a `PositionedFormula[]` → PDF content-stream bytes, Identity-H 2-byte CIDs for text-showing, `re`/`m`/`l` operators for rules and the radical hook, and one glyph-ID-addressed text object per placement of a stretched construction, wrapped in an `/ActualText` marked-content span). See [Fidelity](#fidelity) for the CFF-full-embed (not glyph-subsetted) simplification this makes.
|
package/dist/codec.d.cts
CHANGED
|
@@ -24,12 +24,12 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
|
|
|
24
24
|
font: z.ZodObject<{
|
|
25
25
|
family: z.ZodString;
|
|
26
26
|
weight: z.ZodEnum<{
|
|
27
|
-
bold: "bold";
|
|
28
27
|
normal: "normal";
|
|
28
|
+
bold: "bold";
|
|
29
29
|
}>;
|
|
30
30
|
style: z.ZodEnum<{
|
|
31
|
-
italic: "italic";
|
|
32
31
|
normal: "normal";
|
|
32
|
+
italic: "italic";
|
|
33
33
|
}>;
|
|
34
34
|
}, z.core.$strip>;
|
|
35
35
|
sizePt: z.ZodNumber;
|
package/dist/codec.d.ts
CHANGED
|
@@ -24,12 +24,12 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
|
|
|
24
24
|
font: z.ZodObject<{
|
|
25
25
|
family: z.ZodString;
|
|
26
26
|
weight: z.ZodEnum<{
|
|
27
|
-
bold: "bold";
|
|
28
27
|
normal: "normal";
|
|
28
|
+
bold: "bold";
|
|
29
29
|
}>;
|
|
30
30
|
style: z.ZodEnum<{
|
|
31
|
-
italic: "italic";
|
|
32
31
|
normal: "normal";
|
|
32
|
+
italic: "italic";
|
|
33
33
|
}>;
|
|
34
34
|
}, z.core.$strip>;
|
|
35
35
|
sizePt: z.ZodNumber;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-codec",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.5",
|
|
4
4
|
"description": "Hand-written, dependency-minimal PDF codec: parses arbitrary real-world PDFs and generates new ones, built on document-schema.js's LayoutDocument pivot and Zod 4 codecs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"license": "MIT",
|
|
71
71
|
"packageManager": "pnpm@11.6.0",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"document-schema.js": "^2.3.
|
|
73
|
+
"document-schema.js": "^2.3.2",
|
|
74
74
|
"fflate": "^0.8.3",
|
|
75
75
|
"zod": "^4.4.3"
|
|
76
76
|
},
|