reamkit 1.29.0 → 1.30.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 (50) hide show
  1. package/dist/esm/core/document-model/types.d.ts +2 -0
  2. package/dist/esm/core/drawingml/shape-render.js +13 -1
  3. package/dist/esm/core/fonts/index.d.ts +1 -1
  4. package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
  5. package/dist/esm/core/fonts/remote-fonts.js +100 -17
  6. package/dist/esm/core/ir/flow.d.ts +17 -0
  7. package/dist/esm/index.d.ts +1 -1
  8. package/dist/esm/pdf-reader/annot-draw.js +93 -1
  9. package/dist/esm/pdf-reader/annots.d.ts +18 -0
  10. package/dist/esm/pdf-reader/annots.js +86 -9
  11. package/dist/esm/pdf-reader/cmap.js +5 -2
  12. package/dist/esm/pdf-reader/content.d.ts +17 -4
  13. package/dist/esm/pdf-reader/content.js +163 -11
  14. package/dist/esm/pdf-reader/display.js +16 -0
  15. package/dist/esm/pdf-reader/embedded-fonts.d.ts +24 -0
  16. package/dist/esm/pdf-reader/embedded-fonts.js +48 -9
  17. package/dist/esm/pdf-reader/flow-build.d.ts +96 -5
  18. package/dist/esm/pdf-reader/flow-build.js +210 -23
  19. package/dist/esm/pdf-reader/font.d.ts +27 -1
  20. package/dist/esm/pdf-reader/font.js +313 -29
  21. package/dist/esm/pdf-reader/glyf-outline.js +13 -2
  22. package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
  23. package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
  24. package/dist/esm/pdf-reader/image-decode.js +70 -4
  25. package/dist/esm/pdf-reader/images.d.ts +5 -0
  26. package/dist/esm/pdf-reader/images.js +4 -2
  27. package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
  28. package/dist/esm/pdf-reader/jbig2.js +78 -16
  29. package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
  30. package/dist/esm/pdf-reader/jpeg.js +21 -1
  31. package/dist/esm/pdf-reader/layout.d.ts +26 -2
  32. package/dist/esm/pdf-reader/layout.js +638 -92
  33. package/dist/esm/pdf-reader/lexer.d.ts +10 -0
  34. package/dist/esm/pdf-reader/lexer.js +17 -0
  35. package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
  36. package/dist/esm/pdf-reader/pattern-tint.js +21 -3
  37. package/dist/esm/pdf-reader/regions.d.ts +25 -0
  38. package/dist/esm/pdf-reader/regions.js +167 -0
  39. package/dist/esm/pdf-reader/shading.d.ts +58 -2
  40. package/dist/esm/pdf-reader/shading.js +181 -11
  41. package/dist/esm/pdf-reader/struct-tree.js +112 -8
  42. package/dist/esm/pdf-reader/tagged.js +27 -7
  43. package/dist/esm/pdf-reader/text-rules.js +1 -1
  44. package/dist/esm/pdf-reader/text.js +9 -14
  45. package/dist/esm/pdf-reader/vector.d.ts +8 -2
  46. package/dist/esm/pdf-reader/vector.js +34 -5
  47. package/dist/esm/word/docx-writer.js +137 -36
  48. package/dist/esm/word/drawing-parser.js +7 -2
  49. package/dist/esm/word/paragraph-properties.js +2 -0
  50. package/package.json +5 -3
@@ -41,6 +41,8 @@ function decodePdfImage(file, stream, fillHex) {
41
41
  const last = filters[filters.length - 1];
42
42
  if (last === "DCTDecode" || last === "DCT") {
43
43
  const jpegBytes = applyChainExceptLast(filters, stream.data);
44
+ const inks = cmykJpeg(file, d, jpegBytes, width, height);
45
+ if (inks) return inks;
44
46
  const masked = hasSMask(file, d) ? maskedJpeg(file, d, jpegBytes, width, height) : void 0;
45
47
  if (masked) return masked;
46
48
  const degraded = hasSMask(file, d) ? "image transparency dropped (JPEG could not be decoded to attach its mask)" : void 0;
@@ -74,7 +76,7 @@ function decodePdfImage(file, stream, fillHex) {
74
76
  }
75
77
  const decoded = last === "CCITTFaxDecode" || last === "CCF" ? decodeCcittImage(file, stream, filters, width, height) : decodeToSamples(file, stream, filters, width, height);
76
78
  if (typeof decoded === "string") return fail("dropped", decoded);
77
- const alpha = decodeSMask(file, d, width, height);
79
+ const alpha = decodeSMask(file, d, width, height) ?? decoded.keyed;
78
80
  const { color, samples } = alpha ? combineAlpha(decoded, alpha) : decoded;
79
81
  return {
80
82
  ok: true,
@@ -92,7 +94,40 @@ function decodeToSamples(file, stream, filters, width, height) {
92
94
  if (!cs) return "unsupported image colour space";
93
95
  const bpc = intOf(file.get(d, "BitsPerComponent")) || intOf(file.get(d, "BPC")) || 8;
94
96
  const decodeArr = decodeArrayOf(file, d);
95
- return toColor(cs, unpackSamples(raw, width, height, cs.components, bpc), width * height, bpc, decodeArr);
97
+ const integers = unpackSamples(raw, width, height, cs.components, bpc);
98
+ const keyed = colorKey(file, d, integers, cs.components, width * height);
99
+ return {
100
+ ...toColor(cs, integers, width * height, bpc, decodeArr),
101
+ ...keyed ? { keyed } : {}
102
+ };
103
+ }
104
+ /**
105
+ * §8.9.6.4 — colour key masking: `/Mask` as an array of a range per colour
106
+ * component, and a sample whose every component falls inside its range is
107
+ * not painted. The ranges are in the image's own sample values, before any
108
+ * `/Decode`. colorkeymask.pdf keys out every sample of full red, and painted
109
+ * whole its red bar stood beside the green and blue ones the page shows.
110
+ *
111
+ * @returns The alpha it comes to, or `undefined` where no sample is keyed out.
112
+ */
113
+ function colorKey(file, d, samples, n, px) {
114
+ const mask = file.resolve(d.get("Mask") ?? PDF_NULL);
115
+ if (!Array.isArray(mask) || mask.length < 2 * n) return void 0;
116
+ const ranges = mask.slice(0, 2 * n).map((v) => file.resolve(v));
117
+ if (!ranges.every((v) => typeof v === "number")) return void 0;
118
+ const data = new Uint8Array(px).fill(255);
119
+ let keyed = false;
120
+ for (let i = 0; i < px; i++) {
121
+ let inside = true;
122
+ for (let c = 0; c < n && inside; c++) {
123
+ const v = samples[i * n + c];
124
+ inside = v >= ranges[2 * c] && v <= ranges[2 * c + 1];
125
+ }
126
+ if (!inside) continue;
127
+ data[i] = 0;
128
+ keyed = true;
129
+ }
130
+ return keyed ? { data } : void 0;
96
131
  }
97
132
  /**
98
133
  * §7.4.7 — a `/JBIG2Decode` image: the stream's own segments, plus the shared
@@ -579,7 +614,7 @@ function unpackSamples(raw, width, height, ncomp, bpc) {
579
614
  */
580
615
  function maskedJpeg(file, d, jpegBytes, width, height) {
581
616
  const image = decodeJpeg(jpegBytes);
582
- if (!image) return void 0;
617
+ if (!image || image.components === 4) return void 0;
583
618
  const alpha = decodeSMask(file, d, image.width, image.height);
584
619
  if (!alpha) return void 0;
585
620
  const { color: pngColor, samples } = combineAlpha(image.components === 1 ? {
@@ -624,7 +659,7 @@ function decodeSMask(file, d, width, height) {
624
659
  /** A DCT-coded mask decoded to samples, or `undefined` past the baseline decoder. */
625
660
  function jpegSamples(bytes) {
626
661
  const decoded = decodeJpeg(bytes);
627
- if (!decoded) return void 0;
662
+ if (!decoded || decoded.components === 4) return void 0;
628
663
  return decoded.components === 1 ? {
629
664
  color: "gray",
630
665
  samples: decoded.samples
@@ -633,6 +668,37 @@ function jpegSamples(bytes) {
633
668
  samples: decoded.samples
634
669
  };
635
670
  }
671
+ /**
672
+ * §8.6.4.4 — a four-ink JPEG taken apart and given back as the colours it
673
+ * paints, through the image's colour space and its `/Decode` (§8.9.5.2) as any
674
+ * other sampled CMYK image is, with an `/SMask` folded in where it has one.
675
+ *
676
+ * @returns The picture as a PNG, or `undefined` where the JPEG is not four
677
+ * inks or is past the baseline decoder — it then travels as it is.
678
+ */
679
+ function cmykJpeg(file, d, jpegBytes, width, height) {
680
+ const image = decodeJpeg(jpegBytes);
681
+ if (image?.components !== 4) return void 0;
682
+ const cs = resolveColorSpace(file, d.get("ColorSpace") ?? d.get("CS"));
683
+ const inks = cs?.components === 4 ? cs : {
684
+ kind: "cmyk",
685
+ components: 4
686
+ };
687
+ const px = image.width * image.height;
688
+ const color = toColor(inks, Uint16Array.from(image.samples), px, 8, decodeArrayOf(file, d));
689
+ const alpha = hasSMask(file, d) ? decodeSMask(file, d, image.width, image.height) : void 0;
690
+ const { color: pngColor, samples } = alpha ? combineAlpha(color, alpha) : {
691
+ color: color.color,
692
+ samples: color.samples
693
+ };
694
+ return {
695
+ ok: true,
696
+ bytes: encodePng(image.width, image.height, pngColor, samples),
697
+ format: "png",
698
+ widthPx: width,
699
+ heightPx: height
700
+ };
701
+ }
636
702
  function combineAlpha(color, alpha) {
637
703
  const px = alpha.data.length;
638
704
  if (color.color === "gray") {
@@ -22,6 +22,11 @@ export interface PdfImage {
22
22
  * centre, which is what every downstream format does with one.
23
23
  */
24
24
  readonly rotationDeg?: number;
25
+ /**
26
+ * §8.9.5 — the CTM MIRRORS the picture: its first row stands at the foot of
27
+ * the box. Drawn as a flip top to bottom, and then the turn above.
28
+ */
29
+ readonly flipV?: boolean;
25
30
  /**
26
31
  * §8.5.4 — the fraction of each of the picture's OWN edges a clip cut away,
27
32
  * where one bounded it: `a:srcRect` in DrawingML terms. Absent is whole.
@@ -3,7 +3,7 @@ import { FEATURES } from "../core/ir/features.js";
3
3
  import { encodePng } from "../core/png-encode.js";
4
4
  import { buildAlphaMap, sampledShading } from "./shading.js";
5
5
  import { interpretContent, multiply } from "./content.js";
6
- import { collectPageAppearances } from "./annots.js";
6
+ import { appearanceContent, collectPageAppearances } from "./annots.js";
7
7
  import { hiddenProperties, hiddenXObject } from "./optional-content.js";
8
8
  import { buildFonts } from "./text.js";
9
9
  import { decodePdfImage } from "./image-decode.js";
@@ -139,7 +139,7 @@ function collectPageImages(file, page) {
139
139
  0
140
140
  ], 0, void 0, []);
141
141
  collectPageAppearances(file, page).forEach((appearance, index) => {
142
- walk(appearance.resources ?? page.resources, file.streamData(appearance.stream), appearance.ctm, 1, void 0, [Number.MAX_SAFE_INTEGER, index]);
142
+ walk(appearance.resources ?? page.resources, appearanceContent(file, appearance), appearance.ctm, 1, void 0, [Number.MAX_SAFE_INTEGER, index]);
143
143
  });
144
144
  return {
145
145
  images,
@@ -150,6 +150,7 @@ function geometry(ctm, decoded, mcid, clip, alpha) {
150
150
  const widthPt = Math.hypot(ctm[0], ctm[1]) || 1;
151
151
  const heightPt = Math.hypot(ctm[2], ctm[3]) || 1;
152
152
  const angle = Math.atan2(ctm[1], ctm[0]) * 180 / Math.PI;
153
+ const mirrored = ctm[0] * ctm[3] - ctm[1] * ctm[2] < 0;
153
154
  const box = clippedUnitBox(ctm, clip);
154
155
  const u = (box.u0 + box.u1) / 2;
155
156
  const v = (box.v0 + box.v1) / 2;
@@ -172,6 +173,7 @@ function geometry(ctm, decoded, mcid, clip, alpha) {
172
173
  y: cy - shownH / 2,
173
174
  ...crop ? { crop } : {},
174
175
  ...Math.abs(angle) > .5 ? { rotationDeg: angle } : {},
176
+ ...mirrored ? { flipV: true } : {},
175
177
  ...mcid !== void 0 ? { mcid } : {},
176
178
  ...alpha !== void 0 ? { alpha } : {}
177
179
  };
@@ -5,7 +5,7 @@ export interface Jbig2Bitmap {
5
5
  readonly data: Uint8Array;
6
6
  }
7
7
  /** A context's state: the Table E.1 index and which symbol is currently more probable. */
8
- interface Cx {
8
+ export interface Cx {
9
9
  i: Uint8Array;
10
10
  mps: Uint8Array;
11
11
  }
@@ -71,6 +71,19 @@ export declare function decodeGenericRegion(mq: MQDecoder, cx: Cx, width: number
71
71
  * @param dy Likewise, vertically.
72
72
  */
73
73
  export declare function decodeRefinement(mq: MQDecoder, cx: Cx, width: number, height: number, template: number, reference: Jbig2Bitmap, dx: number, dy: number, at: ReadonlyArray<At>, tpgron: boolean): Jbig2Bitmap;
74
+ /**
75
+ * §B.5 — the prefix every line of standard table B.`n` is written with, the
76
+ * way the spec prints it: a string of bits beside the values the line covers.
77
+ *
78
+ * @param n The table's number, 1–15.
79
+ * @returns Its lines, shortest prefix first, each with its prefix.
80
+ */
81
+ export declare function standardTableCodes(n: number): Array<{
82
+ prefix: string;
83
+ rangeLow: number;
84
+ rangeLen: number;
85
+ kind?: 'lower' | 'oob';
86
+ }>;
74
87
  /**
75
88
  * §6.4.5 — draw a text region: a run of strips, each holding instances of the
76
89
  * symbols in `symbols`, placed by running coordinates rather than absolute ones.
@@ -94,6 +107,32 @@ export declare function decodeRefinement(mq: MQDecoder, cx: Cx, width: number, h
94
107
  * @returns The number of bits an id takes.
95
108
  */
96
109
  export declare function symbolCodeLength(symbols: number): number;
110
+ /**
111
+ * §7.4.3.1.1 — the statistics a symbol dictionary codes its bitmaps with: the
112
+ * generic region's contexts and the refinement's. A dictionary may keep them
113
+ * when it is done (bit 9) and a later one start from them (bit 8) instead of
114
+ * from nothing.
115
+ */
116
+ export interface BitmapContexts {
117
+ readonly generic: Cx;
118
+ readonly refine: Cx;
119
+ }
120
+ /**
121
+ * §7.4.3.2 step 3 — the statistics a symbol dictionary starts from.
122
+ *
123
+ * One that says it USES the coding context starts where the LAST dictionary it
124
+ * refers to was left, among those that kept theirs: the bitmaps it codes were
125
+ * coded against those. Started fresh, every shape of
126
+ * bitmap-symbol-context-reuse.pdf decoded to paper and the sheet came back
127
+ * blank. It starts from a copy — two dictionaries may start from the same
128
+ * one, and the second must not start where the first left off.
129
+ *
130
+ * @param used Whether the dictionary's flags say it uses the context (bit 8).
131
+ * @param referred The segments it refers to, in the order it names them.
132
+ * @param kept What each dictionary that retained its context was left with.
133
+ * @returns The contexts to decode with.
134
+ */
135
+ export declare function startingContexts(used: boolean, referred: ReadonlyArray<number>, kept: ReadonlyMap<number, BitmapContexts>): BitmapContexts;
97
136
  /**
98
137
  * Decode an embedded JBIG2 image.
99
138
  *
@@ -726,8 +726,11 @@ function huffDecode(r, t) {
726
726
  * §B.5 Tables B.1–B.15 — the standard tables, as `[prefLen, rangeLen,
727
727
  * rangeLow]` with `'lower'` and `'oob'` on the two lines that need them.
728
728
  *
729
- * They are transcribed rather than derived, and the corpus is what checks
730
- * them: a table off by one line decodes rubble, and the suite says so at once.
729
+ * They are transcribed rather than derived, and a transcription can lose a
730
+ * line without a sound: B.7 came over four lines short and B.10 with five
731
+ * prefixes a bit or two long, and only the files that read through those two
732
+ * ever noticed. Every table is a complete prefix code — its lengths sum to
733
+ * exactly one, by Kraft — and that is what the tests hold each of them to.
731
734
  */
732
735
  var STANDARD_TABLES = [
733
736
  [
@@ -1020,18 +1023,38 @@ var STANDARD_TABLES = [
1020
1023
  },
1021
1024
  {
1022
1025
  prefLen: 4,
1023
- rangeLen: 9,
1026
+ rangeLen: 5,
1024
1027
  rangeLow: 0
1025
1028
  },
1026
1029
  {
1027
1030
  prefLen: 5,
1028
- rangeLen: 10,
1031
+ rangeLen: 5,
1032
+ rangeLow: 32
1033
+ },
1034
+ {
1035
+ prefLen: 5,
1036
+ rangeLen: 6,
1037
+ rangeLow: 64
1038
+ },
1039
+ {
1040
+ prefLen: 4,
1041
+ rangeLen: 7,
1042
+ rangeLow: 128
1043
+ },
1044
+ {
1045
+ prefLen: 3,
1046
+ rangeLen: 8,
1047
+ rangeLow: 256
1048
+ },
1049
+ {
1050
+ prefLen: 3,
1051
+ rangeLen: 9,
1029
1052
  rangeLow: 512
1030
1053
  },
1031
1054
  {
1032
1055
  prefLen: 3,
1033
1056
  rangeLen: 10,
1034
- rangeLow: 1536
1057
+ rangeLow: 1024
1035
1058
  },
1036
1059
  {
1037
1060
  prefLen: 5,
@@ -1042,7 +1065,7 @@ var STANDARD_TABLES = [
1042
1065
  {
1043
1066
  prefLen: 5,
1044
1067
  rangeLen: 32,
1045
- rangeLow: 2560
1068
+ rangeLow: 2048
1046
1069
  }
1047
1070
  ],
1048
1071
  [
@@ -1330,27 +1353,27 @@ var STANDARD_TABLES = [
1330
1353
  rangeLow: 102
1331
1354
  },
1332
1355
  {
1333
- prefLen: 7,
1356
+ prefLen: 6,
1334
1357
  rangeLen: 6,
1335
1358
  rangeLow: 134
1336
1359
  },
1337
1360
  {
1338
- prefLen: 8,
1361
+ prefLen: 6,
1339
1362
  rangeLen: 7,
1340
1363
  rangeLow: 198
1341
1364
  },
1342
1365
  {
1343
- prefLen: 8,
1366
+ prefLen: 6,
1344
1367
  rangeLen: 8,
1345
1368
  rangeLow: 326
1346
1369
  },
1347
1370
  {
1348
- prefLen: 8,
1371
+ prefLen: 6,
1349
1372
  rangeLen: 9,
1350
1373
  rangeLow: 582
1351
1374
  },
1352
1375
  {
1353
- prefLen: 8,
1376
+ prefLen: 6,
1354
1377
  rangeLen: 10,
1355
1378
  rangeLow: 1094
1356
1379
  },
@@ -1879,15 +1902,15 @@ function decodeTextRegion(mq, symbols, p, stripsLog, lent) {
1879
1902
  *
1880
1903
  * @param input The symbols this dictionary inherits from the ones it refers to.
1881
1904
  */
1882
- function decodeSymbolDictionary(mq, input, newCount, exportCount, template, at, refAgg, rTemplate, rAt) {
1905
+ function decodeSymbolDictionary(mq, input, newCount, exportCount, template, at, refAgg, rTemplate, rAt, contexts = freshBitmapContexts()) {
1883
1906
  const iadh = new IntDecoder(mq);
1884
1907
  const iadw = new IntDecoder(mq);
1885
1908
  const iaex = new IntDecoder(mq);
1886
1909
  const iaai = new IntDecoder(mq);
1887
1910
  const iardx = new IntDecoder(mq);
1888
1911
  const iardy = new IntDecoder(mq);
1889
- const genericCx = newContexts(65536);
1890
- const refineCx = newContexts(8192);
1912
+ const genericCx = contexts.generic;
1913
+ const refineCx = contexts.refine;
1891
1914
  const newSymbols = [];
1892
1915
  const num = (v) => v === OOB ? 0 : v;
1893
1916
  const codeLen = symbolCodeLength(input.length + newCount);
@@ -2365,6 +2388,41 @@ function compose(dst, src, x0, y0, op) {
2365
2388
  }
2366
2389
  }
2367
2390
  }
2391
+ function freshBitmapContexts() {
2392
+ return {
2393
+ generic: newContexts(65536),
2394
+ refine: newContexts(8192)
2395
+ };
2396
+ }
2397
+ /**
2398
+ * §7.4.3.2 step 3 — the statistics a symbol dictionary starts from.
2399
+ *
2400
+ * One that says it USES the coding context starts where the LAST dictionary it
2401
+ * refers to was left, among those that kept theirs: the bitmaps it codes were
2402
+ * coded against those. Started fresh, every shape of
2403
+ * bitmap-symbol-context-reuse.pdf decoded to paper and the sheet came back
2404
+ * blank. It starts from a copy — two dictionaries may start from the same
2405
+ * one, and the second must not start where the first left off.
2406
+ *
2407
+ * @param used Whether the dictionary's flags say it uses the context (bit 8).
2408
+ * @param referred The segments it refers to, in the order it names them.
2409
+ * @param kept What each dictionary that retained its context was left with.
2410
+ * @returns The contexts to decode with.
2411
+ */
2412
+ function startingContexts(used, referred, kept) {
2413
+ const last = used ? referred.map((n) => kept.get(n)).filter((c) => c !== void 0).at(-1) : void 0;
2414
+ if (last === void 0) return freshBitmapContexts();
2415
+ return {
2416
+ generic: {
2417
+ i: last.generic.i.slice(),
2418
+ mps: last.generic.mps.slice()
2419
+ },
2420
+ refine: {
2421
+ i: last.refine.i.slice(),
2422
+ mps: last.refine.mps.slice()
2423
+ }
2424
+ };
2425
+ }
2368
2426
  /**
2369
2427
  * Decode an embedded JBIG2 image.
2370
2428
  *
@@ -2391,6 +2449,7 @@ function decodeJbig2(data, globals, width, height) {
2391
2449
  const symbolsBySegment = /* @__PURE__ */ new Map();
2392
2450
  const patternsBySegment = /* @__PURE__ */ new Map();
2393
2451
  const tablesBySegment = /* @__PURE__ */ new Map();
2452
+ const contextsBySegment = /* @__PURE__ */ new Map();
2394
2453
  const run = (bytes) => {
2395
2454
  for (const seg of parseSegments(bytes)) {
2396
2455
  const r = new Reader(bytes.subarray(seg.start, seg.end));
@@ -2496,7 +2555,8 @@ function decodeJbig2(data, globals, width, height) {
2496
2555
  const refAgg = (flags & 2) !== 0;
2497
2556
  const template = flags >> 10 & 3;
2498
2557
  const rTemplate = flags >> 12 & 1;
2499
- flags & 256;
2558
+ const ctxUsed = (flags & 256) !== 0;
2559
+ const ctxRetained = (flags & 512) !== 0;
2500
2560
  const at = [];
2501
2561
  if (!huff) {
2502
2562
  const n = template === 0 ? 4 : 1;
@@ -2529,7 +2589,9 @@ function decodeJbig2(data, globals, width, height) {
2529
2589
  } : void 0));
2530
2590
  continue;
2531
2591
  }
2532
- symbolsBySegment.set(seg.number, decodeSymbolDictionary(new MQDecoder(bytes.subarray(seg.start + r.pos, seg.end)), inherited, newCount, exportCount, template, at.length > 0 ? at : NOMINAL_AT[template] ?? [], refAgg, rTemplate, rAt.length > 0 ? rAt : NOMINAL_AT_REFINE));
2592
+ const contexts = startingContexts(ctxUsed, seg.referred, contextsBySegment);
2593
+ symbolsBySegment.set(seg.number, decodeSymbolDictionary(new MQDecoder(bytes.subarray(seg.start + r.pos, seg.end)), inherited, newCount, exportCount, template, at.length > 0 ? at : NOMINAL_AT[template] ?? [], refAgg, rTemplate, rAt.length > 0 ? rAt : NOMINAL_AT_REFINE, contexts));
2594
+ if (ctxRetained) contextsBySegment.set(seg.number, contexts);
2533
2595
  continue;
2534
2596
  }
2535
2597
  if (seg.type === 4 || seg.type === 6 || seg.type === 7) {
@@ -1,9 +1,12 @@
1
- /** A decoded JPEG: 8-bit interleaved samples, one or three components. */
1
+ /** A decoded JPEG: 8-bit interleaved samples, one, three or four components. */
2
2
  export interface DecodedJpeg {
3
3
  readonly width: number;
4
4
  readonly height: number;
5
- /** 1 = grayscale, 3 = RGB (already converted from YCbCr where it applies). */
6
- readonly components: 1 | 3;
5
+ /**
6
+ * 1 = grayscale, 3 = RGB (already converted from YCbCr where it applies),
7
+ * 4 = CMYK as the stream stores it (converted from YCCK where it applies).
8
+ */
9
+ readonly components: 1 | 3 | 4;
7
10
  /** `width * height * components` bytes, row-major. */
8
11
  readonly samples: Uint8Array;
9
12
  }
@@ -151,7 +151,7 @@ function readFrame(bytes, p) {
151
151
  const width = be16(bytes, p + 3);
152
152
  const count = bytes[p + 5];
153
153
  if (width <= 0 || height <= 0 || width * height > MAX_PIXELS) return void 0;
154
- if (count !== 1 && count !== 3) return void 0;
154
+ if (count !== 1 && count !== 3 && count !== 4) return void 0;
155
155
  const comps = [];
156
156
  let maxH = 1;
157
157
  let maxV = 1;
@@ -393,6 +393,15 @@ function assemble(frame, adobeTransform) {
393
393
  components: 1,
394
394
  samples
395
395
  };
396
+ if (n === 4) {
397
+ if (adobeTransform === 2) ycckToCmyk(samples);
398
+ return {
399
+ width,
400
+ height,
401
+ components: 4,
402
+ samples
403
+ };
404
+ }
396
405
  if (adobeTransform !== 0) ycbcrToRgb(samples);
397
406
  return {
398
407
  width,
@@ -401,6 +410,17 @@ function assemble(frame, adobeTransform) {
401
410
  samples
402
411
  };
403
412
  }
413
+ /** Adobe's YCCK → CMYK, in place: the YCbCr → RGB conversion, complemented. */
414
+ function ycckToCmyk(s) {
415
+ for (let i = 0; i < s.length; i += 4) {
416
+ const y = s[i];
417
+ const cb = s[i + 1] - 128;
418
+ const cr = s[i + 2] - 128;
419
+ s[i] = 255 - clamp(y + 1.402 * cr);
420
+ s[i + 1] = 255 - clamp(y - .344136 * cb - .714136 * cr);
421
+ s[i + 2] = 255 - clamp(y + 1.772 * cb);
422
+ }
423
+ }
404
424
  /** ITU-T T.871 §7 — the JFIF YCbCr → RGB conversion, in place. */
405
425
  function ycbcrToRgb(s) {
406
426
  for (let i = 0; i < s.length; i += 3) {
@@ -1,3 +1,4 @@
1
+ import { PdfVector } from './vector.js';
1
2
  import { PdfFile } from './document.js';
2
3
  import { Reconstruction } from './flow-build.js';
3
4
  /** §9.10.2 — a glyph the face maps to no character (see `./font`). */
@@ -33,8 +34,14 @@ export declare function reconstructByLayout(file: PdfFile, mode?: 'flow' | 'posi
33
34
  * placed rather than set — a centred title's every line is short of the measure
34
35
  * and none of them ends anything.
35
36
  *
36
- * @param prev The line before: where it starts, how wide it is, its face.
37
- * @param next The line after — only where it starts matters.
37
+ * A line of a right-to-left script starts at the RIGHT and ends at the left,
38
+ * and is read the other way round: ArabicCIDTrueType.pdf sets four lines, each
39
+ * flush right and shorter than the one before, and read as a left-to-right
40
+ * setting every pair of them ran together as one paragraph.
41
+ *
42
+ * @param prev The line before: where it starts, how wide it is, its face,
43
+ * and its text when it has any.
44
+ * @param next The line after — where it starts, and how wide it is.
38
45
  * @param column The measure both were set in, when it is known.
39
46
  * @returns Whether the first line ended a paragraph.
40
47
  */
@@ -42,9 +49,26 @@ export declare function endedParagraph(prev: {
42
49
  x: number;
43
50
  width: number;
44
51
  fontSize: number;
52
+ text?: string;
45
53
  }, next: {
46
54
  x: number;
55
+ width?: number;
47
56
  }, column: {
48
57
  left: number;
49
58
  right: number;
50
59
  } | undefined): boolean;
60
+ /**
61
+ * §9.6.6 — the glyphs a page draws for want of characters, gathered into the
62
+ * WORDS they stand in: a traced glyph that stands against the one before it,
63
+ * on its line and in its colour, is drawn as one shape with it.
64
+ *
65
+ * Every mark kept apart is a shape of its own in the document, anchored to
66
+ * the page — and a paper set in a subset whose glyphs name nothing is nothing
67
+ * but such marks: TAMReview.pdf came back as forty-two thousand shapes, a
68
+ * package no reader would open in under three minutes. The picture is the
69
+ * same either way.
70
+ *
71
+ * @param vectors The page's painted paths, in painting order.
72
+ * @returns The same paths, each run of traced glyphs made one.
73
+ */
74
+ export declare function drawnWords(vectors: ReadonlyArray<PdfVector>): Array<PdfVector>;