pdf-codec 4.1.0 → 4.3.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.
@@ -0,0 +1,224 @@
1
+ import { hasBytes, i16, sfntTableBytes, u16, u32 } from "./sfnt.js";
2
+ import { parseCoverage } from "./ot-layout-common.js";
3
+ //#region src/gsub-table.ts
4
+ const LIGATURE_FEATURE_TAGS = ["liga", "rlig"];
5
+ const GSUB_HEADER_SIZE = 10;
6
+ const SCRIPT_RECORD_SIZE = 6;
7
+ const FEATURE_RECORD_SIZE = 6;
8
+ const LANG_SYS_HEADER_SIZE = 6;
9
+ const FEATURE_HEADER_SIZE = 4;
10
+ const LOOKUP_HEADER_SIZE = 6;
11
+ const LOOKUP_TYPE_SINGLE_SUBST = 1;
12
+ const LOOKUP_TYPE_LIGATURE_SUBST = 4;
13
+ const LOOKUP_TYPE_EXTENSION_SUBST = 7;
14
+ const EXTENSION_SUBST_HEADER_SIZE = 8;
15
+ const PREFERRED_SCRIPT_TAGS = ["latn", "DFLT"];
16
+ function readTag(bytes, offset) {
17
+ return String.fromCharCode(u16(bytes, offset) >> 8, u16(bytes, offset) & 255, u16(bytes, offset + 2) >> 8, u16(bytes, offset + 2) & 255);
18
+ }
19
+ const SINGLE_SUBST_FORMAT_1_SIZE = 6;
20
+ function parseSingleSubstFormat1(bytes, subtableOffset) {
21
+ if (!hasBytes(bytes, subtableOffset, SINGLE_SUBST_FORMAT_1_SIZE)) return;
22
+ const coverage = parseCoverage(bytes, subtableOffset + u16(bytes, subtableOffset + 2));
23
+ if (coverage === void 0) return;
24
+ const delta = i16(bytes, subtableOffset + 4);
25
+ return {
26
+ kind: "single",
27
+ lookup: (glyphId) => {
28
+ if (coverage.coverageIndex(glyphId) === void 0) return;
29
+ return glyphId + delta;
30
+ }
31
+ };
32
+ }
33
+ const SINGLE_SUBST_FORMAT_2_HEADER_SIZE = 6;
34
+ function parseSingleSubstFormat2(bytes, subtableOffset) {
35
+ if (!hasBytes(bytes, subtableOffset, SINGLE_SUBST_FORMAT_2_HEADER_SIZE)) return;
36
+ const coverage = parseCoverage(bytes, subtableOffset + u16(bytes, subtableOffset + 2));
37
+ if (coverage === void 0) return;
38
+ const glyphCount = u16(bytes, subtableOffset + 4);
39
+ const substitutesOffset = subtableOffset + SINGLE_SUBST_FORMAT_2_HEADER_SIZE;
40
+ if (!hasBytes(bytes, substitutesOffset, glyphCount * 2)) return;
41
+ return {
42
+ kind: "single",
43
+ lookup: (glyphId) => {
44
+ const coverageIndex = coverage.coverageIndex(glyphId);
45
+ if (coverageIndex === void 0 || coverageIndex >= glyphCount) return;
46
+ return u16(bytes, substitutesOffset + coverageIndex * 2);
47
+ }
48
+ };
49
+ }
50
+ const LIGATURE_SUBST_FORMAT_1_HEADER_SIZE = 6;
51
+ const LIGATURE_SET_HEADER_SIZE = 2;
52
+ const LIGATURE_HEADER_PREFIX_SIZE = 4;
53
+ function parseLigatureSubstFormat1(bytes, subtableOffset) {
54
+ if (!hasBytes(bytes, subtableOffset, LIGATURE_SUBST_FORMAT_1_HEADER_SIZE)) return;
55
+ const coverage = parseCoverage(bytes, subtableOffset + u16(bytes, subtableOffset + 2));
56
+ if (coverage === void 0) return;
57
+ const ligSetCount = u16(bytes, subtableOffset + 4);
58
+ const ligSetOffsetsOffset = subtableOffset + LIGATURE_SUBST_FORMAT_1_HEADER_SIZE;
59
+ if (!hasBytes(bytes, ligSetOffsetsOffset, ligSetCount * 2)) return;
60
+ return {
61
+ kind: "ligature",
62
+ match: (glyphIds, index) => {
63
+ const coverageIndex = coverage.coverageIndex(glyphIds[index]);
64
+ if (coverageIndex === void 0 || coverageIndex >= ligSetCount) return;
65
+ const ligSetOffset = subtableOffset + u16(bytes, ligSetOffsetsOffset + coverageIndex * 2);
66
+ if (!hasBytes(bytes, ligSetOffset, LIGATURE_SET_HEADER_SIZE)) return;
67
+ const ligatureCount = u16(bytes, ligSetOffset);
68
+ const ligatureOffsetsOffset = ligSetOffset + LIGATURE_SET_HEADER_SIZE;
69
+ if (!hasBytes(bytes, ligatureOffsetsOffset, ligatureCount * 2)) return;
70
+ for (let i = 0; i < ligatureCount; i++) {
71
+ const ligatureOffset = ligSetOffset + u16(bytes, ligatureOffsetsOffset + i * 2);
72
+ if (!hasBytes(bytes, ligatureOffset, LIGATURE_HEADER_PREFIX_SIZE)) continue;
73
+ const ligatureGlyph = u16(bytes, ligatureOffset);
74
+ const componentCount = u16(bytes, ligatureOffset + 2);
75
+ if (componentCount === 0) continue;
76
+ const componentsOffset = ligatureOffset + LIGATURE_HEADER_PREFIX_SIZE;
77
+ if (!hasBytes(bytes, componentsOffset, (componentCount - 1) * 2)) continue;
78
+ if (index + componentCount > glyphIds.length) continue;
79
+ let matches = true;
80
+ for (let c = 1; c < componentCount; c++) if (glyphIds[index + c] !== u16(bytes, componentsOffset + (c - 1) * 2)) {
81
+ matches = false;
82
+ break;
83
+ }
84
+ if (matches) return {
85
+ ligatureGlyph,
86
+ componentCount
87
+ };
88
+ }
89
+ }
90
+ };
91
+ }
92
+ function parseSubtable(bytes, lookupType, subtableOffset) {
93
+ if (lookupType === LOOKUP_TYPE_EXTENSION_SUBST) {
94
+ if (!hasBytes(bytes, subtableOffset, EXTENSION_SUBST_HEADER_SIZE) || u16(bytes, subtableOffset) !== 1) return;
95
+ const extensionLookupType = u16(bytes, subtableOffset + 2);
96
+ if (extensionLookupType === LOOKUP_TYPE_EXTENSION_SUBST) return;
97
+ return parseSubtable(bytes, extensionLookupType, subtableOffset + u32(bytes, subtableOffset + 4));
98
+ }
99
+ if (lookupType === LOOKUP_TYPE_SINGLE_SUBST) {
100
+ if (!hasBytes(bytes, subtableOffset, 2)) return;
101
+ const substFormat = u16(bytes, subtableOffset);
102
+ if (substFormat === 1) return parseSingleSubstFormat1(bytes, subtableOffset);
103
+ if (substFormat === 2) return parseSingleSubstFormat2(bytes, subtableOffset);
104
+ return;
105
+ }
106
+ if (lookupType === LOOKUP_TYPE_LIGATURE_SUBST) {
107
+ if (!hasBytes(bytes, subtableOffset, 2)) return;
108
+ if (u16(bytes, subtableOffset) !== 1) return;
109
+ return parseLigatureSubstFormat1(bytes, subtableOffset);
110
+ }
111
+ }
112
+ function findScriptOffset(bytes, scriptListOffset) {
113
+ if (!hasBytes(bytes, scriptListOffset, 2)) return;
114
+ const scriptCount = u16(bytes, scriptListOffset);
115
+ const recordsOffset = scriptListOffset + 2;
116
+ if (!hasBytes(bytes, recordsOffset, scriptCount * SCRIPT_RECORD_SIZE) || scriptCount === 0) return;
117
+ for (const wanted of PREFERRED_SCRIPT_TAGS) for (let i = 0; i < scriptCount; i++) {
118
+ const recordOffset = recordsOffset + i * SCRIPT_RECORD_SIZE;
119
+ if (readTag(bytes, recordOffset) === wanted) return scriptListOffset + u16(bytes, recordOffset + 4);
120
+ }
121
+ return scriptListOffset + u16(bytes, recordsOffset + 4);
122
+ }
123
+ function parseDefaultLangSysFeatureIndices(bytes, scriptOffset) {
124
+ if (!hasBytes(bytes, scriptOffset, 2)) return [];
125
+ const defaultLangSysOffset = u16(bytes, scriptOffset);
126
+ if (defaultLangSysOffset === 0) return [];
127
+ const langSysOffset = scriptOffset + defaultLangSysOffset;
128
+ if (!hasBytes(bytes, langSysOffset, LANG_SYS_HEADER_SIZE)) return [];
129
+ const featureIndexCount = u16(bytes, langSysOffset + 4);
130
+ const indicesOffset = langSysOffset + LANG_SYS_HEADER_SIZE;
131
+ if (!hasBytes(bytes, indicesOffset, featureIndexCount * 2)) return [];
132
+ const indices = [];
133
+ for (let i = 0; i < featureIndexCount; i++) indices.push(u16(bytes, indicesOffset + i * 2));
134
+ return indices;
135
+ }
136
+ function collectLigatureLookupIndices(bytes, featureListOffset, featureIndices) {
137
+ if (!hasBytes(bytes, featureListOffset, 2)) return [];
138
+ const featureCount = u16(bytes, featureListOffset);
139
+ const recordsOffset = featureListOffset + 2;
140
+ if (!hasBytes(bytes, recordsOffset, featureCount * FEATURE_RECORD_SIZE)) return [];
141
+ const lookupIndices = [];
142
+ const seen = /* @__PURE__ */ new Set();
143
+ for (const featureIndex of featureIndices) {
144
+ if (featureIndex >= featureCount) continue;
145
+ const recordOffset = recordsOffset + featureIndex * FEATURE_RECORD_SIZE;
146
+ const tag = readTag(bytes, recordOffset);
147
+ if (!LIGATURE_FEATURE_TAGS.includes(tag)) continue;
148
+ const featureOffset = featureListOffset + u16(bytes, recordOffset + 4);
149
+ if (!hasBytes(bytes, featureOffset, FEATURE_HEADER_SIZE)) continue;
150
+ const lookupIndexCount = u16(bytes, featureOffset + 2);
151
+ const indicesOffset = featureOffset + FEATURE_HEADER_SIZE;
152
+ if (!hasBytes(bytes, indicesOffset, lookupIndexCount * 2)) continue;
153
+ for (let i = 0; i < lookupIndexCount; i++) {
154
+ const lookupIndex = u16(bytes, indicesOffset + i * 2);
155
+ if (!seen.has(lookupIndex)) {
156
+ seen.add(lookupIndex);
157
+ lookupIndices.push(lookupIndex);
158
+ }
159
+ }
160
+ }
161
+ return lookupIndices;
162
+ }
163
+ function buildGsubShaper(font) {
164
+ const bytes = sfntTableBytes(font, "GSUB");
165
+ if (bytes === void 0 || !hasBytes(bytes, 0, GSUB_HEADER_SIZE) || u16(bytes, 0) !== 1) return;
166
+ const scriptOffset = findScriptOffset(bytes, u16(bytes, 4));
167
+ if (scriptOffset === void 0) return;
168
+ const lookupIndices = collectLigatureLookupIndices(bytes, u16(bytes, 6), parseDefaultLangSysFeatureIndices(bytes, scriptOffset));
169
+ if (lookupIndices.length === 0) return;
170
+ const lookupListOffset = u16(bytes, 8);
171
+ if (!hasBytes(bytes, lookupListOffset, 2)) return;
172
+ const lookupCount = u16(bytes, lookupListOffset);
173
+ const lookupOffsetsOffset = lookupListOffset + 2;
174
+ if (!hasBytes(bytes, lookupOffsetsOffset, lookupCount * 2)) return;
175
+ const subtables = [];
176
+ for (const lookupIndex of lookupIndices) {
177
+ if (lookupIndex >= lookupCount) continue;
178
+ const lookupOffset = lookupListOffset + u16(bytes, lookupOffsetsOffset + lookupIndex * 2);
179
+ if (!hasBytes(bytes, lookupOffset, LOOKUP_HEADER_SIZE)) continue;
180
+ if (u16(bytes, lookupOffset + 2) !== 0) continue;
181
+ const lookupType = u16(bytes, lookupOffset);
182
+ const subTableCount = u16(bytes, lookupOffset + 4);
183
+ const subtableOffsetsOffset = lookupOffset + LOOKUP_HEADER_SIZE;
184
+ if (!hasBytes(bytes, subtableOffsetsOffset, subTableCount * 2)) continue;
185
+ for (let i = 0; i < subTableCount; i++) {
186
+ const subtable = parseSubtable(bytes, lookupType, lookupOffset + u16(bytes, subtableOffsetsOffset + i * 2));
187
+ if (subtable !== void 0) subtables.push(subtable);
188
+ }
189
+ }
190
+ if (subtables.length === 0) return;
191
+ return (glyphIds) => {
192
+ const shaped = [];
193
+ const spans = [];
194
+ let index = 0;
195
+ outer: while (index < glyphIds.length) {
196
+ for (const subtable of subtables) if (subtable.kind === "single") {
197
+ const substitute = subtable.lookup(glyphIds[index]);
198
+ if (substitute !== void 0) {
199
+ shaped.push(substitute);
200
+ spans.push(1);
201
+ index += 1;
202
+ continue outer;
203
+ }
204
+ } else {
205
+ const match = subtable.match(glyphIds, index);
206
+ if (match !== void 0) {
207
+ shaped.push(match.ligatureGlyph);
208
+ spans.push(match.componentCount);
209
+ index += match.componentCount;
210
+ continue outer;
211
+ }
212
+ }
213
+ shaped.push(glyphIds[index]);
214
+ spans.push(1);
215
+ index += 1;
216
+ }
217
+ return {
218
+ glyphIds: shaped,
219
+ spans
220
+ };
221
+ };
222
+ }
223
+ //#endregion
224
+ export { buildGsubShaper };
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@ import { NOOP_DIAGNOSTIC_SINK, PdfDiagnostic, PdfDiagnosticSeverity, PdfDiagnost
3
3
  import { LAYOUT_FORMAT_VERSION, LayoutAnnotation, LayoutAnnotationQuad, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachment, LayoutAttachmentSchema, LayoutDestination, LayoutDestinationSchema, LayoutDestinationTarget, LayoutDestinationTargetSchema, LayoutDocument, LayoutDocumentSchema, LayoutEllipse, LayoutEllipseSchema, LayoutFormField, LayoutFormFieldSchema, LayoutFormWidget, LayoutFormWidgetSchema, LayoutImage, LayoutImageAsset, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLink, LayoutInternalLinkSchema, LayoutItem, LayoutItemSchema, LayoutLayer, LayoutLayerSchema, LayoutLine, LayoutLineSchema, LayoutLink, LayoutLinkSchema, LayoutOutlineItem, LayoutOutlineItemSchema, LayoutPage, LayoutPageSchema, LayoutPath, LayoutPathSchema, LayoutPathSegment, LayoutPathSegmentSchema, LayoutRect, LayoutRectSchema, LayoutStructureElement, LayoutStructureElementSchema, LayoutSubpath, LayoutSubpathSchema, LayoutText, LayoutTextSchema } from "./layout.cjs";
4
4
  import { t as GlyphInkBounds } from "./glyph-bounds-BV_Z40KS.cjs";
5
5
  import { PdfBytesSchema, pdfCodec } from "./codec.cjs";
6
- import { n as EmbeddedFaceMetrics, r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-BU6Jgcof.cjs";
6
+ import { n as EmbeddedFaceMetrics, r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-oc65VGlr.cjs";
7
7
  import { WinAnsiSubstitution } from "./winansi.cjs";
8
8
  import { PdfEncryptionError, PdfEncryptionOptions, PdfEncryptionPermissions, PdfEncryptionScheme } from "./encrypt-write.cjs";
9
9
  import { FontFace, FontFaceParseError, readFontFace } from "./font-face.cjs";
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { NOOP_DIAGNOSTIC_SINK, PdfDiagnostic, PdfDiagnosticSeverity, PdfDiagnost
3
3
  import { LAYOUT_FORMAT_VERSION, LayoutAnnotation, LayoutAnnotationQuad, LayoutAnnotationQuadSchema, LayoutAnnotationSchema, LayoutAttachment, LayoutAttachmentSchema, LayoutDestination, LayoutDestinationSchema, LayoutDestinationTarget, LayoutDestinationTargetSchema, LayoutDocument, LayoutDocumentSchema, LayoutEllipse, LayoutEllipseSchema, LayoutFormField, LayoutFormFieldSchema, LayoutFormWidget, LayoutFormWidgetSchema, LayoutImage, LayoutImageAsset, LayoutImageAssetSchema, LayoutImageSchema, LayoutInternalLink, LayoutInternalLinkSchema, LayoutItem, LayoutItemSchema, LayoutLayer, LayoutLayerSchema, LayoutLine, LayoutLineSchema, LayoutLink, LayoutLinkSchema, LayoutOutlineItem, LayoutOutlineItemSchema, LayoutPage, LayoutPageSchema, LayoutPath, LayoutPathSchema, LayoutPathSegment, LayoutPathSegmentSchema, LayoutRect, LayoutRectSchema, LayoutStructureElement, LayoutStructureElementSchema, LayoutSubpath, LayoutSubpathSchema, LayoutText, LayoutTextSchema } from "./layout.js";
4
4
  import { t as GlyphInkBounds } from "./glyph-bounds-BV_Z40KS.js";
5
5
  import { PdfBytesSchema, pdfCodec } from "./codec.js";
6
- import { n as EmbeddedFaceMetrics, r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-fREdbxnr.js";
6
+ import { n as EmbeddedFaceMetrics, r as EmbeddedFaceSubstitution, t as EmbeddedFace } from "./embedded-font-B4p9a3O3.js";
7
7
  import { WinAnsiSubstitution } from "./winansi.js";
8
8
  import { PdfEncryptionError, PdfEncryptionOptions, PdfEncryptionPermissions, PdfEncryptionScheme } from "./encrypt-write.js";
9
9
  import { FontFace, FontFaceParseError, readFontFace } from "./font-face.js";
@@ -40,7 +40,7 @@ function buildFontFileStream(font, compress) {
40
40
  }
41
41
  function toUnicodeEntries(usedGlyphs) {
42
42
  const entries = /* @__PURE__ */ new Map();
43
- for (const [glyphId, codePoint] of usedGlyphs) if (codePoint !== void 0) entries.set(glyphId, codePoint);
43
+ for (const [glyphId, codePoint] of usedGlyphs) if (codePoint !== void 0) entries.set(glyphId, [codePoint]);
44
44
  return entries;
45
45
  }
46
46
  function buildMathFontObjects(font, usedGlyphs, refs, compress) {
@@ -39,7 +39,7 @@ function buildFontFileStream(font, compress) {
39
39
  }
40
40
  function toUnicodeEntries(usedGlyphs) {
41
41
  const entries = /* @__PURE__ */ new Map();
42
- for (const [glyphId, codePoint] of usedGlyphs) if (codePoint !== void 0) entries.set(glyphId, codePoint);
42
+ for (const [glyphId, codePoint] of usedGlyphs) if (codePoint !== void 0) entries.set(glyphId, [codePoint]);
43
43
  return entries;
44
44
  }
45
45
  function buildMathFontObjects(font, usedGlyphs, refs, compress) {
@@ -71,7 +71,7 @@ function buildPostStub(font) {
71
71
  if (sourcePost !== void 0 && require_sfnt.hasBytes(sourcePost, POST_METRICS_OFFSET, POST_METRICS_SIZE)) post.set(sourcePost.subarray(POST_METRICS_OFFSET, 16), POST_METRICS_OFFSET);
72
72
  return post;
73
73
  }
74
- function collectGlyphIds(glyf, cmap, numGlyphs, codePoints) {
74
+ function collectGlyphIds(glyf, cmap, numGlyphs, codePoints, extraGlyphIds) {
75
75
  const used = /* @__PURE__ */ new Set([0]);
76
76
  const unmapped = /* @__PURE__ */ new Set();
77
77
  const pending = [0];
@@ -87,6 +87,13 @@ function collectGlyphIds(glyf, cmap, numGlyphs, codePoints) {
87
87
  pending.push(glyphId);
88
88
  }
89
89
  }
90
+ for (const glyphId of extraGlyphIds) {
91
+ if (glyphId >= numGlyphs) return;
92
+ if (!used.has(glyphId)) {
93
+ used.add(glyphId);
94
+ pending.push(glyphId);
95
+ }
96
+ }
90
97
  while (pending.length > 0) {
91
98
  const glyphId = pending.pop();
92
99
  if (glyf.glyphBytes(glyphId) === void 0) return;
@@ -107,7 +114,7 @@ function collectGlyphIds(glyf, cmap, numGlyphs, codePoints) {
107
114
  unmapped: [...unmapped].sort((a, b) => a - b)
108
115
  };
109
116
  }
110
- function subsetSfnt(font, codePoints) {
117
+ function subsetSfnt(font, codePoints, extraGlyphIds = []) {
111
118
  const head = require_font_tables.parseHead(font);
112
119
  const maxp = require_font_tables.parseMaxp(font);
113
120
  const sourceHead = require_sfnt.sfntTableBytes(font, "head");
@@ -124,7 +131,7 @@ function subsetSfnt(font, codePoints) {
124
131
  });
125
132
  const cmap = require_cmap_table.buildCmapLookup(font);
126
133
  if (glyf === void 0 || cmap === void 0) return;
127
- const collected = collectGlyphIds(glyf, cmap, maxp.numGlyphs, codePoints);
134
+ const collected = collectGlyphIds(glyf, cmap, maxp.numGlyphs, codePoints, extraGlyphIds);
128
135
  if (collected === void 0) return;
129
136
  const glyphIds = [...collected.used].sort((a, b) => a - b);
130
137
  const numGlyphs = glyphIds[glyphIds.length - 1] + 1;
@@ -6,6 +6,6 @@ interface SfntSubsetResult {
6
6
  readonly glyphIds: readonly number[];
7
7
  readonly unmappedCodePoints: readonly number[];
8
8
  }
9
- declare function subsetSfnt(font: SfntFont, codePoints: Iterable<number>): SfntSubsetResult | undefined;
9
+ declare function subsetSfnt(font: SfntFont, codePoints: Iterable<number>, extraGlyphIds?: Iterable<number>): SfntSubsetResult | undefined;
10
10
  //#endregion
11
11
  export { SfntSubsetResult, subsetSfnt };
@@ -6,6 +6,6 @@ interface SfntSubsetResult {
6
6
  readonly glyphIds: readonly number[];
7
7
  readonly unmappedCodePoints: readonly number[];
8
8
  }
9
- declare function subsetSfnt(font: SfntFont, codePoints: Iterable<number>): SfntSubsetResult | undefined;
9
+ declare function subsetSfnt(font: SfntFont, codePoints: Iterable<number>, extraGlyphIds?: Iterable<number>): SfntSubsetResult | undefined;
10
10
  //#endregion
11
11
  export { SfntSubsetResult, subsetSfnt };
@@ -70,7 +70,7 @@ function buildPostStub(font) {
70
70
  if (sourcePost !== void 0 && hasBytes(sourcePost, POST_METRICS_OFFSET, POST_METRICS_SIZE)) post.set(sourcePost.subarray(POST_METRICS_OFFSET, 16), POST_METRICS_OFFSET);
71
71
  return post;
72
72
  }
73
- function collectGlyphIds(glyf, cmap, numGlyphs, codePoints) {
73
+ function collectGlyphIds(glyf, cmap, numGlyphs, codePoints, extraGlyphIds) {
74
74
  const used = /* @__PURE__ */ new Set([0]);
75
75
  const unmapped = /* @__PURE__ */ new Set();
76
76
  const pending = [0];
@@ -86,6 +86,13 @@ function collectGlyphIds(glyf, cmap, numGlyphs, codePoints) {
86
86
  pending.push(glyphId);
87
87
  }
88
88
  }
89
+ for (const glyphId of extraGlyphIds) {
90
+ if (glyphId >= numGlyphs) return;
91
+ if (!used.has(glyphId)) {
92
+ used.add(glyphId);
93
+ pending.push(glyphId);
94
+ }
95
+ }
89
96
  while (pending.length > 0) {
90
97
  const glyphId = pending.pop();
91
98
  if (glyf.glyphBytes(glyphId) === void 0) return;
@@ -106,7 +113,7 @@ function collectGlyphIds(glyf, cmap, numGlyphs, codePoints) {
106
113
  unmapped: [...unmapped].sort((a, b) => a - b)
107
114
  };
108
115
  }
109
- function subsetSfnt(font, codePoints) {
116
+ function subsetSfnt(font, codePoints, extraGlyphIds = []) {
110
117
  const head = parseHead(font);
111
118
  const maxp = parseMaxp(font);
112
119
  const sourceHead = sfntTableBytes(font, "head");
@@ -123,7 +130,7 @@ function subsetSfnt(font, codePoints) {
123
130
  });
124
131
  const cmap = buildCmapLookup(font);
125
132
  if (glyf === void 0 || cmap === void 0) return;
126
- const collected = collectGlyphIds(glyf, cmap, maxp.numGlyphs, codePoints);
133
+ const collected = collectGlyphIds(glyf, cmap, maxp.numGlyphs, codePoints, extraGlyphIds);
127
134
  if (collected === void 0) return;
128
135
  const glyphIds = [...collected.used].sort((a, b) => a - b);
129
136
  const numGlyphs = glyphIds[glyphIds.length - 1] + 1;
@@ -8,8 +8,13 @@ function codePointToUtf16BEHex(codePoint) {
8
8
  for (let i = 0; i < text.length; i++) hex += text.charCodeAt(i).toString(16).padStart(4, "0");
9
9
  return hex;
10
10
  }
11
- function buildToUnicodeCMap(codeToCodePoint) {
12
- const entries = [...codeToCodePoint].sort((a, b) => a[0] - b[0]);
11
+ function sequenceToUtf16BEHex(codePoints) {
12
+ let hex = "";
13
+ for (const codePoint of codePoints) hex += codePointToUtf16BEHex(codePoint);
14
+ return hex;
15
+ }
16
+ function buildToUnicodeCMap(codeToSequence) {
17
+ const entries = [...codeToSequence].sort((a, b) => a[0] - b[0]);
13
18
  const lines = [
14
19
  "/CIDInit /ProcSet findresource begin",
15
20
  "12 dict begin",
@@ -24,7 +29,7 @@ function buildToUnicodeCMap(codeToCodePoint) {
24
29
  for (let start = 0; start < entries.length; start += MAX_BFCHAR_ENTRIES_PER_BLOCK) {
25
30
  const block = entries.slice(start, start + MAX_BFCHAR_ENTRIES_PER_BLOCK);
26
31
  lines.push(`${block.length} beginbfchar`);
27
- for (const [code, codePoint] of block) lines.push(`<${code.toString(16).padStart(4, "0")}> <${codePointToUtf16BEHex(codePoint)}>`);
32
+ for (const [code, sequence] of block) lines.push(`<${code.toString(16).padStart(4, "0")}> <${sequenceToUtf16BEHex(sequence)}>`);
28
33
  lines.push("endbfchar");
29
34
  }
30
35
  lines.push("endcmap", "CMapName currentdict /CMap defineresource pop", "end", "end");
@@ -1,5 +1,5 @@
1
1
  import { r as PdfObject } from "./objects-B7HdMeWS.cjs";
2
2
  //#region src/tounicode.d.ts
3
- declare function buildToUnicodeCMap(codeToCodePoint: ReadonlyMap<number, number>): PdfObject;
3
+ declare function buildToUnicodeCMap(codeToSequence: ReadonlyMap<number, readonly number[]>): PdfObject;
4
4
  //#endregion
5
5
  export { buildToUnicodeCMap };
@@ -1,5 +1,5 @@
1
1
  import { r as PdfObject } from "./objects-B7HdMeWS.js";
2
2
  //#region src/tounicode.d.ts
3
- declare function buildToUnicodeCMap(codeToCodePoint: ReadonlyMap<number, number>): PdfObject;
3
+ declare function buildToUnicodeCMap(codeToSequence: ReadonlyMap<number, readonly number[]>): PdfObject;
4
4
  //#endregion
5
5
  export { buildToUnicodeCMap };
package/dist/tounicode.js CHANGED
@@ -7,8 +7,13 @@ function codePointToUtf16BEHex(codePoint) {
7
7
  for (let i = 0; i < text.length; i++) hex += text.charCodeAt(i).toString(16).padStart(4, "0");
8
8
  return hex;
9
9
  }
10
- function buildToUnicodeCMap(codeToCodePoint) {
11
- const entries = [...codeToCodePoint].sort((a, b) => a[0] - b[0]);
10
+ function sequenceToUtf16BEHex(codePoints) {
11
+ let hex = "";
12
+ for (const codePoint of codePoints) hex += codePointToUtf16BEHex(codePoint);
13
+ return hex;
14
+ }
15
+ function buildToUnicodeCMap(codeToSequence) {
16
+ const entries = [...codeToSequence].sort((a, b) => a[0] - b[0]);
12
17
  const lines = [
13
18
  "/CIDInit /ProcSet findresource begin",
14
19
  "12 dict begin",
@@ -23,7 +28,7 @@ function buildToUnicodeCMap(codeToCodePoint) {
23
28
  for (let start = 0; start < entries.length; start += MAX_BFCHAR_ENTRIES_PER_BLOCK) {
24
29
  const block = entries.slice(start, start + MAX_BFCHAR_ENTRIES_PER_BLOCK);
25
30
  lines.push(`${block.length} beginbfchar`);
26
- for (const [code, codePoint] of block) lines.push(`<${code.toString(16).padStart(4, "0")}> <${codePointToUtf16BEHex(codePoint)}>`);
31
+ for (const [code, sequence] of block) lines.push(`<${code.toString(16).padStart(4, "0")}> <${sequenceToUtf16BEHex(sequence)}>`);
27
32
  lines.push("endbfchar");
28
33
  }
29
34
  lines.push("endcmap", "CMapName currentdict /CMap defineresource pop", "end", "end");
package/dist/write.cjs CHANGED
@@ -202,11 +202,14 @@ function destinationViewArray(target) {
202
202
  if (target.kind === "fitBV") return [require_objects.pdfName("FitBV"), n(target.leftPt)];
203
203
  return [require_objects.pdfName(target.kind === "fitB" ? "FitB" : "Fit")];
204
204
  }
205
- function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
206
- const destination = doc.destinations?.find((d) => d.name === link.destination);
207
- if (destination === void 0) throw new Error(`internal link names destination "${link.destination}", which the document's destinations table does not carry -- this is a caller-invariant violation`);
205
+ function resolveDestinationArray(doc, pageAllocs, name, what) {
206
+ const destination = doc.destinations?.find((d) => d.name === name);
207
+ if (destination === void 0) throw new Error(`${what} names destination "${name}", which the document's destinations table does not carry -- this is a caller-invariant violation`);
208
208
  const targetPage = pageAllocs[destination.pageIndex];
209
- if (targetPage === void 0) throw new Error(`destination "${link.destination}" names page index ${destination.pageIndex}, which is beyond the document's own pages -- this is a caller-invariant violation`);
209
+ if (targetPage === void 0) throw new Error(`destination "${destination.name}" names page index ${destination.pageIndex}, which is beyond the document's own pages -- this is a caller-invariant violation`);
210
+ return [require_objects.pdfRef(targetPage.pageNum, 0), ...destinationViewArray(destination.target)];
211
+ }
212
+ function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
210
213
  return require_objects.pdfDict({
211
214
  Type: require_objects.pdfName("Annot"),
212
215
  Subtype: require_objects.pdfName("Link"),
@@ -221,7 +224,7 @@ function buildInternalLinkAnnotDict(link, doc, pageAllocs) {
221
224
  0,
222
225
  0
223
226
  ].map((v) => require_objects.pdfNum(v))),
224
- Dest: require_objects.pdfArray([require_objects.pdfRef(targetPage.pageNum, 0), ...destinationViewArray(destination.target)])
227
+ Dest: require_objects.pdfArray(resolveDestinationArray(doc, pageAllocs, link.destination, "internal link"))
225
228
  });
226
229
  }
227
230
  function isLinkItem(item) {
@@ -322,13 +325,28 @@ function writePdf(doc, options = {}) {
322
325
  contentsNum: nextObjNum++
323
326
  }));
324
327
  const encryptDictNum = options.encryption === void 0 ? void 0 : nextObjNum++;
328
+ const attachmentAllocs = (doc.attachments ?? []).map(() => ({
329
+ fileNum: nextObjNum++,
330
+ specNum: nextObjNum++
331
+ }));
332
+ const attachmentsNamesNum = attachmentAllocs.length > 0 ? nextObjNum++ : void 0;
333
+ const outlineRootNum = (doc.outline ?? []).length > 0 ? nextObjNum++ : void 0;
334
+ const outlineItemNums = [];
335
+ if (outlineRootNum !== void 0) {
336
+ const countItems = (items) => {
337
+ let n = 0;
338
+ for (const item of items) n += 1 + countItems(item.children);
339
+ return n;
340
+ };
341
+ for (let i = 0; i < countItems(doc.outline ?? []); i += 1) outlineItemNums.push(nextObjNum++);
342
+ }
325
343
  const objects = [];
344
+ const catalogEntries = [["Type", require_objects.pdfName("Catalog")], ["Pages", require_objects.pdfRef(pagesNum, 0)]];
345
+ if (attachmentsNamesNum !== void 0) catalogEntries.push(["Names", require_objects.pdfRef(attachmentsNamesNum, 0)]);
346
+ if (outlineRootNum !== void 0) catalogEntries.push(["Outlines", require_objects.pdfRef(outlineRootNum, 0)]);
326
347
  objects.push({
327
348
  num: catalogNum,
328
- value: require_objects.pdfDict({
329
- Type: require_objects.pdfName("Catalog"),
330
- Pages: require_objects.pdfRef(pagesNum, 0)
331
- })
349
+ value: require_objects.pdfDict(Object.fromEntries(catalogEntries))
332
350
  });
333
351
  objects.push({
334
352
  num: pagesNum,
@@ -342,6 +360,73 @@ function writePdf(doc, options = {}) {
342
360
  num: infoNum,
343
361
  value: buildInfoDict(doc)
344
362
  });
363
+ for (const [index, attachment] of (doc.attachments ?? []).entries()) {
364
+ const alloc = attachmentAllocs[index];
365
+ const fileEntries = [["Type", require_objects.pdfName("EmbeddedFile")]];
366
+ if (attachment.mimeType !== void 0) fileEntries.push(["Subtype", require_objects.pdfName(attachment.mimeType)]);
367
+ objects.push({
368
+ num: alloc.fileNum,
369
+ value: require_objects.pdfStream(require_objects.pdfDict(Object.fromEntries(fileEntries)), require_util_base64.base64ToBytes(attachment.base64))
370
+ });
371
+ const specEntries = [
372
+ ["Type", require_objects.pdfName("Filespec")],
373
+ ["F", require_objects.pdfLiteralString(new TextEncoder().encode(attachment.name))],
374
+ ["EF", require_objects.pdfDict({ F: require_objects.pdfRef(alloc.fileNum, 0) })]
375
+ ];
376
+ if (attachment.description !== void 0) specEntries.push(["Desc", require_objects.pdfLiteralString(new TextEncoder().encode(attachment.description))]);
377
+ objects.push({
378
+ num: alloc.specNum,
379
+ value: require_objects.pdfDict(Object.fromEntries(specEntries))
380
+ });
381
+ }
382
+ if (attachmentsNamesNum !== void 0) {
383
+ const names = [];
384
+ for (const [index, attachment] of (doc.attachments ?? []).entries()) {
385
+ names.push(require_objects.pdfLiteralString(new TextEncoder().encode(attachment.name)));
386
+ names.push(require_objects.pdfRef(attachmentAllocs[index].specNum, 0));
387
+ }
388
+ objects.push({
389
+ num: attachmentsNamesNum,
390
+ value: require_objects.pdfDict({ EmbeddedFiles: require_objects.pdfDict({ Names: require_objects.pdfArray(names) }) })
391
+ });
392
+ }
393
+ if (outlineRootNum !== void 0 && (doc.outline ?? []).length > 0) {
394
+ let itemCursor = 0;
395
+ const emitItems = (items, parentNum) => {
396
+ const siblingNums = [];
397
+ let prevNum;
398
+ for (const item of items) {
399
+ const ownNum = outlineItemNums[itemCursor];
400
+ itemCursor += 1;
401
+ siblingNums.push(ownNum);
402
+ const childNums = emitItems(item.children, ownNum);
403
+ const entries = [["Title", require_objects.pdfLiteralString(new TextEncoder().encode(item.title))], ["Parent", require_objects.pdfRef(parentNum, 0)]];
404
+ if (item.destination !== void 0) entries.push(["Dest", require_objects.pdfArray(resolveDestinationArray(doc, pageAllocs, item.destination, `outline item "${item.title}"`))]);
405
+ if (prevNum !== void 0) entries.push(["Prev", require_objects.pdfRef(prevNum, 0)]);
406
+ if (siblingNums.length < items.length) entries.push(["Next", require_objects.pdfRef(outlineItemNums[itemCursor], 0)]);
407
+ if (childNums.length > 0) {
408
+ entries.push(["First", require_objects.pdfRef(childNums[0], 0)]);
409
+ entries.push(["Last", require_objects.pdfRef(childNums[childNums.length - 1], 0)]);
410
+ entries.push(["Count", require_objects.pdfNum(childNums.length)]);
411
+ }
412
+ objects.push({
413
+ num: ownNum,
414
+ value: require_objects.pdfDict(Object.fromEntries(entries))
415
+ });
416
+ prevNum = ownNum;
417
+ }
418
+ return siblingNums;
419
+ };
420
+ const topLevelNums = emitItems(doc.outline ?? [], outlineRootNum);
421
+ objects.push({
422
+ num: outlineRootNum,
423
+ value: require_objects.pdfDict({
424
+ Type: require_objects.pdfName("Outlines"),
425
+ First: require_objects.pdfRef(topLevelNums[0], 0),
426
+ Last: require_objects.pdfRef(topLevelNums[topLevelNums.length - 1], 0)
427
+ })
428
+ });
429
+ }
345
430
  for (const [standardName, alloc] of fontAllocs) {
346
431
  const { font, descriptor } = buildFontObjects(standardName, require_objects.pdfRef(alloc.descNum, 0));
347
432
  objects.push({
@@ -397,9 +482,10 @@ function writePdf(doc, options = {}) {
397
482
  });
398
483
  }
399
484
  for (const [face, alloc] of embeddedAllocs) {
400
- const subset = require_sfnt_subset.subsetSfnt(face.font, [...alloc.codePoints].sort((a, b) => a - b));
485
+ const usedGlyphs = require_embedded_font.collectEmbeddedGlyphs(alloc.texts, face);
486
+ const subset = require_sfnt_subset.subsetSfnt(face.font, [...alloc.codePoints].sort((a, b) => a - b), [...usedGlyphs.keys()].sort((a, b) => a - b));
401
487
  if (subset === void 0) throw new Error(`font "${face.postScriptName}" resolved to an embeddable face, but its glyph outlines could not be subsetted -- only TrueType-outline ('glyf') fonts can be embedded, so supply a TrueType face for this family or drop it from the registry`);
402
- const built = require_embedded_font_write.buildEmbeddedFontObjects(face, subset, require_embedded_font.collectEmbeddedGlyphs(alloc.texts, face), {
488
+ const built = require_embedded_font_write.buildEmbeddedFontObjects(face, subset, usedGlyphs, {
403
489
  cidFontRef: require_objects.pdfRef(alloc.cidFontNum, 0),
404
490
  descriptorRef: require_objects.pdfRef(alloc.descriptorNum, 0),
405
491
  fontFileRef: require_objects.pdfRef(alloc.fontFileNum, 0),
package/dist/write.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LayoutDocument } from "./layout.cjs";
2
- import { r as EmbeddedFaceSubstitution } from "./embedded-font-BU6Jgcof.cjs";
2
+ import { r as EmbeddedFaceSubstitution } from "./embedded-font-oc65VGlr.cjs";
3
3
  import { WinAnsiSubstitution } from "./winansi.cjs";
4
4
  import { PdfEncryptionOptions } from "./encrypt-write.cjs";
5
5
  import { FontRegistry } from "./font-registry.cjs";
package/dist/write.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LayoutDocument } from "./layout.js";
2
- import { r as EmbeddedFaceSubstitution } from "./embedded-font-fREdbxnr.js";
2
+ import { r as EmbeddedFaceSubstitution } from "./embedded-font-B4p9a3O3.js";
3
3
  import { WinAnsiSubstitution } from "./winansi.js";
4
4
  import { PdfEncryptionOptions } from "./encrypt-write.js";
5
5
  import { FontRegistry } from "./font-registry.js";