pdf-codec 4.3.0 → 4.5.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 +2 -1
- package/dist/content-write.cjs +29 -7
- package/dist/content-write.d.cts +8 -1
- package/dist/content-write.d.ts +8 -1
- package/dist/content-write.js +29 -7
- package/dist/image/ccitt-encode.cjs +119 -0
- package/dist/image/ccitt-encode.d.cts +9 -0
- package/dist/image/ccitt-encode.d.ts +9 -0
- package/dist/image/ccitt-encode.js +118 -0
- package/dist/image/ccitt.cjs +3 -0
- package/dist/image/ccitt.d.cts +4 -1
- package/dist/image/ccitt.d.ts +4 -1
- package/dist/image/ccitt.js +1 -1
- package/dist/write.cjs +291 -2
- package/dist/write.js +292 -3
- package/package.json +1 -1
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,
|
|
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`).
|
|
@@ -264,6 +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
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`.
|
|
268
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.
|
|
269
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).
|
package/dist/content-write.cjs
CHANGED
|
@@ -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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
package/dist/content-write.d.cts
CHANGED
|
@@ -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 };
|
package/dist/content-write.d.ts
CHANGED
|
@@ -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 };
|
package/dist/content-write.js
CHANGED
|
@@ -312,16 +312,38 @@ function writeContentStream(items, context) {
|
|
|
312
312
|
const writer = new ByteWriter();
|
|
313
313
|
const substitutions = [];
|
|
314
314
|
const missingGlyphs = [];
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_image_ccitt = require("./ccitt.cjs");
|
|
3
|
+
//#region src/image/ccitt-encode.ts
|
|
4
|
+
function buildRunCodes(table) {
|
|
5
|
+
const map = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const [bits, run] of table) map.set(run, bits);
|
|
7
|
+
for (const [bits, run] of require_image_ccitt.EXTENDED_MAKEUP) if (!map.has(run)) map.set(run, bits);
|
|
8
|
+
return map;
|
|
9
|
+
}
|
|
10
|
+
const WHITE_RUN_CODES = buildRunCodes(require_image_ccitt.WHITE_TERMINATING_AND_MAKEUP);
|
|
11
|
+
const BLACK_RUN_CODES = buildRunCodes(require_image_ccitt.BLACK_TERMINATING_AND_MAKEUP);
|
|
12
|
+
const PASS_MODE = "0001";
|
|
13
|
+
const HORIZONTAL_MODE = "001";
|
|
14
|
+
const VERTICAL_MODES = /* @__PURE__ */ new Map([
|
|
15
|
+
[0, "1"],
|
|
16
|
+
[1, "011"],
|
|
17
|
+
[-1, "010"],
|
|
18
|
+
[2, "000011"],
|
|
19
|
+
[-2, "000010"],
|
|
20
|
+
[3, "0000011"],
|
|
21
|
+
[-3, "0000010"]
|
|
22
|
+
]);
|
|
23
|
+
const LARGEST_MAKEUP_RUN = 2560;
|
|
24
|
+
const MAKEUP_GRANULARITY = 64;
|
|
25
|
+
var BitWriter = class {
|
|
26
|
+
bytes = [];
|
|
27
|
+
current = 0;
|
|
28
|
+
used = 0;
|
|
29
|
+
get byteLength() {
|
|
30
|
+
return this.bytes.length;
|
|
31
|
+
}
|
|
32
|
+
writeBits(bits) {
|
|
33
|
+
for (const char of bits) {
|
|
34
|
+
this.current = this.current << 1 | (char === "1" ? 1 : 0);
|
|
35
|
+
this.used++;
|
|
36
|
+
if (this.used === 8) {
|
|
37
|
+
this.bytes.push(this.current);
|
|
38
|
+
this.current = 0;
|
|
39
|
+
this.used = 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
finish() {
|
|
44
|
+
if (this.used > 0) this.bytes.push(this.current << 8 - this.used);
|
|
45
|
+
return new Uint8Array(this.bytes);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function writeRun(writer, run, white) {
|
|
49
|
+
const codes = white ? WHITE_RUN_CODES : BLACK_RUN_CODES;
|
|
50
|
+
let remaining = run;
|
|
51
|
+
while (remaining >= MAKEUP_GRANULARITY) {
|
|
52
|
+
const makeup = Math.min(remaining - remaining % MAKEUP_GRANULARITY, LARGEST_MAKEUP_RUN);
|
|
53
|
+
writer.writeBits(codes.get(makeup));
|
|
54
|
+
remaining -= makeup;
|
|
55
|
+
}
|
|
56
|
+
writer.writeBits(codes.get(remaining));
|
|
57
|
+
}
|
|
58
|
+
function changingElements(bitmap, rowByteOffset, columns) {
|
|
59
|
+
const changes = [];
|
|
60
|
+
let previous = 1;
|
|
61
|
+
for (let x = 0; x < columns; x++) {
|
|
62
|
+
const pixel = (bitmap[rowByteOffset + (x >> 3)] ?? 0) >> 7 - (x & 7) & 1;
|
|
63
|
+
if (pixel !== previous) {
|
|
64
|
+
changes.push(x);
|
|
65
|
+
previous = pixel;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return changes;
|
|
69
|
+
}
|
|
70
|
+
function encodeCcittFax(bitmap, options) {
|
|
71
|
+
const { columns, rows, maxBytes } = options;
|
|
72
|
+
if (columns <= 0 || rows <= 0) return /* @__PURE__ */ new Uint8Array(0);
|
|
73
|
+
const rowBytes = columns + 7 >> 3;
|
|
74
|
+
const writer = new BitWriter();
|
|
75
|
+
let reference = [];
|
|
76
|
+
for (let y = 0; y < rows; y++) {
|
|
77
|
+
const offset = y * rowBytes;
|
|
78
|
+
const current = changingElements(bitmap, offset, columns);
|
|
79
|
+
let ci = 0;
|
|
80
|
+
let ri = 0;
|
|
81
|
+
const cur = (i) => current[i] ?? columns;
|
|
82
|
+
const ref = (i) => reference[i] ?? columns;
|
|
83
|
+
let a0 = -1;
|
|
84
|
+
let white = true;
|
|
85
|
+
while (a0 < columns) {
|
|
86
|
+
if (maxBytes !== void 0 && writer.byteLength > maxBytes) return;
|
|
87
|
+
const a1 = cur(ci);
|
|
88
|
+
const a2 = cur(ci + 1);
|
|
89
|
+
while (ri < reference.length && ref(ri) <= a0) ri++;
|
|
90
|
+
let b1Index = ri;
|
|
91
|
+
if (b1Index % 2 !== (white ? 0 : 1)) b1Index++;
|
|
92
|
+
const b1 = ref(b1Index);
|
|
93
|
+
const b2 = ref(b1Index + 1);
|
|
94
|
+
if (b2 < a1) {
|
|
95
|
+
writer.writeBits(PASS_MODE);
|
|
96
|
+
a0 = b2;
|
|
97
|
+
} else {
|
|
98
|
+
const delta = a1 - b1;
|
|
99
|
+
const vertical = VERTICAL_MODES.get(delta);
|
|
100
|
+
if (vertical !== void 0) {
|
|
101
|
+
writer.writeBits(vertical);
|
|
102
|
+
a0 = a1;
|
|
103
|
+
ci++;
|
|
104
|
+
} else {
|
|
105
|
+
writer.writeBits(HORIZONTAL_MODE);
|
|
106
|
+
writeRun(writer, a1 - Math.max(a0, 0), white);
|
|
107
|
+
writeRun(writer, a2 - a1, !white);
|
|
108
|
+
a0 = a2;
|
|
109
|
+
ci += 2;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (a0 < columns) white = ((bitmap[offset + (a0 >> 3)] ?? 0) >> 7 - (a0 & 7) & 1) === 1;
|
|
113
|
+
}
|
|
114
|
+
reference = current;
|
|
115
|
+
}
|
|
116
|
+
return writer.finish();
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
exports.encodeCcittFax = encodeCcittFax;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/image/ccitt-encode.d.ts
|
|
2
|
+
interface EncodeCcittFaxOptions {
|
|
3
|
+
readonly columns: number;
|
|
4
|
+
readonly rows: number;
|
|
5
|
+
readonly maxBytes?: number;
|
|
6
|
+
}
|
|
7
|
+
declare function encodeCcittFax(bitmap: Uint8Array, options: EncodeCcittFaxOptions): Uint8Array<ArrayBuffer> | undefined;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { EncodeCcittFaxOptions, encodeCcittFax };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/image/ccitt-encode.d.ts
|
|
2
|
+
interface EncodeCcittFaxOptions {
|
|
3
|
+
readonly columns: number;
|
|
4
|
+
readonly rows: number;
|
|
5
|
+
readonly maxBytes?: number;
|
|
6
|
+
}
|
|
7
|
+
declare function encodeCcittFax(bitmap: Uint8Array, options: EncodeCcittFaxOptions): Uint8Array<ArrayBuffer> | undefined;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { EncodeCcittFaxOptions, encodeCcittFax };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { BLACK_TERMINATING_AND_MAKEUP, EXTENDED_MAKEUP, WHITE_TERMINATING_AND_MAKEUP } from "./ccitt.js";
|
|
2
|
+
//#region src/image/ccitt-encode.ts
|
|
3
|
+
function buildRunCodes(table) {
|
|
4
|
+
const map = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const [bits, run] of table) map.set(run, bits);
|
|
6
|
+
for (const [bits, run] of EXTENDED_MAKEUP) if (!map.has(run)) map.set(run, bits);
|
|
7
|
+
return map;
|
|
8
|
+
}
|
|
9
|
+
const WHITE_RUN_CODES = buildRunCodes(WHITE_TERMINATING_AND_MAKEUP);
|
|
10
|
+
const BLACK_RUN_CODES = buildRunCodes(BLACK_TERMINATING_AND_MAKEUP);
|
|
11
|
+
const PASS_MODE = "0001";
|
|
12
|
+
const HORIZONTAL_MODE = "001";
|
|
13
|
+
const VERTICAL_MODES = /* @__PURE__ */ new Map([
|
|
14
|
+
[0, "1"],
|
|
15
|
+
[1, "011"],
|
|
16
|
+
[-1, "010"],
|
|
17
|
+
[2, "000011"],
|
|
18
|
+
[-2, "000010"],
|
|
19
|
+
[3, "0000011"],
|
|
20
|
+
[-3, "0000010"]
|
|
21
|
+
]);
|
|
22
|
+
const LARGEST_MAKEUP_RUN = 2560;
|
|
23
|
+
const MAKEUP_GRANULARITY = 64;
|
|
24
|
+
var BitWriter = class {
|
|
25
|
+
bytes = [];
|
|
26
|
+
current = 0;
|
|
27
|
+
used = 0;
|
|
28
|
+
get byteLength() {
|
|
29
|
+
return this.bytes.length;
|
|
30
|
+
}
|
|
31
|
+
writeBits(bits) {
|
|
32
|
+
for (const char of bits) {
|
|
33
|
+
this.current = this.current << 1 | (char === "1" ? 1 : 0);
|
|
34
|
+
this.used++;
|
|
35
|
+
if (this.used === 8) {
|
|
36
|
+
this.bytes.push(this.current);
|
|
37
|
+
this.current = 0;
|
|
38
|
+
this.used = 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
finish() {
|
|
43
|
+
if (this.used > 0) this.bytes.push(this.current << 8 - this.used);
|
|
44
|
+
return new Uint8Array(this.bytes);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
function writeRun(writer, run, white) {
|
|
48
|
+
const codes = white ? WHITE_RUN_CODES : BLACK_RUN_CODES;
|
|
49
|
+
let remaining = run;
|
|
50
|
+
while (remaining >= MAKEUP_GRANULARITY) {
|
|
51
|
+
const makeup = Math.min(remaining - remaining % MAKEUP_GRANULARITY, LARGEST_MAKEUP_RUN);
|
|
52
|
+
writer.writeBits(codes.get(makeup));
|
|
53
|
+
remaining -= makeup;
|
|
54
|
+
}
|
|
55
|
+
writer.writeBits(codes.get(remaining));
|
|
56
|
+
}
|
|
57
|
+
function changingElements(bitmap, rowByteOffset, columns) {
|
|
58
|
+
const changes = [];
|
|
59
|
+
let previous = 1;
|
|
60
|
+
for (let x = 0; x < columns; x++) {
|
|
61
|
+
const pixel = (bitmap[rowByteOffset + (x >> 3)] ?? 0) >> 7 - (x & 7) & 1;
|
|
62
|
+
if (pixel !== previous) {
|
|
63
|
+
changes.push(x);
|
|
64
|
+
previous = pixel;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return changes;
|
|
68
|
+
}
|
|
69
|
+
function encodeCcittFax(bitmap, options) {
|
|
70
|
+
const { columns, rows, maxBytes } = options;
|
|
71
|
+
if (columns <= 0 || rows <= 0) return /* @__PURE__ */ new Uint8Array(0);
|
|
72
|
+
const rowBytes = columns + 7 >> 3;
|
|
73
|
+
const writer = new BitWriter();
|
|
74
|
+
let reference = [];
|
|
75
|
+
for (let y = 0; y < rows; y++) {
|
|
76
|
+
const offset = y * rowBytes;
|
|
77
|
+
const current = changingElements(bitmap, offset, columns);
|
|
78
|
+
let ci = 0;
|
|
79
|
+
let ri = 0;
|
|
80
|
+
const cur = (i) => current[i] ?? columns;
|
|
81
|
+
const ref = (i) => reference[i] ?? columns;
|
|
82
|
+
let a0 = -1;
|
|
83
|
+
let white = true;
|
|
84
|
+
while (a0 < columns) {
|
|
85
|
+
if (maxBytes !== void 0 && writer.byteLength > maxBytes) return;
|
|
86
|
+
const a1 = cur(ci);
|
|
87
|
+
const a2 = cur(ci + 1);
|
|
88
|
+
while (ri < reference.length && ref(ri) <= a0) ri++;
|
|
89
|
+
let b1Index = ri;
|
|
90
|
+
if (b1Index % 2 !== (white ? 0 : 1)) b1Index++;
|
|
91
|
+
const b1 = ref(b1Index);
|
|
92
|
+
const b2 = ref(b1Index + 1);
|
|
93
|
+
if (b2 < a1) {
|
|
94
|
+
writer.writeBits(PASS_MODE);
|
|
95
|
+
a0 = b2;
|
|
96
|
+
} else {
|
|
97
|
+
const delta = a1 - b1;
|
|
98
|
+
const vertical = VERTICAL_MODES.get(delta);
|
|
99
|
+
if (vertical !== void 0) {
|
|
100
|
+
writer.writeBits(vertical);
|
|
101
|
+
a0 = a1;
|
|
102
|
+
ci++;
|
|
103
|
+
} else {
|
|
104
|
+
writer.writeBits(HORIZONTAL_MODE);
|
|
105
|
+
writeRun(writer, a1 - Math.max(a0, 0), white);
|
|
106
|
+
writeRun(writer, a2 - a1, !white);
|
|
107
|
+
a0 = a2;
|
|
108
|
+
ci += 2;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (a0 < columns) white = ((bitmap[offset + (a0 >> 3)] ?? 0) >> 7 - (a0 & 7) & 1) === 1;
|
|
112
|
+
}
|
|
113
|
+
reference = current;
|
|
114
|
+
}
|
|
115
|
+
return writer.finish();
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { encodeCcittFax };
|
package/dist/image/ccitt.cjs
CHANGED
|
@@ -485,4 +485,7 @@ function decodeCcittFax(data, options = {}) {
|
|
|
485
485
|
};
|
|
486
486
|
}
|
|
487
487
|
//#endregion
|
|
488
|
+
exports.BLACK_TERMINATING_AND_MAKEUP = BLACK_TERMINATING_AND_MAKEUP;
|
|
489
|
+
exports.EXTENDED_MAKEUP = EXTENDED_MAKEUP;
|
|
490
|
+
exports.WHITE_TERMINATING_AND_MAKEUP = WHITE_TERMINATING_AND_MAKEUP;
|
|
488
491
|
exports.decodeCcittFax = decodeCcittFax;
|
package/dist/image/ccitt.d.cts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
//#region src/image/ccitt.d.ts
|
|
2
|
+
declare const WHITE_TERMINATING_AND_MAKEUP: readonly (readonly [string, number])[];
|
|
3
|
+
declare const BLACK_TERMINATING_AND_MAKEUP: readonly (readonly [string, number])[];
|
|
4
|
+
declare const EXTENDED_MAKEUP: readonly (readonly [string, number])[];
|
|
2
5
|
interface CcittFaxOptions {
|
|
3
6
|
readonly k?: number;
|
|
4
7
|
readonly columns?: number;
|
|
@@ -14,4 +17,4 @@ interface CcittFaxImage {
|
|
|
14
17
|
}
|
|
15
18
|
declare function decodeCcittFax(data: Uint8Array<ArrayBuffer>, options?: CcittFaxOptions): CcittFaxImage;
|
|
16
19
|
//#endregion
|
|
17
|
-
export { CcittFaxImage, CcittFaxOptions, decodeCcittFax };
|
|
20
|
+
export { BLACK_TERMINATING_AND_MAKEUP, CcittFaxImage, CcittFaxOptions, EXTENDED_MAKEUP, WHITE_TERMINATING_AND_MAKEUP, decodeCcittFax };
|
package/dist/image/ccitt.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
//#region src/image/ccitt.d.ts
|
|
2
|
+
declare const WHITE_TERMINATING_AND_MAKEUP: readonly (readonly [string, number])[];
|
|
3
|
+
declare const BLACK_TERMINATING_AND_MAKEUP: readonly (readonly [string, number])[];
|
|
4
|
+
declare const EXTENDED_MAKEUP: readonly (readonly [string, number])[];
|
|
2
5
|
interface CcittFaxOptions {
|
|
3
6
|
readonly k?: number;
|
|
4
7
|
readonly columns?: number;
|
|
@@ -14,4 +17,4 @@ interface CcittFaxImage {
|
|
|
14
17
|
}
|
|
15
18
|
declare function decodeCcittFax(data: Uint8Array<ArrayBuffer>, options?: CcittFaxOptions): CcittFaxImage;
|
|
16
19
|
//#endregion
|
|
17
|
-
export { CcittFaxImage, CcittFaxOptions, decodeCcittFax };
|
|
20
|
+
export { BLACK_TERMINATING_AND_MAKEUP, CcittFaxImage, CcittFaxOptions, EXTENDED_MAKEUP, WHITE_TERMINATING_AND_MAKEUP, decodeCcittFax };
|
package/dist/image/ccitt.js
CHANGED
package/dist/write.cjs
CHANGED
|
@@ -4,12 +4,15 @@ const require_notes_annotation_author = require("./notes-annotation-author.cjs")
|
|
|
4
4
|
const require_objects = require("./objects.cjs");
|
|
5
5
|
const require_bytes_writer = require("./bytes/writer.cjs");
|
|
6
6
|
const require_serialize = require("./serialize.cjs");
|
|
7
|
+
const require_bytes_reader = require("./bytes/reader.cjs");
|
|
7
8
|
const require_bytes_flate = require("./bytes/flate.cjs");
|
|
8
9
|
const require_util_base64 = require("./util/base64.cjs");
|
|
10
|
+
const require_parse = require("./parse.cjs");
|
|
9
11
|
const require_util_abort = require("./util/abort.cjs");
|
|
10
12
|
const require_crypto_random = require("./crypto/random.cjs");
|
|
11
13
|
const require_encrypt_write = require("./encrypt-write.cjs");
|
|
12
14
|
const require_image_jpeg_info = require("./image/jpeg-info.cjs");
|
|
15
|
+
const require_image_ccitt_encode = require("./image/ccitt-encode.cjs");
|
|
13
16
|
const require_image_png_decode = require("./image/png-decode.cjs");
|
|
14
17
|
const require_embedded_font = require("./embedded-font.cjs");
|
|
15
18
|
const require_content_write = require("./content-write.cjs");
|
|
@@ -143,9 +146,57 @@ function pngImageDict(width, height, colorSpace, compress) {
|
|
|
143
146
|
if (compress) entries.set("Filter", require_objects.pdfName("FlateDecode"));
|
|
144
147
|
return require_objects.pdfDict(entries);
|
|
145
148
|
}
|
|
149
|
+
function packBilevel(raw) {
|
|
150
|
+
const rowBytes = raw.width + 7 >> 3;
|
|
151
|
+
const packed = new Uint8Array(rowBytes * raw.height);
|
|
152
|
+
for (let y = 0; y < raw.height; y++) for (let x = 0; x < raw.width; x++) {
|
|
153
|
+
const sample = raw.data[y * raw.width + x] ?? 0;
|
|
154
|
+
if (sample !== 0 && sample !== 255) return;
|
|
155
|
+
if (sample === 255) {
|
|
156
|
+
const index = y * rowBytes + (x >> 3);
|
|
157
|
+
packed[index] = (packed[index] ?? 0) | 128 >> (x & 7);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return packed;
|
|
161
|
+
}
|
|
146
162
|
function preparePngImage(bytes, compress) {
|
|
147
163
|
const raw = require_image_png_decode.decodePng(bytes);
|
|
148
164
|
const colorSpace = raw.channels === 1 ? "DeviceGray" : "DeviceRGB";
|
|
165
|
+
if (compress && raw.channels === 1 && raw.alpha === void 0) {
|
|
166
|
+
const bilevel = packBilevel(raw);
|
|
167
|
+
if (bilevel !== void 0) {
|
|
168
|
+
const flate = require_bytes_flate.deflate(raw.data);
|
|
169
|
+
const g4 = require_image_ccitt_encode.encodeCcittFax(bilevel, {
|
|
170
|
+
columns: raw.width,
|
|
171
|
+
rows: raw.height,
|
|
172
|
+
maxBytes: flate.length
|
|
173
|
+
});
|
|
174
|
+
if (g4 !== void 0) return {
|
|
175
|
+
dict: require_objects.pdfDict(/* @__PURE__ */ new Map([
|
|
176
|
+
["Type", require_objects.pdfName("XObject")],
|
|
177
|
+
["Subtype", require_objects.pdfName("Image")],
|
|
178
|
+
["Width", require_objects.pdfNum(raw.width)],
|
|
179
|
+
["Height", require_objects.pdfNum(raw.height)],
|
|
180
|
+
["ColorSpace", require_objects.pdfName("DeviceGray")],
|
|
181
|
+
["BitsPerComponent", require_objects.pdfNum(1)],
|
|
182
|
+
["Filter", require_objects.pdfName("CCITTFaxDecode")],
|
|
183
|
+
["DecodeParms", require_objects.pdfDict(/* @__PURE__ */ new Map([
|
|
184
|
+
["K", require_objects.pdfNum(-1)],
|
|
185
|
+
["Columns", require_objects.pdfNum(raw.width)],
|
|
186
|
+
["Rows", require_objects.pdfNum(raw.height)],
|
|
187
|
+
["BlackIs1", require_objects.pdfBool(false)]
|
|
188
|
+
]))]
|
|
189
|
+
])),
|
|
190
|
+
raw: g4,
|
|
191
|
+
alpha: void 0
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
dict: pngImageDict(raw.width, raw.height, colorSpace, true),
|
|
195
|
+
raw: flate,
|
|
196
|
+
alpha: void 0
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
149
200
|
return {
|
|
150
201
|
dict: pngImageDict(raw.width, raw.height, colorSpace, compress),
|
|
151
202
|
raw: compress ? require_bytes_flate.deflate(raw.data) : raw.data,
|
|
@@ -252,6 +303,24 @@ function buildNotesAnnotDict(notes) {
|
|
|
252
303
|
function xrefEntry(offset, generation, inUse) {
|
|
253
304
|
return `${offset.toString().padStart(10, "0")} ${generation.toString().padStart(5, "0")} ${inUse ? "n" : "f"} \n`;
|
|
254
305
|
}
|
|
306
|
+
function parseResidueRow(residue) {
|
|
307
|
+
const reader = new require_bytes_reader.ByteReader(new TextEncoder().encode(residue.xml));
|
|
308
|
+
return require_parse.parseValue(reader, () => {});
|
|
309
|
+
}
|
|
310
|
+
function objectContainsReference(obj) {
|
|
311
|
+
if (obj.kind === "ref") return true;
|
|
312
|
+
if (obj.kind === "array") return obj.items.some(objectContainsReference);
|
|
313
|
+
if (obj.kind === "dict") return [...obj.entries.values()].some(objectContainsReference);
|
|
314
|
+
if (obj.kind === "stream") return [...obj.dict.entries.values()].some(objectContainsReference);
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
function restoreResidueRow(source, key) {
|
|
318
|
+
const row = source?.[key];
|
|
319
|
+
if (row === void 0) return;
|
|
320
|
+
const parsed = parseResidueRow(row);
|
|
321
|
+
if (parsed === void 0 || objectContainsReference(parsed)) return;
|
|
322
|
+
return parsed;
|
|
323
|
+
}
|
|
255
324
|
function writePdf(doc, options = {}) {
|
|
256
325
|
const compress = options.compress ?? true;
|
|
257
326
|
const registry = options.fonts;
|
|
@@ -340,10 +409,80 @@ function writePdf(doc, options = {}) {
|
|
|
340
409
|
};
|
|
341
410
|
for (let i = 0; i < countItems(doc.outline ?? []); i += 1) outlineItemNums.push(nextObjNum++);
|
|
342
411
|
}
|
|
412
|
+
const layerNumByName = /* @__PURE__ */ new Map();
|
|
413
|
+
for (const layer of doc.layers ?? []) layerNumByName.set(layer.name, nextObjNum++);
|
|
414
|
+
const formObjectNums = [];
|
|
415
|
+
const countFieldObjects = (fields) => {
|
|
416
|
+
let n = 0;
|
|
417
|
+
for (const field of fields) n += 1 + (field.fieldType !== "group" && field.widgets.length > 1 ? field.widgets.length - 1 : 0) + countFieldObjects(field.children);
|
|
418
|
+
return n;
|
|
419
|
+
};
|
|
420
|
+
for (let i = 0; i < countFieldObjects(doc.form ?? []); i += 1) formObjectNums.push(nextObjNum++);
|
|
421
|
+
const formNumByField = /* @__PURE__ */ new Map();
|
|
422
|
+
const formExtraWidgetNums = /* @__PURE__ */ new Map();
|
|
423
|
+
let formNumCursor = 0;
|
|
424
|
+
const claimFormNums = (fields) => {
|
|
425
|
+
for (const field of fields) {
|
|
426
|
+
formNumByField.set(field, formObjectNums[formNumCursor++]);
|
|
427
|
+
if (field.fieldType !== "group" && field.widgets.length > 1) formExtraWidgetNums.set(field, field.widgets.slice(1).map(() => formObjectNums[formNumCursor++]));
|
|
428
|
+
claimFormNums(field.children);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
claimFormNums(doc.form ?? []);
|
|
432
|
+
const formNumOf = (field) => {
|
|
433
|
+
const num = formNumByField.get(field);
|
|
434
|
+
if (num === void 0) throw new Error("AcroForm field object number was not claimed -- this is a writePdf internal invariant violation");
|
|
435
|
+
return num;
|
|
436
|
+
};
|
|
437
|
+
const structElementNumById = /* @__PURE__ */ new Map();
|
|
438
|
+
const countElements = (elements) => {
|
|
439
|
+
let n = 0;
|
|
440
|
+
for (const element of elements) {
|
|
441
|
+
structElementNumById.set(element.id, nextObjNum++);
|
|
442
|
+
n += 1 + countElements(element.children);
|
|
443
|
+
}
|
|
444
|
+
return n;
|
|
445
|
+
};
|
|
446
|
+
const structElementCount = countElements(doc.structure ?? []);
|
|
447
|
+
const structRootNum = structElementCount > 0 ? nextObjNum++ : void 0;
|
|
448
|
+
const structParentTreeNum = structElementCount > 0 ? nextObjNum++ : void 0;
|
|
449
|
+
const residueXmpNum = doc.source?.xmp !== void 0 ? nextObjNum++ : void 0;
|
|
343
450
|
const objects = [];
|
|
344
451
|
const catalogEntries = [["Type", require_objects.pdfName("Catalog")], ["Pages", require_objects.pdfRef(pagesNum, 0)]];
|
|
345
452
|
if (attachmentsNamesNum !== void 0) catalogEntries.push(["Names", require_objects.pdfRef(attachmentsNamesNum, 0)]);
|
|
346
453
|
if (outlineRootNum !== void 0) catalogEntries.push(["Outlines", require_objects.pdfRef(outlineRootNum, 0)]);
|
|
454
|
+
if (layerNumByName.size > 0) {
|
|
455
|
+
const ocgRefs = [...layerNumByName.values()].map((num) => require_objects.pdfRef(num, 0));
|
|
456
|
+
const visible = [];
|
|
457
|
+
const hidden = [];
|
|
458
|
+
for (const layer of doc.layers ?? []) {
|
|
459
|
+
const num = layerNumByName.get(layer.name);
|
|
460
|
+
(layer.visible ? visible : hidden).push(require_objects.pdfRef(num, 0));
|
|
461
|
+
}
|
|
462
|
+
const defaultConfigEntries = [];
|
|
463
|
+
if (visible.length > 0) defaultConfigEntries.push(["ON", require_objects.pdfArray(visible)]);
|
|
464
|
+
if (hidden.length > 0) defaultConfigEntries.push(["OFF", require_objects.pdfArray(hidden)]);
|
|
465
|
+
catalogEntries.push(["OCProperties", require_objects.pdfDict({
|
|
466
|
+
OCGs: require_objects.pdfArray(ocgRefs),
|
|
467
|
+
D: require_objects.pdfDict(Object.fromEntries(defaultConfigEntries))
|
|
468
|
+
})]);
|
|
469
|
+
}
|
|
470
|
+
if (doc.form !== void 0 && doc.form.length > 0) catalogEntries.push(["AcroForm", require_objects.pdfDict({ Fields: require_objects.pdfArray(doc.form.map((field) => require_objects.pdfRef(formNumOf(field), 0))) })]);
|
|
471
|
+
if (structRootNum !== void 0) catalogEntries.push(["StructTreeRoot", require_objects.pdfRef(structRootNum, 0)]);
|
|
472
|
+
if (residueXmpNum !== void 0) catalogEntries.push(["Metadata", require_objects.pdfRef(residueXmpNum, 0)]);
|
|
473
|
+
const trailerIdRestore = restoreResidueRow(doc.source, "trailer-id");
|
|
474
|
+
for (const [rowKey, catalogKey] of [
|
|
475
|
+
["viewer-preferences", "ViewerPreferences"],
|
|
476
|
+
["page-mode", "PageMode"],
|
|
477
|
+
["page-layout", "PageLayout"],
|
|
478
|
+
["output-intents", "OutputIntents"],
|
|
479
|
+
["piece-info", "PieceInfo"],
|
|
480
|
+
["legal", "Legal"],
|
|
481
|
+
["collection", "Collection"]
|
|
482
|
+
]) {
|
|
483
|
+
const restored = restoreResidueRow(doc.source, rowKey);
|
|
484
|
+
if (restored !== void 0) catalogEntries.push([catalogKey, restored]);
|
|
485
|
+
}
|
|
347
486
|
objects.push({
|
|
348
487
|
num: catalogNum,
|
|
349
488
|
value: require_objects.pdfDict(Object.fromEntries(catalogEntries))
|
|
@@ -427,6 +566,118 @@ function writePdf(doc, options = {}) {
|
|
|
427
566
|
})
|
|
428
567
|
});
|
|
429
568
|
}
|
|
569
|
+
for (const layer of doc.layers ?? []) {
|
|
570
|
+
const num = layerNumByName.get(layer.name);
|
|
571
|
+
objects.push({
|
|
572
|
+
num,
|
|
573
|
+
value: require_objects.pdfDict({
|
|
574
|
+
Type: require_objects.pdfName("OCG"),
|
|
575
|
+
Name: require_objects.pdfLiteralString(new TextEncoder().encode(layer.name))
|
|
576
|
+
})
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const widgetRectArray = (widget) => require_objects.pdfArray([
|
|
580
|
+
widget.xPt,
|
|
581
|
+
widget.yPt,
|
|
582
|
+
widget.xPt + widget.widthPt,
|
|
583
|
+
widget.yPt + widget.heightPt
|
|
584
|
+
].map((n) => require_objects.pdfNum(n)));
|
|
585
|
+
const widgetDict = (widget) => require_objects.pdfDict({
|
|
586
|
+
Subtype: require_objects.pdfName("Widget"),
|
|
587
|
+
Rect: widgetRectArray(widget),
|
|
588
|
+
P: require_objects.pdfRef(pageAllocs[widget.pageIndex].pageNum, 0)
|
|
589
|
+
});
|
|
590
|
+
const FIELD_TYPE_PDF_NAME = {
|
|
591
|
+
text: "Tx",
|
|
592
|
+
checkbox: "Btn",
|
|
593
|
+
radio: "Btn",
|
|
594
|
+
button: "Btn",
|
|
595
|
+
listbox: "Ch",
|
|
596
|
+
combobox: "Ch",
|
|
597
|
+
signature: "Sig"
|
|
598
|
+
};
|
|
599
|
+
const emitFormFieldObjects = (fields, parentName) => {
|
|
600
|
+
for (const field of fields) {
|
|
601
|
+
const ownName = parentName.length > 0 && field.name.startsWith(`${parentName}.`) ? field.name.slice(parentName.length + 1) : field.name;
|
|
602
|
+
const entries = [];
|
|
603
|
+
if (ownName.length > 0) entries.push(["T", require_objects.pdfLiteralString(new TextEncoder().encode(ownName))]);
|
|
604
|
+
if (field.alias !== void 0) entries.push(["TU", require_objects.pdfLiteralString(new TextEncoder().encode(field.alias))]);
|
|
605
|
+
if (field.fieldType === "group") entries.push(["Kids", require_objects.pdfArray(field.children.map((child) => require_objects.pdfRef(formNumOf(child), 0)))]);
|
|
606
|
+
else {
|
|
607
|
+
entries.push(["FT", require_objects.pdfName(FIELD_TYPE_PDF_NAME[field.fieldType])]);
|
|
608
|
+
const FLAG_READ_ONLY = 1;
|
|
609
|
+
const FLAG_PUSHBUTTON = 4;
|
|
610
|
+
const FLAG_RADIO = 32768;
|
|
611
|
+
const FLAG_COMBO = 131072;
|
|
612
|
+
let flags = 0;
|
|
613
|
+
if (field.readOnly === true) flags |= FLAG_READ_ONLY;
|
|
614
|
+
if (field.fieldType === "button") flags |= FLAG_PUSHBUTTON;
|
|
615
|
+
if (field.fieldType === "radio") flags |= FLAG_RADIO;
|
|
616
|
+
if (field.fieldType === "combobox") flags |= FLAG_COMBO;
|
|
617
|
+
if (flags !== 0) entries.push(["Ff", require_objects.pdfNum(flags)]);
|
|
618
|
+
if (field.fieldType === "text" || field.fieldType === "listbox" || field.fieldType === "combobox") {
|
|
619
|
+
if (field.value !== void 0) entries.push(["V", require_objects.pdfLiteralString(new TextEncoder().encode(field.value))]);
|
|
620
|
+
} else if (field.fieldType === "checkbox" || field.fieldType === "radio") entries.push(["V", require_objects.pdfName(field.checked === false || field.value === void 0 ? field.value ?? (field.checked === true ? "Yes" : "Off") : field.value)]);
|
|
621
|
+
if (field.options !== void 0) entries.push(["Opt", require_objects.pdfArray(field.options.map((option) => require_objects.pdfLiteralString(new TextEncoder().encode(option))))]);
|
|
622
|
+
const firstWidget = field.widgets[0];
|
|
623
|
+
if (field.widgets.length === 1 && firstWidget !== void 0) {
|
|
624
|
+
entries.push(["Subtype", require_objects.pdfName("Widget")]);
|
|
625
|
+
entries.push(["Rect", widgetRectArray(firstWidget)]);
|
|
626
|
+
entries.push(["P", require_objects.pdfRef(pageAllocs[firstWidget.pageIndex].pageNum, 0)]);
|
|
627
|
+
} else if (field.widgets.length > 1 && firstWidget !== void 0) {
|
|
628
|
+
const extraNums = formExtraWidgetNums.get(field) ?? [];
|
|
629
|
+
entries.push(["Kids", require_objects.pdfArray([widgetDict(firstWidget), ...extraNums.map((num) => require_objects.pdfRef(num, 0))])]);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
objects.push({
|
|
633
|
+
num: formNumOf(field),
|
|
634
|
+
value: require_objects.pdfDict(Object.fromEntries(entries))
|
|
635
|
+
});
|
|
636
|
+
const extraNums = formExtraWidgetNums.get(field) ?? [];
|
|
637
|
+
for (const [index, num] of extraNums.entries()) {
|
|
638
|
+
const widget = field.widgets[index + 1];
|
|
639
|
+
objects.push({
|
|
640
|
+
num,
|
|
641
|
+
value: widgetDict(widget)
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
emitFormFieldObjects(field.children, field.name);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
emitFormFieldObjects(doc.form ?? [], "");
|
|
648
|
+
if (structRootNum !== void 0 && structParentTreeNum !== void 0) {
|
|
649
|
+
const emitStructureElement = (element, parentNum) => {
|
|
650
|
+
const entries = [
|
|
651
|
+
["Type", require_objects.pdfName("StructElem")],
|
|
652
|
+
["S", require_objects.pdfName(element.type)],
|
|
653
|
+
["P", require_objects.pdfRef(parentNum, 0)]
|
|
654
|
+
];
|
|
655
|
+
if (element.title !== void 0) entries.push(["T", require_objects.pdfLiteralString(new TextEncoder().encode(element.title))]);
|
|
656
|
+
if (element.language !== void 0) entries.push(["Lang", require_objects.pdfLiteralString(new TextEncoder().encode(element.language))]);
|
|
657
|
+
if (element.alt !== void 0) entries.push(["Alt", require_objects.pdfLiteralString(new TextEncoder().encode(element.alt))]);
|
|
658
|
+
if (element.actualText !== void 0) entries.push(["ActualText", require_objects.pdfLiteralString(new TextEncoder().encode(element.actualText))]);
|
|
659
|
+
if (element.children.length > 0) entries.push(["K", require_objects.pdfArray(element.children.map((child) => {
|
|
660
|
+
const num = structElementNumById.get(child.id);
|
|
661
|
+
if (num === void 0) throw new Error(`structure element "${child.id}" was not allocated -- this is a writePdf internal invariant violation`);
|
|
662
|
+
return require_objects.pdfRef(num, 0);
|
|
663
|
+
}))]);
|
|
664
|
+
const ownNum = structElementNumById.get(element.id);
|
|
665
|
+
if (ownNum === void 0) throw new Error(`structure element "${element.id}" was not allocated -- this is a writePdf internal invariant violation`);
|
|
666
|
+
objects.push({
|
|
667
|
+
num: ownNum,
|
|
668
|
+
value: require_objects.pdfDict(Object.fromEntries(entries))
|
|
669
|
+
});
|
|
670
|
+
for (const child of element.children) emitStructureElement(child, ownNum);
|
|
671
|
+
};
|
|
672
|
+
for (const element of doc.structure ?? []) emitStructureElement(element, structRootNum);
|
|
673
|
+
}
|
|
674
|
+
if (residueXmpNum !== void 0 && doc.source?.xmp !== void 0) objects.push({
|
|
675
|
+
num: residueXmpNum,
|
|
676
|
+
value: require_objects.pdfStream(require_objects.pdfDict({
|
|
677
|
+
Type: require_objects.pdfName("Metadata"),
|
|
678
|
+
Subtype: require_objects.pdfName("XML")
|
|
679
|
+
}), new TextEncoder().encode(doc.source.xmp.xml))
|
|
680
|
+
});
|
|
430
681
|
for (const [standardName, alloc] of fontAllocs) {
|
|
431
682
|
const { font, descriptor } = buildFontObjects(standardName, require_objects.pdfRef(alloc.descNum, 0));
|
|
432
683
|
objects.push({
|
|
@@ -521,6 +772,7 @@ function writePdf(doc, options = {}) {
|
|
|
521
772
|
}
|
|
522
773
|
if (imageAllocs.size > 0) resourceEntries.set("XObject", require_objects.pdfDict(new Map([...imageAllocs.values()].map((alloc) => [alloc.resourceName, require_objects.pdfRef(alloc.imageNum, 0)]))));
|
|
523
774
|
const resourcesDict = require_objects.pdfDict(resourceEntries);
|
|
775
|
+
const markedStructureByPage = /* @__PURE__ */ new Map();
|
|
524
776
|
const formulasByPage = /* @__PURE__ */ new Map();
|
|
525
777
|
for (const formula of formulas) {
|
|
526
778
|
const forPage = formulasByPage.get(formula.pageIndex);
|
|
@@ -557,7 +809,14 @@ function writePdf(doc, options = {}) {
|
|
|
557
809
|
doc.pages.forEach((page, pageIndex) => {
|
|
558
810
|
require_util_abort.throwIfAborted(options.signal);
|
|
559
811
|
const { pageNum, contentsNum } = pageAllocs[pageIndex];
|
|
560
|
-
|
|
812
|
+
let pageMcid = 0;
|
|
813
|
+
const pageContext = {
|
|
814
|
+
...context,
|
|
815
|
+
nextMcid: () => pageMcid++,
|
|
816
|
+
layerObjectNumberOf: (name) => layerNumByName.get(name)
|
|
817
|
+
};
|
|
818
|
+
const { bytes: contentBytes, substitutions, missingGlyphs, markedStructure } = require_content_write.writeContentStream(page.items, pageContext);
|
|
819
|
+
if (markedStructure.length > 0) markedStructureByPage.set(pageIndex, [...markedStructure]);
|
|
561
820
|
for (const substitution of substitutions) options.onSubstitution?.(substitution, { pageIndex });
|
|
562
821
|
for (const missing of missingGlyphs) options.onMissingGlyph?.(missing, { pageIndex });
|
|
563
822
|
const pageFormulas = formulasByPage.get(pageIndex);
|
|
@@ -587,11 +846,41 @@ function writePdf(doc, options = {}) {
|
|
|
587
846
|
["Contents", require_objects.pdfRef(contentsNum, 0)]
|
|
588
847
|
]);
|
|
589
848
|
if (annots.length > 0) pageEntries.set("Annots", require_objects.pdfArray(annots));
|
|
849
|
+
if ((markedStructureByPage.get(pageIndex) ?? []).length > 0) pageEntries.set("StructParents", require_objects.pdfNum(pageIndex));
|
|
590
850
|
objects.push({
|
|
591
851
|
num: pageNum,
|
|
592
852
|
value: require_objects.pdfDict(pageEntries)
|
|
593
853
|
});
|
|
594
854
|
});
|
|
855
|
+
if (structRootNum !== void 0 && structParentTreeNum !== void 0) {
|
|
856
|
+
const nums = [];
|
|
857
|
+
for (const [pageIndex, marks] of markedStructureByPage) {
|
|
858
|
+
const maxMcid = Math.max(...marks.map((mark) => mark.mcid));
|
|
859
|
+
const byMcid = Array.from({ length: maxMcid + 1 }, () => require_objects.pdfNull());
|
|
860
|
+
for (const mark of marks) {
|
|
861
|
+
const elementNum = structElementNumById.get(mark.structureId);
|
|
862
|
+
if (elementNum !== void 0) byMcid[mark.mcid] = require_objects.pdfRef(elementNum, 0);
|
|
863
|
+
}
|
|
864
|
+
nums.push(require_objects.pdfNum(pageIndex), require_objects.pdfArray(byMcid));
|
|
865
|
+
}
|
|
866
|
+
objects.push({
|
|
867
|
+
num: structParentTreeNum,
|
|
868
|
+
value: require_objects.pdfDict({ Nums: require_objects.pdfArray(nums) })
|
|
869
|
+
});
|
|
870
|
+
const rootEntries = [
|
|
871
|
+
["Type", require_objects.pdfName("StructTreeRoot")],
|
|
872
|
+
["K", require_objects.pdfArray((doc.structure ?? []).map((element) => {
|
|
873
|
+
const num = structElementNumById.get(element.id);
|
|
874
|
+
if (num === void 0) throw new Error(`structure element "${element.id}" was not allocated -- this is a writePdf internal invariant violation`);
|
|
875
|
+
return require_objects.pdfRef(num, 0);
|
|
876
|
+
}))],
|
|
877
|
+
["ParentTree", require_objects.pdfRef(structParentTreeNum, 0)]
|
|
878
|
+
];
|
|
879
|
+
objects.push({
|
|
880
|
+
num: structRootNum,
|
|
881
|
+
value: require_objects.pdfDict(Object.fromEntries(rootEntries))
|
|
882
|
+
});
|
|
883
|
+
}
|
|
595
884
|
let fileId;
|
|
596
885
|
let encryptedObjects = objects;
|
|
597
886
|
if (options.encryption !== void 0 && encryptDictNum !== void 0) {
|
|
@@ -633,7 +922,7 @@ function writePdf(doc, options = {}) {
|
|
|
633
922
|
if (fileId !== void 0 && encryptDictNum !== void 0) {
|
|
634
923
|
trailerEntries.set("ID", require_objects.pdfArray([require_objects.pdfHexString(fileId), require_objects.pdfHexString(fileId)]));
|
|
635
924
|
trailerEntries.set("Encrypt", require_objects.pdfRef(encryptDictNum, 0));
|
|
636
|
-
}
|
|
925
|
+
} else if (trailerIdRestore !== void 0) trailerEntries.set("ID", trailerIdRestore);
|
|
637
926
|
writer.writeAscii("trailer\n");
|
|
638
927
|
require_serialize.writeObject(writer, require_objects.pdfDict(trailerEntries));
|
|
639
928
|
writer.writeAscii("\nstartxref\n");
|
package/dist/write.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { n as widthOfCode, p as winAnsiGlyphName, t as STANDARD_METRICS } from "./afm-widths-De_QMWgC.js";
|
|
2
2
|
import { NOTES_ANNOTATION_AUTHOR } from "./notes-annotation-author.js";
|
|
3
|
-
import { pdfArray, pdfDict, pdfHexString, pdfLiteralString, pdfName, pdfNull, pdfNum, pdfRef, pdfStream } from "./objects.js";
|
|
3
|
+
import { pdfArray, pdfBool, pdfDict, pdfHexString, pdfLiteralString, pdfName, pdfNull, pdfNum, pdfRef, pdfStream } from "./objects.js";
|
|
4
4
|
import { ByteWriter, concatBytes } from "./bytes/writer.js";
|
|
5
5
|
import { writeObject } from "./serialize.js";
|
|
6
|
+
import { ByteReader } from "./bytes/reader.js";
|
|
6
7
|
import { deflate } from "./bytes/flate.js";
|
|
7
8
|
import { base64ToBytes } from "./util/base64.js";
|
|
9
|
+
import { parseValue } from "./parse.js";
|
|
8
10
|
import { throwIfAborted } from "./util/abort.js";
|
|
9
11
|
import { randomBytes } from "./crypto/random.js";
|
|
10
12
|
import { createStandardEncryptor, encryptIndirectObject } from "./encrypt-write.js";
|
|
11
13
|
import { readJpegInfo } from "./image/jpeg-info.js";
|
|
14
|
+
import { encodeCcittFax } from "./image/ccitt-encode.js";
|
|
12
15
|
import { decodePng } from "./image/png-decode.js";
|
|
13
16
|
import { collectEmbeddedGlyphs } from "./embedded-font.js";
|
|
14
17
|
import { writeContentStream } from "./content-write.js";
|
|
@@ -142,9 +145,57 @@ function pngImageDict(width, height, colorSpace, compress) {
|
|
|
142
145
|
if (compress) entries.set("Filter", pdfName("FlateDecode"));
|
|
143
146
|
return pdfDict(entries);
|
|
144
147
|
}
|
|
148
|
+
function packBilevel(raw) {
|
|
149
|
+
const rowBytes = raw.width + 7 >> 3;
|
|
150
|
+
const packed = new Uint8Array(rowBytes * raw.height);
|
|
151
|
+
for (let y = 0; y < raw.height; y++) for (let x = 0; x < raw.width; x++) {
|
|
152
|
+
const sample = raw.data[y * raw.width + x] ?? 0;
|
|
153
|
+
if (sample !== 0 && sample !== 255) return;
|
|
154
|
+
if (sample === 255) {
|
|
155
|
+
const index = y * rowBytes + (x >> 3);
|
|
156
|
+
packed[index] = (packed[index] ?? 0) | 128 >> (x & 7);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return packed;
|
|
160
|
+
}
|
|
145
161
|
function preparePngImage(bytes, compress) {
|
|
146
162
|
const raw = decodePng(bytes);
|
|
147
163
|
const colorSpace = raw.channels === 1 ? "DeviceGray" : "DeviceRGB";
|
|
164
|
+
if (compress && raw.channels === 1 && raw.alpha === void 0) {
|
|
165
|
+
const bilevel = packBilevel(raw);
|
|
166
|
+
if (bilevel !== void 0) {
|
|
167
|
+
const flate = deflate(raw.data);
|
|
168
|
+
const g4 = encodeCcittFax(bilevel, {
|
|
169
|
+
columns: raw.width,
|
|
170
|
+
rows: raw.height,
|
|
171
|
+
maxBytes: flate.length
|
|
172
|
+
});
|
|
173
|
+
if (g4 !== void 0) return {
|
|
174
|
+
dict: pdfDict(/* @__PURE__ */ new Map([
|
|
175
|
+
["Type", pdfName("XObject")],
|
|
176
|
+
["Subtype", pdfName("Image")],
|
|
177
|
+
["Width", pdfNum(raw.width)],
|
|
178
|
+
["Height", pdfNum(raw.height)],
|
|
179
|
+
["ColorSpace", pdfName("DeviceGray")],
|
|
180
|
+
["BitsPerComponent", pdfNum(1)],
|
|
181
|
+
["Filter", pdfName("CCITTFaxDecode")],
|
|
182
|
+
["DecodeParms", pdfDict(/* @__PURE__ */ new Map([
|
|
183
|
+
["K", pdfNum(-1)],
|
|
184
|
+
["Columns", pdfNum(raw.width)],
|
|
185
|
+
["Rows", pdfNum(raw.height)],
|
|
186
|
+
["BlackIs1", pdfBool(false)]
|
|
187
|
+
]))]
|
|
188
|
+
])),
|
|
189
|
+
raw: g4,
|
|
190
|
+
alpha: void 0
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
dict: pngImageDict(raw.width, raw.height, colorSpace, true),
|
|
194
|
+
raw: flate,
|
|
195
|
+
alpha: void 0
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
148
199
|
return {
|
|
149
200
|
dict: pngImageDict(raw.width, raw.height, colorSpace, compress),
|
|
150
201
|
raw: compress ? deflate(raw.data) : raw.data,
|
|
@@ -251,6 +302,24 @@ function buildNotesAnnotDict(notes) {
|
|
|
251
302
|
function xrefEntry(offset, generation, inUse) {
|
|
252
303
|
return `${offset.toString().padStart(10, "0")} ${generation.toString().padStart(5, "0")} ${inUse ? "n" : "f"} \n`;
|
|
253
304
|
}
|
|
305
|
+
function parseResidueRow(residue) {
|
|
306
|
+
const reader = new ByteReader(new TextEncoder().encode(residue.xml));
|
|
307
|
+
return parseValue(reader, () => {});
|
|
308
|
+
}
|
|
309
|
+
function objectContainsReference(obj) {
|
|
310
|
+
if (obj.kind === "ref") return true;
|
|
311
|
+
if (obj.kind === "array") return obj.items.some(objectContainsReference);
|
|
312
|
+
if (obj.kind === "dict") return [...obj.entries.values()].some(objectContainsReference);
|
|
313
|
+
if (obj.kind === "stream") return [...obj.dict.entries.values()].some(objectContainsReference);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
function restoreResidueRow(source, key) {
|
|
317
|
+
const row = source?.[key];
|
|
318
|
+
if (row === void 0) return;
|
|
319
|
+
const parsed = parseResidueRow(row);
|
|
320
|
+
if (parsed === void 0 || objectContainsReference(parsed)) return;
|
|
321
|
+
return parsed;
|
|
322
|
+
}
|
|
254
323
|
function writePdf(doc, options = {}) {
|
|
255
324
|
const compress = options.compress ?? true;
|
|
256
325
|
const registry = options.fonts;
|
|
@@ -339,10 +408,80 @@ function writePdf(doc, options = {}) {
|
|
|
339
408
|
};
|
|
340
409
|
for (let i = 0; i < countItems(doc.outline ?? []); i += 1) outlineItemNums.push(nextObjNum++);
|
|
341
410
|
}
|
|
411
|
+
const layerNumByName = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const layer of doc.layers ?? []) layerNumByName.set(layer.name, nextObjNum++);
|
|
413
|
+
const formObjectNums = [];
|
|
414
|
+
const countFieldObjects = (fields) => {
|
|
415
|
+
let n = 0;
|
|
416
|
+
for (const field of fields) n += 1 + (field.fieldType !== "group" && field.widgets.length > 1 ? field.widgets.length - 1 : 0) + countFieldObjects(field.children);
|
|
417
|
+
return n;
|
|
418
|
+
};
|
|
419
|
+
for (let i = 0; i < countFieldObjects(doc.form ?? []); i += 1) formObjectNums.push(nextObjNum++);
|
|
420
|
+
const formNumByField = /* @__PURE__ */ new Map();
|
|
421
|
+
const formExtraWidgetNums = /* @__PURE__ */ new Map();
|
|
422
|
+
let formNumCursor = 0;
|
|
423
|
+
const claimFormNums = (fields) => {
|
|
424
|
+
for (const field of fields) {
|
|
425
|
+
formNumByField.set(field, formObjectNums[formNumCursor++]);
|
|
426
|
+
if (field.fieldType !== "group" && field.widgets.length > 1) formExtraWidgetNums.set(field, field.widgets.slice(1).map(() => formObjectNums[formNumCursor++]));
|
|
427
|
+
claimFormNums(field.children);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
claimFormNums(doc.form ?? []);
|
|
431
|
+
const formNumOf = (field) => {
|
|
432
|
+
const num = formNumByField.get(field);
|
|
433
|
+
if (num === void 0) throw new Error("AcroForm field object number was not claimed -- this is a writePdf internal invariant violation");
|
|
434
|
+
return num;
|
|
435
|
+
};
|
|
436
|
+
const structElementNumById = /* @__PURE__ */ new Map();
|
|
437
|
+
const countElements = (elements) => {
|
|
438
|
+
let n = 0;
|
|
439
|
+
for (const element of elements) {
|
|
440
|
+
structElementNumById.set(element.id, nextObjNum++);
|
|
441
|
+
n += 1 + countElements(element.children);
|
|
442
|
+
}
|
|
443
|
+
return n;
|
|
444
|
+
};
|
|
445
|
+
const structElementCount = countElements(doc.structure ?? []);
|
|
446
|
+
const structRootNum = structElementCount > 0 ? nextObjNum++ : void 0;
|
|
447
|
+
const structParentTreeNum = structElementCount > 0 ? nextObjNum++ : void 0;
|
|
448
|
+
const residueXmpNum = doc.source?.xmp !== void 0 ? nextObjNum++ : void 0;
|
|
342
449
|
const objects = [];
|
|
343
450
|
const catalogEntries = [["Type", pdfName("Catalog")], ["Pages", pdfRef(pagesNum, 0)]];
|
|
344
451
|
if (attachmentsNamesNum !== void 0) catalogEntries.push(["Names", pdfRef(attachmentsNamesNum, 0)]);
|
|
345
452
|
if (outlineRootNum !== void 0) catalogEntries.push(["Outlines", pdfRef(outlineRootNum, 0)]);
|
|
453
|
+
if (layerNumByName.size > 0) {
|
|
454
|
+
const ocgRefs = [...layerNumByName.values()].map((num) => pdfRef(num, 0));
|
|
455
|
+
const visible = [];
|
|
456
|
+
const hidden = [];
|
|
457
|
+
for (const layer of doc.layers ?? []) {
|
|
458
|
+
const num = layerNumByName.get(layer.name);
|
|
459
|
+
(layer.visible ? visible : hidden).push(pdfRef(num, 0));
|
|
460
|
+
}
|
|
461
|
+
const defaultConfigEntries = [];
|
|
462
|
+
if (visible.length > 0) defaultConfigEntries.push(["ON", pdfArray(visible)]);
|
|
463
|
+
if (hidden.length > 0) defaultConfigEntries.push(["OFF", pdfArray(hidden)]);
|
|
464
|
+
catalogEntries.push(["OCProperties", pdfDict({
|
|
465
|
+
OCGs: pdfArray(ocgRefs),
|
|
466
|
+
D: pdfDict(Object.fromEntries(defaultConfigEntries))
|
|
467
|
+
})]);
|
|
468
|
+
}
|
|
469
|
+
if (doc.form !== void 0 && doc.form.length > 0) catalogEntries.push(["AcroForm", pdfDict({ Fields: pdfArray(doc.form.map((field) => pdfRef(formNumOf(field), 0))) })]);
|
|
470
|
+
if (structRootNum !== void 0) catalogEntries.push(["StructTreeRoot", pdfRef(structRootNum, 0)]);
|
|
471
|
+
if (residueXmpNum !== void 0) catalogEntries.push(["Metadata", pdfRef(residueXmpNum, 0)]);
|
|
472
|
+
const trailerIdRestore = restoreResidueRow(doc.source, "trailer-id");
|
|
473
|
+
for (const [rowKey, catalogKey] of [
|
|
474
|
+
["viewer-preferences", "ViewerPreferences"],
|
|
475
|
+
["page-mode", "PageMode"],
|
|
476
|
+
["page-layout", "PageLayout"],
|
|
477
|
+
["output-intents", "OutputIntents"],
|
|
478
|
+
["piece-info", "PieceInfo"],
|
|
479
|
+
["legal", "Legal"],
|
|
480
|
+
["collection", "Collection"]
|
|
481
|
+
]) {
|
|
482
|
+
const restored = restoreResidueRow(doc.source, rowKey);
|
|
483
|
+
if (restored !== void 0) catalogEntries.push([catalogKey, restored]);
|
|
484
|
+
}
|
|
346
485
|
objects.push({
|
|
347
486
|
num: catalogNum,
|
|
348
487
|
value: pdfDict(Object.fromEntries(catalogEntries))
|
|
@@ -426,6 +565,118 @@ function writePdf(doc, options = {}) {
|
|
|
426
565
|
})
|
|
427
566
|
});
|
|
428
567
|
}
|
|
568
|
+
for (const layer of doc.layers ?? []) {
|
|
569
|
+
const num = layerNumByName.get(layer.name);
|
|
570
|
+
objects.push({
|
|
571
|
+
num,
|
|
572
|
+
value: pdfDict({
|
|
573
|
+
Type: pdfName("OCG"),
|
|
574
|
+
Name: pdfLiteralString(new TextEncoder().encode(layer.name))
|
|
575
|
+
})
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
const widgetRectArray = (widget) => pdfArray([
|
|
579
|
+
widget.xPt,
|
|
580
|
+
widget.yPt,
|
|
581
|
+
widget.xPt + widget.widthPt,
|
|
582
|
+
widget.yPt + widget.heightPt
|
|
583
|
+
].map((n) => pdfNum(n)));
|
|
584
|
+
const widgetDict = (widget) => pdfDict({
|
|
585
|
+
Subtype: pdfName("Widget"),
|
|
586
|
+
Rect: widgetRectArray(widget),
|
|
587
|
+
P: pdfRef(pageAllocs[widget.pageIndex].pageNum, 0)
|
|
588
|
+
});
|
|
589
|
+
const FIELD_TYPE_PDF_NAME = {
|
|
590
|
+
text: "Tx",
|
|
591
|
+
checkbox: "Btn",
|
|
592
|
+
radio: "Btn",
|
|
593
|
+
button: "Btn",
|
|
594
|
+
listbox: "Ch",
|
|
595
|
+
combobox: "Ch",
|
|
596
|
+
signature: "Sig"
|
|
597
|
+
};
|
|
598
|
+
const emitFormFieldObjects = (fields, parentName) => {
|
|
599
|
+
for (const field of fields) {
|
|
600
|
+
const ownName = parentName.length > 0 && field.name.startsWith(`${parentName}.`) ? field.name.slice(parentName.length + 1) : field.name;
|
|
601
|
+
const entries = [];
|
|
602
|
+
if (ownName.length > 0) entries.push(["T", pdfLiteralString(new TextEncoder().encode(ownName))]);
|
|
603
|
+
if (field.alias !== void 0) entries.push(["TU", pdfLiteralString(new TextEncoder().encode(field.alias))]);
|
|
604
|
+
if (field.fieldType === "group") entries.push(["Kids", pdfArray(field.children.map((child) => pdfRef(formNumOf(child), 0)))]);
|
|
605
|
+
else {
|
|
606
|
+
entries.push(["FT", pdfName(FIELD_TYPE_PDF_NAME[field.fieldType])]);
|
|
607
|
+
const FLAG_READ_ONLY = 1;
|
|
608
|
+
const FLAG_PUSHBUTTON = 4;
|
|
609
|
+
const FLAG_RADIO = 32768;
|
|
610
|
+
const FLAG_COMBO = 131072;
|
|
611
|
+
let flags = 0;
|
|
612
|
+
if (field.readOnly === true) flags |= FLAG_READ_ONLY;
|
|
613
|
+
if (field.fieldType === "button") flags |= FLAG_PUSHBUTTON;
|
|
614
|
+
if (field.fieldType === "radio") flags |= FLAG_RADIO;
|
|
615
|
+
if (field.fieldType === "combobox") flags |= FLAG_COMBO;
|
|
616
|
+
if (flags !== 0) entries.push(["Ff", pdfNum(flags)]);
|
|
617
|
+
if (field.fieldType === "text" || field.fieldType === "listbox" || field.fieldType === "combobox") {
|
|
618
|
+
if (field.value !== void 0) entries.push(["V", pdfLiteralString(new TextEncoder().encode(field.value))]);
|
|
619
|
+
} else if (field.fieldType === "checkbox" || field.fieldType === "radio") entries.push(["V", pdfName(field.checked === false || field.value === void 0 ? field.value ?? (field.checked === true ? "Yes" : "Off") : field.value)]);
|
|
620
|
+
if (field.options !== void 0) entries.push(["Opt", pdfArray(field.options.map((option) => pdfLiteralString(new TextEncoder().encode(option))))]);
|
|
621
|
+
const firstWidget = field.widgets[0];
|
|
622
|
+
if (field.widgets.length === 1 && firstWidget !== void 0) {
|
|
623
|
+
entries.push(["Subtype", pdfName("Widget")]);
|
|
624
|
+
entries.push(["Rect", widgetRectArray(firstWidget)]);
|
|
625
|
+
entries.push(["P", pdfRef(pageAllocs[firstWidget.pageIndex].pageNum, 0)]);
|
|
626
|
+
} else if (field.widgets.length > 1 && firstWidget !== void 0) {
|
|
627
|
+
const extraNums = formExtraWidgetNums.get(field) ?? [];
|
|
628
|
+
entries.push(["Kids", pdfArray([widgetDict(firstWidget), ...extraNums.map((num) => pdfRef(num, 0))])]);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
objects.push({
|
|
632
|
+
num: formNumOf(field),
|
|
633
|
+
value: pdfDict(Object.fromEntries(entries))
|
|
634
|
+
});
|
|
635
|
+
const extraNums = formExtraWidgetNums.get(field) ?? [];
|
|
636
|
+
for (const [index, num] of extraNums.entries()) {
|
|
637
|
+
const widget = field.widgets[index + 1];
|
|
638
|
+
objects.push({
|
|
639
|
+
num,
|
|
640
|
+
value: widgetDict(widget)
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
emitFormFieldObjects(field.children, field.name);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
emitFormFieldObjects(doc.form ?? [], "");
|
|
647
|
+
if (structRootNum !== void 0 && structParentTreeNum !== void 0) {
|
|
648
|
+
const emitStructureElement = (element, parentNum) => {
|
|
649
|
+
const entries = [
|
|
650
|
+
["Type", pdfName("StructElem")],
|
|
651
|
+
["S", pdfName(element.type)],
|
|
652
|
+
["P", pdfRef(parentNum, 0)]
|
|
653
|
+
];
|
|
654
|
+
if (element.title !== void 0) entries.push(["T", pdfLiteralString(new TextEncoder().encode(element.title))]);
|
|
655
|
+
if (element.language !== void 0) entries.push(["Lang", pdfLiteralString(new TextEncoder().encode(element.language))]);
|
|
656
|
+
if (element.alt !== void 0) entries.push(["Alt", pdfLiteralString(new TextEncoder().encode(element.alt))]);
|
|
657
|
+
if (element.actualText !== void 0) entries.push(["ActualText", pdfLiteralString(new TextEncoder().encode(element.actualText))]);
|
|
658
|
+
if (element.children.length > 0) entries.push(["K", pdfArray(element.children.map((child) => {
|
|
659
|
+
const num = structElementNumById.get(child.id);
|
|
660
|
+
if (num === void 0) throw new Error(`structure element "${child.id}" was not allocated -- this is a writePdf internal invariant violation`);
|
|
661
|
+
return pdfRef(num, 0);
|
|
662
|
+
}))]);
|
|
663
|
+
const ownNum = structElementNumById.get(element.id);
|
|
664
|
+
if (ownNum === void 0) throw new Error(`structure element "${element.id}" was not allocated -- this is a writePdf internal invariant violation`);
|
|
665
|
+
objects.push({
|
|
666
|
+
num: ownNum,
|
|
667
|
+
value: pdfDict(Object.fromEntries(entries))
|
|
668
|
+
});
|
|
669
|
+
for (const child of element.children) emitStructureElement(child, ownNum);
|
|
670
|
+
};
|
|
671
|
+
for (const element of doc.structure ?? []) emitStructureElement(element, structRootNum);
|
|
672
|
+
}
|
|
673
|
+
if (residueXmpNum !== void 0 && doc.source?.xmp !== void 0) objects.push({
|
|
674
|
+
num: residueXmpNum,
|
|
675
|
+
value: pdfStream(pdfDict({
|
|
676
|
+
Type: pdfName("Metadata"),
|
|
677
|
+
Subtype: pdfName("XML")
|
|
678
|
+
}), new TextEncoder().encode(doc.source.xmp.xml))
|
|
679
|
+
});
|
|
429
680
|
for (const [standardName, alloc] of fontAllocs) {
|
|
430
681
|
const { font, descriptor } = buildFontObjects(standardName, pdfRef(alloc.descNum, 0));
|
|
431
682
|
objects.push({
|
|
@@ -520,6 +771,7 @@ function writePdf(doc, options = {}) {
|
|
|
520
771
|
}
|
|
521
772
|
if (imageAllocs.size > 0) resourceEntries.set("XObject", pdfDict(new Map([...imageAllocs.values()].map((alloc) => [alloc.resourceName, pdfRef(alloc.imageNum, 0)]))));
|
|
522
773
|
const resourcesDict = pdfDict(resourceEntries);
|
|
774
|
+
const markedStructureByPage = /* @__PURE__ */ new Map();
|
|
523
775
|
const formulasByPage = /* @__PURE__ */ new Map();
|
|
524
776
|
for (const formula of formulas) {
|
|
525
777
|
const forPage = formulasByPage.get(formula.pageIndex);
|
|
@@ -556,7 +808,14 @@ function writePdf(doc, options = {}) {
|
|
|
556
808
|
doc.pages.forEach((page, pageIndex) => {
|
|
557
809
|
throwIfAborted(options.signal);
|
|
558
810
|
const { pageNum, contentsNum } = pageAllocs[pageIndex];
|
|
559
|
-
|
|
811
|
+
let pageMcid = 0;
|
|
812
|
+
const pageContext = {
|
|
813
|
+
...context,
|
|
814
|
+
nextMcid: () => pageMcid++,
|
|
815
|
+
layerObjectNumberOf: (name) => layerNumByName.get(name)
|
|
816
|
+
};
|
|
817
|
+
const { bytes: contentBytes, substitutions, missingGlyphs, markedStructure } = writeContentStream(page.items, pageContext);
|
|
818
|
+
if (markedStructure.length > 0) markedStructureByPage.set(pageIndex, [...markedStructure]);
|
|
560
819
|
for (const substitution of substitutions) options.onSubstitution?.(substitution, { pageIndex });
|
|
561
820
|
for (const missing of missingGlyphs) options.onMissingGlyph?.(missing, { pageIndex });
|
|
562
821
|
const pageFormulas = formulasByPage.get(pageIndex);
|
|
@@ -586,11 +845,41 @@ function writePdf(doc, options = {}) {
|
|
|
586
845
|
["Contents", pdfRef(contentsNum, 0)]
|
|
587
846
|
]);
|
|
588
847
|
if (annots.length > 0) pageEntries.set("Annots", pdfArray(annots));
|
|
848
|
+
if ((markedStructureByPage.get(pageIndex) ?? []).length > 0) pageEntries.set("StructParents", pdfNum(pageIndex));
|
|
589
849
|
objects.push({
|
|
590
850
|
num: pageNum,
|
|
591
851
|
value: pdfDict(pageEntries)
|
|
592
852
|
});
|
|
593
853
|
});
|
|
854
|
+
if (structRootNum !== void 0 && structParentTreeNum !== void 0) {
|
|
855
|
+
const nums = [];
|
|
856
|
+
for (const [pageIndex, marks] of markedStructureByPage) {
|
|
857
|
+
const maxMcid = Math.max(...marks.map((mark) => mark.mcid));
|
|
858
|
+
const byMcid = Array.from({ length: maxMcid + 1 }, () => pdfNull());
|
|
859
|
+
for (const mark of marks) {
|
|
860
|
+
const elementNum = structElementNumById.get(mark.structureId);
|
|
861
|
+
if (elementNum !== void 0) byMcid[mark.mcid] = pdfRef(elementNum, 0);
|
|
862
|
+
}
|
|
863
|
+
nums.push(pdfNum(pageIndex), pdfArray(byMcid));
|
|
864
|
+
}
|
|
865
|
+
objects.push({
|
|
866
|
+
num: structParentTreeNum,
|
|
867
|
+
value: pdfDict({ Nums: pdfArray(nums) })
|
|
868
|
+
});
|
|
869
|
+
const rootEntries = [
|
|
870
|
+
["Type", pdfName("StructTreeRoot")],
|
|
871
|
+
["K", pdfArray((doc.structure ?? []).map((element) => {
|
|
872
|
+
const num = structElementNumById.get(element.id);
|
|
873
|
+
if (num === void 0) throw new Error(`structure element "${element.id}" was not allocated -- this is a writePdf internal invariant violation`);
|
|
874
|
+
return pdfRef(num, 0);
|
|
875
|
+
}))],
|
|
876
|
+
["ParentTree", pdfRef(structParentTreeNum, 0)]
|
|
877
|
+
];
|
|
878
|
+
objects.push({
|
|
879
|
+
num: structRootNum,
|
|
880
|
+
value: pdfDict(Object.fromEntries(rootEntries))
|
|
881
|
+
});
|
|
882
|
+
}
|
|
594
883
|
let fileId;
|
|
595
884
|
let encryptedObjects = objects;
|
|
596
885
|
if (options.encryption !== void 0 && encryptDictNum !== void 0) {
|
|
@@ -632,7 +921,7 @@ function writePdf(doc, options = {}) {
|
|
|
632
921
|
if (fileId !== void 0 && encryptDictNum !== void 0) {
|
|
633
922
|
trailerEntries.set("ID", pdfArray([pdfHexString(fileId), pdfHexString(fileId)]));
|
|
634
923
|
trailerEntries.set("Encrypt", pdfRef(encryptDictNum, 0));
|
|
635
|
-
}
|
|
924
|
+
} else if (trailerIdRestore !== void 0) trailerEntries.set("ID", trailerIdRestore);
|
|
636
925
|
writer.writeAscii("trailer\n");
|
|
637
926
|
writeObject(writer, pdfDict(trailerEntries));
|
|
638
927
|
writer.writeAscii("\nstartxref\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pdf-codec",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Hand-written, dependency-minimal PDF codec: parses arbitrary real-world PDFs and generates new ones, built on its own codec-owned LayoutDocument item model and Zod 4 codecs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|