reamkit 1.27.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +6 -5
  2. package/dist/esm/core/converter/ream.d.ts +8 -9
  3. package/dist/esm/core/converter/ream.js +0 -1
  4. package/dist/esm/core/document-model/types.d.ts +5 -3
  5. package/dist/esm/core/font/index.d.ts +1 -0
  6. package/dist/esm/core/font/ligatures.d.ts +17 -0
  7. package/dist/esm/core/font/ligatures.js +49 -0
  8. package/dist/esm/core/font/ttf-parser.d.ts +2 -1
  9. package/dist/esm/core/font/ttf-parser.js +11 -2
  10. package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
  11. package/dist/esm/core/fonts/remote-fonts.js +47 -2
  12. package/dist/esm/core/fonts/scripts.js +10 -5
  13. package/dist/esm/excel/header-footer.js +53 -8
  14. package/dist/esm/layout/styled-layout.js +53 -3
  15. package/dist/esm/pdf/cid-font.js +35 -8
  16. package/dist/esm/pdf-reader/annot-draw.js +114 -1
  17. package/dist/esm/pdf-reader/annots.d.ts +0 -14
  18. package/dist/esm/pdf-reader/annots.js +13 -1
  19. package/dist/esm/pdf-reader/ccitt.d.ts +2 -3
  20. package/dist/esm/pdf-reader/ccitt.js +32 -4
  21. package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
  22. package/dist/esm/pdf-reader/cff-outline.js +1122 -0
  23. package/dist/esm/pdf-reader/cmap.js +16 -6
  24. package/dist/esm/pdf-reader/content.d.ts +91 -0
  25. package/dist/esm/pdf-reader/content.js +156 -60
  26. package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
  27. package/dist/esm/pdf-reader/dingbats.js +1033 -0
  28. package/dist/esm/pdf-reader/display.js +41 -1
  29. package/dist/esm/pdf-reader/document.d.ts +8 -0
  30. package/dist/esm/pdf-reader/document.js +26 -9
  31. package/dist/esm/pdf-reader/embedded-fonts.d.ts +11 -0
  32. package/dist/esm/pdf-reader/embedded-fonts.js +14 -1
  33. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  34. package/dist/esm/pdf-reader/encodings.js +110 -0
  35. package/dist/esm/pdf-reader/flow-build.d.ts +15 -4
  36. package/dist/esm/pdf-reader/flow-build.js +24 -11
  37. package/dist/esm/pdf-reader/font.js +329 -8
  38. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  39. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  40. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  41. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  42. package/dist/esm/pdf-reader/icc.js +210 -0
  43. package/dist/esm/pdf-reader/image-decode.d.ts +4 -3
  44. package/dist/esm/pdf-reader/image-decode.js +85 -64
  45. package/dist/esm/pdf-reader/images.d.ts +6 -0
  46. package/dist/esm/pdf-reader/images.js +50 -7
  47. package/dist/esm/pdf-reader/jbig2.js +104 -19
  48. package/dist/esm/pdf-reader/layout.js +1531 -55
  49. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  50. package/dist/esm/pdf-reader/math-rows.js +198 -0
  51. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  52. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  53. package/dist/esm/pdf-reader/reader.d.ts +6 -9
  54. package/dist/esm/pdf-reader/reader.js +34 -21
  55. package/dist/esm/pdf-reader/shading.d.ts +81 -2
  56. package/dist/esm/pdf-reader/shading.js +191 -25
  57. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  58. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  59. package/dist/esm/pdf-reader/tagged.js +1 -1
  60. package/dist/esm/pdf-reader/text-rules.js +83 -10
  61. package/dist/esm/pdf-reader/text.js +82 -3
  62. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  63. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  64. package/dist/esm/pdf-reader/vector.d.ts +2 -0
  65. package/dist/esm/pdf-reader/vector.js +44 -4
  66. package/dist/esm/word/document-parser.js +4 -1
  67. package/dist/esm/word/docx-writer.js +28 -11
  68. package/package.json +1 -1
@@ -32,7 +32,8 @@ function parseToUnicodeCMap(bytes) {
32
32
  if (src.kind !== "hexstr") continue;
33
33
  const dst = lexer.nextToken();
34
34
  if (dst.kind !== "hexstr") continue;
35
- map.set(bytesToInt(src.bytes), utf16be(dst.bytes));
35
+ const text = stated(utf16be(dst.bytes));
36
+ if (text !== void 0) map.set(bytesToInt(src.bytes), text);
36
37
  }
37
38
  else if (tok.value === "beginbfrange") for (;;) {
38
39
  const lo = lexer.nextToken();
@@ -44,14 +45,17 @@ function parseToUnicodeCMap(bytes) {
44
45
  const hiN = bytesToInt(hi.bytes);
45
46
  const dst = lexer.nextToken();
46
47
  if (dst.kind === "hexstr") {
47
- const base = utf16be(dst.bytes);
48
- for (let i = 0; loN + i <= hiN && i < MAX_RANGE; i++) map.set(loN + i, incString(base, i));
48
+ const base = stated(utf16be(dst.bytes));
49
+ if (base !== void 0) for (let i = 0; loN + i <= hiN && i < MAX_RANGE; i++) map.set(loN + i, incString(base, i));
49
50
  } else if (dst.kind === "arrayOpen") {
50
51
  let i = 0;
51
52
  for (;;) {
52
53
  const el = lexer.nextToken();
53
54
  if (el.kind === "arrayClose" || el.kind === "eof") break;
54
- if (el.kind === "hexstr") map.set(loN + i++, utf16be(el.bytes));
55
+ if (el.kind !== "hexstr") continue;
56
+ const text = stated(utf16be(el.bytes));
57
+ if (text !== void 0) map.set(loN + i, text);
58
+ i++;
55
59
  }
56
60
  }
57
61
  }
@@ -61,6 +65,9 @@ function parseToUnicodeCMap(bytes) {
61
65
  codeBytes
62
66
  };
63
67
  }
68
+ function stated(text) {
69
+ for (const ch of text) if (ch !== "\0") return text;
70
+ }
64
71
  function bytesToInt(bytes) {
65
72
  let n = 0;
66
73
  for (const b of bytes) n = n * 256 + b;
@@ -74,8 +81,11 @@ function utf16be(bytes) {
74
81
  }
75
82
  function incString(base, i) {
76
83
  if (i === 0) return base;
77
- if (base.length === 1) return String.fromCharCode(base.charCodeAt(0) + i);
78
- return base;
84
+ const chars = [...base];
85
+ if (chars.length === 0) return base;
86
+ const last = chars[chars.length - 1].codePointAt(0) + i;
87
+ if (last > 1114111 || last >= 55296 && last <= 57343) return base;
88
+ return chars.slice(0, -1).join("") + String.fromCodePoint(last);
79
89
  }
80
90
  //#endregion
81
91
  export { parseToUnicodeCMap };
@@ -11,10 +11,25 @@ import { PdfDict, PdfStream } from '../pdf/objects.js';
11
11
  export interface ContentFont {
12
12
  /** Bytes per character code: simple fonts read 1 byte/code, Type0 reads 2. */
13
13
  readonly bytesPerCode: 1 | 2;
14
+ /**
15
+ * §9.7.6.2 — how a shown string breaks into codes, where the CMap is not
16
+ * fixed-width. A named CMap like `90ms-RKSJ-H` mixes one-byte and two-byte
17
+ * codes, and split down the middle a Japanese line came apart into nonsense.
18
+ * Absent means the fixed width above.
19
+ */
20
+ readonly splitCodes?: (bytes: Uint8Array) => Array<number>;
14
21
  /** Decode a sequence of character codes to a Unicode string. */
15
22
  decode: (codes: ReadonlyArray<number>) => string;
16
23
  /** Glyph advance for one code, in 1000-unit text space. */
17
24
  width: (code: number) => number;
25
+ /**
26
+ * §9.4.4 / §9.7.4.3 — the face sets its text DOWN the page, not across, and
27
+ * the pen advances by the vertical displacement `w1` rather than by `w0`.
28
+ * A `…-V` CMap asks for this; `/DW2`'s default `[880 -1000]` is one em down.
29
+ * The number here is that displacement in 1000-unit text space, and it is
30
+ * negative because the page's y runs up.
31
+ */
32
+ readonly verticalAdvance?: (code: number) => number;
18
33
  /** §9.6.2 — the face's own `/BaseFont` name, for a document that embeds it. */
19
34
  readonly name?: string;
20
35
  /** §9.8.1 — the face is a bold one (weight, the ForceBold flag, or its name). */
@@ -27,6 +42,19 @@ export interface ContentFont {
27
42
  * procedure paints, in the resources the font states.
28
43
  */
29
44
  readonly type3?: Type3Face;
45
+ /**
46
+ * §9.6.6 — the OUTLINE a code draws, for a code that stands for no
47
+ * character. A face that can say what its characters are never has one:
48
+ * this is the last resort before a blank page.
49
+ */
50
+ readonly outline?: GlyphOutline;
51
+ }
52
+ /** §9.6.6 — the outlines of a face read by glyph index, and their glyph space. */
53
+ export interface GlyphOutline {
54
+ /** Glyph space to text space, as a Type 3 font's `/FontMatrix` is. */
55
+ readonly matrix: Matrix;
56
+ /** The contours one code draws, or `undefined` where it draws none. */
57
+ readonly path: (code: number) => Array<PathSeg> | undefined;
30
58
  }
31
59
  /** §9.6.5 — the parts of a Type 3 font a caller needs to run its glyphs. */
32
60
  export interface Type3Face {
@@ -94,6 +122,12 @@ export interface TextRun {
94
122
  * fill colour still standing from before is not what the page shows.
95
123
  */
96
124
  readonly fillPatternName?: string;
125
+ /**
126
+ * §8.7.4.5 — the glyphs are filled with a SHADING pattern: a sweep from one
127
+ * colour to another, of which the run carries the middle. The colour is not
128
+ * lost, the shape of it is.
129
+ */
130
+ readonly gradientFill?: boolean;
97
131
  /**
98
132
  * §9.3.6 — the page painted these glyphs NOWHERE: mode 3 shows nothing and
99
133
  * mode 7 only adds to the clip. A scanned page carries its recognised words
@@ -157,6 +191,8 @@ export interface ImagePlacement {
157
191
  */
158
192
  readonly fillHex: string;
159
193
  readonly mcid?: number;
194
+ /** §11.6.4.4 `/ca` — how opaque the picture is drawn, when the page asked for less. */
195
+ readonly alpha?: number;
160
196
  /** §11.3.5 `/BM` — a blend the page asked for that nothing here performs. */
161
197
  readonly blend?: string;
162
198
  /** §11.6.5 `/SMask` — the paint faded from place to place; nothing here does. */
@@ -222,6 +258,13 @@ export interface VectorPlacement {
222
258
  readonly blend?: string;
223
259
  /** §11.6.5 `/SMask` — the paint faded from place to place; nothing here does. */
224
260
  readonly masked?: boolean;
261
+ /**
262
+ * §9.6.6 — this path is a GLYPH, traced from the face because its code stands
263
+ * for no character. It is type, not artwork, so the de-cluttering a page's
264
+ * paths go through does not apply: a full stop is a mark one point across and
265
+ * every filter there would throw it out.
266
+ */
267
+ readonly glyph?: boolean;
225
268
  /**
226
269
  * §8.7.3 — the TILING pattern resource name the path is filled with. Its
227
270
  * content is a stream of its own, so what the fill actually shows is only
@@ -241,18 +284,66 @@ export interface InterpretResult {
241
284
  readonly vectors: Array<VectorPlacement>;
242
285
  /** §9.6.5 — every Type 3 glyph the stream showed, with where to run it. */
243
286
  readonly glyphs: Array<Type3Call>;
287
+ /**
288
+ * §8.7.4.3 — every region the stream painted with a bare `sh`, which fills
289
+ * the CLIP rather than a path. What a caller can do with one depends on the
290
+ * shading's own type, so the placement is carried rather than counted.
291
+ */
292
+ readonly shadings: Array<ShadingPaint>;
293
+ /**
294
+ * §9.6.6 — every glyph the stream showed that stands for NO character, as
295
+ * the path it draws in page space. There is nothing to write for one, and
296
+ * there is something to draw.
297
+ */
298
+ readonly outlines: Array<VectorPlacement>;
244
299
  }
245
300
  /**
246
301
  * §9.6.5 — one showing of a Type 3 glyph: which procedure, and the matrix that
247
302
  * puts glyph space on the page.
248
303
  */
304
+ /** §8.7.4.3 — one `sh`: which shading, where the CTM put it, what bounded it. */
305
+ export interface ShadingPaint {
306
+ /** The `/Shading` resource name (no leading slash). */
307
+ readonly name: string;
308
+ /** The CTM in force, which maps the shading's own space onto the page. */
309
+ readonly ctm: Matrix;
310
+ /** §8.5.4 — the clip the paint was bounded by, which is its whole extent. */
311
+ readonly clip?: ClipRegion;
312
+ /**
313
+ * §11.6.5 `/SMask` — the paint was faded from place to place. For a `sh` that
314
+ * is not a detail: the mask is the SHAPE, and the clip is only its outer
315
+ * bound.
316
+ */
317
+ readonly masked?: boolean;
318
+ /** §11.6.4.4 `/ca` — how opaque the paint is, when the page asked for less. */
319
+ readonly alpha?: number;
320
+ /** §11.3.5 `/BM` — the paint only DARKENS what it covers. */
321
+ readonly darkens?: boolean;
322
+ /** Where it fell in the painting order (§8.5.3). */
323
+ readonly order: number;
324
+ }
249
325
  export interface Type3Call {
250
326
  readonly stream: PdfStream;
251
327
  readonly resources: PdfDict | undefined;
252
328
  readonly ctm: Matrix;
253
329
  /** §8.5.3 — its place in the stream's painting order, as a form call has. */
254
330
  readonly order: number;
331
+ /**
332
+ * §9.6.5/§9.10 — the font SAYS what character this glyph stands for. A
333
+ * flowing reading re-sets that character in a face of its own, so drawing the
334
+ * procedure as well puts two copies of the letter on the page.
335
+ */
336
+ readonly readable: boolean;
255
337
  }
338
+ /**
339
+ * §9.10 — whether the font says what character a glyph stands for: text that is
340
+ * neither empty nor the replacement character a reader writes where nothing is
341
+ * stated.
342
+ *
343
+ * @param text What the font's decode gave for the code.
344
+ * @returns Whether a flowing reading can write it as a letter.
345
+ */
346
+ export declare function statesACharacter(text: string): boolean;
256
347
  /**
257
348
  * 2D affine matrix `[a b c d e f]`, row-vector convention (`[x y 1] · M`):
258
349
  * ```
@@ -1,6 +1,6 @@
1
1
  import { PDF_NULL, PdfHexString, PdfName } from "../pdf/objects.js";
2
2
  import { Lexer } from "./lexer.js";
3
- import { cieToSrgb } from "./cie-color.js";
3
+ import { cmykHex, grayHex, rgbHex, spaceColor } from "./shading.js";
4
4
  //#region src/pdf-reader/content.ts
5
5
  /**
6
6
  * A painted path emitted by the path-painting operators (§8.5.3), captured in
@@ -35,6 +35,18 @@ function pathBox(segs) {
35
35
  }
36
36
  /** The area of a box, for choosing the smaller of two clip regions. */
37
37
  var area = (b) => Math.max(0, b.maxX - b.minX) * Math.max(0, b.maxY - b.minY);
38
+ /**
39
+ * §9.10 — whether the font says what character a glyph stands for: text that is
40
+ * neither empty nor the replacement character a reader writes where nothing is
41
+ * stated.
42
+ *
43
+ * @param text What the font's decode gave for the code.
44
+ * @returns Whether a flowing reading can write it as a letter.
45
+ */
46
+ function statesACharacter(text) {
47
+ const trimmed = text.trim();
48
+ return trimmed.length > 0 && !trimmed.includes("�");
49
+ }
38
50
  /** The identity {@link Matrix} (no transform). */
39
51
  var IDENTITY = [
40
52
  1,
@@ -136,39 +148,42 @@ function spaceOf(operands, spaces) {
136
148
  }
137
149
  }
138
150
  /**
139
- * §8.6.8 — the colour a run of `sc` / `scn` components comes to.
140
- *
141
- * The space in force decides, and where it was not read the COUNT is the next
142
- * best witness: three numbers are RGB and four are CMYK on every device space
143
- * there is. One number is the ambiguous case — grey in a device space, but the
144
- * strength of a colorant in a Separation, where 1 is the ink at full and reads
145
- * dark — so a lone component is only taken where the space said what it means.
151
+ * §8.7.4.5 — one colour for a whole sweep: the stop at its middle, or the mean
152
+ * of the two it falls between. A gradient is a shape a run cannot carry, and
153
+ * the end it starts at is no more the colour of the words than the end it
154
+ * finishes at.
146
155
  */
147
- function componentColor(operands, space) {
148
- const nums = operands.filter((o) => typeof o === "number");
149
- if (nums.length === 0) return void 0;
150
- if (space?.cie) {
151
- const [r, g, b] = cieToSrgb(space.cie, nums);
152
- return rgbHex(r, g, b);
153
- }
154
- switch (space?.kind ?? (nums.length === 3 ? "rgb" : nums.length === 4 ? "cmyk" : void 0)) {
155
- case "rgb": return nums.length >= 3 ? rgbHex(nums[0], nums[1], nums[2]) : void 0;
156
- case "cmyk": return nums.length >= 4 ? cmykHex(nums[0], nums[1], nums[2], nums[3]) : void 0;
157
- case "gray": return grayHex(nums[0]);
158
- case "tint":
159
- if (space?.tint) {
160
- const hex = componentColor(space.tint.transform(nums), space.tint.alternate);
161
- if (hex !== void 0) return hex;
162
- }
163
- return grayHex(1 - Math.max(...nums));
164
- default: return;
165
- }
156
+ function midGradient(gradient) {
157
+ const stops = [...gradient.stops].sort((a, b) => a.offset - b.offset);
158
+ if (stops.length === 0) return "000000";
159
+ const exact = stops.find((s) => Math.abs(s.offset - .5) < 1e-6);
160
+ if (exact) return exact.colorHex;
161
+ const before = [...stops].reverse().find((s) => s.offset <= .5) ?? stops[0];
162
+ const after = stops.find((s) => s.offset >= .5) ?? stops[stops.length - 1];
163
+ const span = after.offset - before.offset;
164
+ const t = span > 0 ? (.5 - before.offset) / span : 0;
165
+ const mix = (at) => {
166
+ const a = parseInt(before.colorHex.slice(at, at + 2), 16);
167
+ const b = parseInt(after.colorHex.slice(at, at + 2), 16);
168
+ return Number.isFinite(a) && Number.isFinite(b) ? Math.round(a + (b - a) * t) : 0;
169
+ };
170
+ return [
171
+ mix(0),
172
+ mix(2),
173
+ mix(4)
174
+ ].map((c) => Math.max(0, Math.min(255, c)).toString(16).padStart(2, "0")).join("").toUpperCase();
175
+ }
176
+ /** §8.6.8 — the colour a run of `sc` / `scn` operands comes to (see `spaceColor`). */
177
+ function colorOfOperands(operands, space) {
178
+ return spaceColor(operands.filter((o) => typeof o === "number"), space);
166
179
  }
167
180
  function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__PURE__ */ new Map(), alphas = /* @__PURE__ */ new Map(), spaces = /* @__PURE__ */ new Map(), hiddenOc = /* @__PURE__ */ new Set()) {
168
181
  const runs = [];
169
182
  const images = [];
170
183
  const vectors = [];
171
184
  const glyphs = [];
185
+ const painted = [];
186
+ const outlines = [];
172
187
  const lexer = new Lexer(bytes);
173
188
  const stack = [];
174
189
  let state = initialState();
@@ -281,16 +296,46 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
281
296
  stream,
282
297
  resources: type3.resources,
283
298
  ctm: multiply(type3.matrix, multiply(scale, multiply(tm, state.ctm))),
284
- order: paintOrder++
299
+ order: paintOrder++,
300
+ readable: statesACharacter(state.font.decode([code]))
301
+ });
302
+ }
303
+ }
304
+ const outline = state.font.outline;
305
+ if (outline && visible() && state.renderMode !== 3 && state.renderMode !== 7) {
306
+ const segs = outline.path(code);
307
+ if (segs) {
308
+ const scale = [
309
+ state.fontSize * state.hScale,
310
+ 0,
311
+ 0,
312
+ state.fontSize,
313
+ 0,
314
+ state.rise
315
+ ];
316
+ const place = multiply(outline.matrix, multiply(scale, multiply(tm, state.ctm)));
317
+ outlines.push({
318
+ order: paintOrder++,
319
+ segs: segs.map((seg) => mapSeg(seg, place)),
320
+ ...state.clip ? { clip: state.clip } : {},
321
+ fillHex: state.fillColor,
322
+ ...state.fillAlpha < 1 ? { alpha: state.fillAlpha } : {},
323
+ glyph: true
285
324
  });
286
325
  }
287
326
  }
288
- const w0 = state.font.width(code) / 1e3;
289
327
  const isSpace = state.font.bytesPerCode === 1 && code === 32;
290
- tm = multiply(translation((w0 * state.fontSize + state.charSpacing + (isSpace ? state.wordSpacing : 0)) * state.hScale, 0), tm);
328
+ const spacing = state.charSpacing + (isSpace ? state.wordSpacing : 0);
329
+ const down = state.font.verticalAdvance;
330
+ if (down) {
331
+ tm = multiply(translation(0, down(code) / 1e3 * state.fontSize - spacing), tm);
332
+ return;
333
+ }
334
+ tm = multiply(translation((state.font.width(code) / 1e3 * state.fontSize + spacing) * state.hScale, 0), tm);
291
335
  };
292
336
  const consume = (operand) => {
293
- const codes = splitCodes(toBytes(operand), state.font.bytesPerCode);
337
+ const bytes = toBytes(operand);
338
+ const codes = state.font.splitCodes ? state.font.splitCodes(bytes) : splitCodes(bytes, state.font.bytesPerCode);
294
339
  for (const code of codes) advanceGlyph(code);
295
340
  return state.font.decode(codes);
296
341
  };
@@ -316,13 +361,14 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
316
361
  ...state.font.type3 ? { type3: true } : {},
317
362
  ...state.renderMode === 3 || state.renderMode === 7 ? { invisible: true } : {},
318
363
  ...state.fillPattern !== void 0 ? { fillPatternName: state.fillPattern } : {},
364
+ ...state.fillGradient ? { gradientFill: true } : {},
319
365
  ...strokesText(state.renderMode) ? {
320
366
  outlineHex: state.strokeColor,
321
367
  outlineWidthPt: ctmLineWidth()
322
368
  } : {},
323
369
  ...state.font.bold ? { bold: true } : {},
324
370
  ...state.font.italic ? { italic: true } : {},
325
- colorHex: state.fillColor,
371
+ colorHex: state.fillGradient ? midGradient(state.fillGradient) : state.fillColor,
326
372
  ...mcid !== void 0 ? { mcid } : {}
327
373
  });
328
374
  };
@@ -438,7 +484,9 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
438
484
  ...state.clip ? { clip: state.clip } : {},
439
485
  fillHex: state.fillColor,
440
486
  ...mcid !== void 0 ? { mcid } : {},
441
- ...state.blendMode !== void 0 ? { blend: state.blendMode } : {}
487
+ ...state.fillAlpha < 1 ? { alpha: state.fillAlpha } : {},
488
+ ...state.blendMode !== void 0 ? { blend: state.blendMode } : {},
489
+ ...state.softMask ? { masked: true } : {}
442
490
  });
443
491
  }
444
492
  break;
@@ -473,7 +521,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
473
521
  state.fillPattern = state.fillGradient ? void 0 : named;
474
522
  break;
475
523
  }
476
- const hex = componentColor(operands, state.fillSpace);
524
+ const hex = colorOfOperands(operands, state.fillSpace);
477
525
  if (hex !== void 0) {
478
526
  state.fillColor = hex;
479
527
  state.fillGradient = void 0;
@@ -484,7 +532,7 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
484
532
  case "SCN":
485
533
  case "SC": {
486
534
  if (operands[operands.length - 1] instanceof PdfName) break;
487
- const hex = componentColor(operands, state.strokeSpace);
535
+ const hex = colorOfOperands(operands, state.strokeSpace);
488
536
  if (hex !== void 0) state.strokeColor = hex;
489
537
  break;
490
538
  }
@@ -536,17 +584,32 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
536
584
  path.push({ op: "close" });
537
585
  paintPath(true, true);
538
586
  break;
587
+ case "sh": {
588
+ const nm = operands[0];
589
+ if (nm instanceof PdfName && visible()) painted.push({
590
+ name: nm.value,
591
+ ctm: state.ctm,
592
+ ...state.clip ? { clip: state.clip } : {},
593
+ ...state.softMask ? { masked: true } : {},
594
+ ...state.fillAlpha < 1 ? { alpha: state.fillAlpha } : {},
595
+ ...state.fillDarkens ? { darkens: true } : {},
596
+ order: paintOrder++
597
+ });
598
+ break;
599
+ }
539
600
  case "n":
540
601
  paintPath(false, false);
541
602
  break;
542
603
  case "gs": {
543
604
  const nm = operands[operands.length - 1];
544
- if (nm instanceof PdfName) {
545
- const paint = alphas.get(nm.value);
546
- state.fillAlpha = paint?.alpha ?? 1;
547
- state.fillDarkens = paint?.darkens ?? false;
548
- state.blendMode = paint?.blend;
549
- state.softMask = paint?.masked ?? false;
605
+ const paint = nm instanceof PdfName ? alphas.get(nm.value) : void 0;
606
+ if (paint) {
607
+ if (paint.alpha !== void 0) state.fillAlpha = paint.alpha;
608
+ if (paint.statesBlend === true) {
609
+ state.fillDarkens = paint.darkens ?? false;
610
+ state.blendMode = paint.blend;
611
+ }
612
+ if (paint.masked !== void 0) state.softMask = paint.masked;
550
613
  }
551
614
  break;
552
615
  }
@@ -605,7 +668,34 @@ function interpretContent(bytes, fonts, initialCtm = IDENTITY, shadings = /* @__
605
668
  texts: runs,
606
669
  images,
607
670
  vectors,
608
- glyphs
671
+ glyphs,
672
+ shadings: painted,
673
+ outlines
674
+ };
675
+ }
676
+ /** One path segment through a matrix — the glyph's own space onto the page. */
677
+ function mapSeg(seg, m) {
678
+ const at = (x, y) => [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
679
+ if (seg.op === "close") return seg;
680
+ if (seg.op === "cubic") {
681
+ const [x1, y1] = at(seg.x1, seg.y1);
682
+ const [x2, y2] = at(seg.x2, seg.y2);
683
+ const [x, y] = at(seg.x, seg.y);
684
+ return {
685
+ op: "cubic",
686
+ x1,
687
+ y1,
688
+ x2,
689
+ y2,
690
+ x,
691
+ y
692
+ };
693
+ }
694
+ const [x, y] = at(seg.x, seg.y);
695
+ return {
696
+ op: seg.op,
697
+ x,
698
+ y
609
699
  };
610
700
  }
611
701
  /** §9.3.6 — the rendering modes that put a line round the glyphs. */
@@ -657,21 +747,6 @@ function isRightToLeft(text) {
657
747
  function isRtl(cp) {
658
748
  return cp >= 1424 && cp <= 1535 || cp >= 1536 && cp <= 1791 || cp >= 1792 && cp <= 1871 || cp >= 1920 && cp <= 1983 || cp >= 2208 && cp <= 2303 || cp >= 64285 && cp <= 65023 || cp >= 65136 && cp <= 65279;
659
749
  }
660
- function clamp255(v) {
661
- return Math.max(0, Math.min(255, Math.round(v * 255)));
662
- }
663
- function hex2(v) {
664
- return clamp255(v).toString(16).padStart(2, "0");
665
- }
666
- function rgbHex(r, g, b) {
667
- return (hex2(r) + hex2(g) + hex2(b)).toUpperCase();
668
- }
669
- function grayHex(v) {
670
- return rgbHex(v, v, v);
671
- }
672
- function cmykHex(c, m, y, k) {
673
- return rgbHex((1 - c) * (1 - k), (1 - m) * (1 - k), (1 - y) * (1 - k));
674
- }
675
750
  function matrixFromOperands(operands) {
676
751
  const n = (i) => typeof operands[i] === "number" ? operands[i] : 0;
677
752
  return [
@@ -705,9 +780,30 @@ function readArray(lexer) {
705
780
  for (;;) {
706
781
  const tok = lexer.nextToken();
707
782
  if (tok.kind === "arrayClose" || tok.kind === "eof") break;
708
- if (tok.kind === "num") out.push(tok.value);
709
- else if (tok.kind === "str") out.push(tok.value);
710
- else if (tok.kind === "hexstr") out.push(new PdfHexString(tok.bytes));
783
+ switch (tok.kind) {
784
+ case "num":
785
+ out.push(tok.value);
786
+ break;
787
+ case "str":
788
+ out.push(tok.value);
789
+ break;
790
+ case "hexstr":
791
+ out.push(new PdfHexString(tok.bytes));
792
+ break;
793
+ case "name":
794
+ out.push(new PdfName(tok.value));
795
+ break;
796
+ case "arrayOpen":
797
+ out.push(readArray(lexer));
798
+ break;
799
+ case "dictOpen":
800
+ out.push(readDict(lexer));
801
+ break;
802
+ case "keyword":
803
+ if (tok.value === "true" || tok.value === "false") out.push(tok.value === "true");
804
+ break;
805
+ default: break;
806
+ }
711
807
  }
712
808
  return out;
713
809
  }
@@ -0,0 +1,11 @@
1
+ /** The character a ZapfDingbats code stands for, or `undefined` for an unused one. */
2
+ export declare function zapfDingbatsChar(code: number): string | undefined;
3
+ /**
4
+ * The character a dingbat's glyph NAME stands for — `a38` is ✫ — for a font
5
+ * that names its glyphs through `/Differences` (§9.6.6.1).
6
+ *
7
+ * @param name The glyph name.
8
+ */
9
+ export declare function dingbatChar(name: string): string | undefined;
10
+ /** Whether a base font names the ZapfDingbats face (§9.6.2.2 Table 109). */
11
+ export declare function isZapfDingbats(baseFont: string): boolean;