reamkit 1.26.0 → 1.28.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 (85) hide show
  1. package/README.md +14 -8
  2. package/dist/esm/core/converter/ream.d.ts +0 -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.d.ts +65 -0
  17. package/dist/esm/pdf-reader/annot-draw.js +487 -0
  18. package/dist/esm/pdf-reader/annots.d.ts +0 -12
  19. package/dist/esm/pdf-reader/annots.js +30 -4
  20. package/dist/esm/pdf-reader/ccitt.d.ts +20 -3
  21. package/dist/esm/pdf-reader/ccitt.js +102 -6
  22. package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
  23. package/dist/esm/pdf-reader/cff-outline.js +1122 -0
  24. package/dist/esm/pdf-reader/cie-color.d.ts +33 -0
  25. package/dist/esm/pdf-reader/cie-color.js +112 -0
  26. package/dist/esm/pdf-reader/cmap.js +5 -2
  27. package/dist/esm/pdf-reader/content.d.ts +123 -3
  28. package/dist/esm/pdf-reader/content.js +232 -54
  29. package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
  30. package/dist/esm/pdf-reader/dingbats.js +1033 -0
  31. package/dist/esm/pdf-reader/display.d.ts +1 -1
  32. package/dist/esm/pdf-reader/display.js +61 -6
  33. package/dist/esm/pdf-reader/document.d.ts +6 -0
  34. package/dist/esm/pdf-reader/document.js +55 -10
  35. package/dist/esm/pdf-reader/embedded-fonts.d.ts +23 -3
  36. package/dist/esm/pdf-reader/embedded-fonts.js +36 -4
  37. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  38. package/dist/esm/pdf-reader/encodings.js +110 -0
  39. package/dist/esm/pdf-reader/flow-build.d.ts +19 -5
  40. package/dist/esm/pdf-reader/flow-build.js +111 -11
  41. package/dist/esm/pdf-reader/font.js +403 -21
  42. package/dist/esm/pdf-reader/function.d.ts +16 -0
  43. package/dist/esm/pdf-reader/function.js +414 -0
  44. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  45. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  46. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  47. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  48. package/dist/esm/pdf-reader/icc.js +210 -0
  49. package/dist/esm/pdf-reader/image-decode.d.ts +9 -4
  50. package/dist/esm/pdf-reader/image-decode.js +274 -72
  51. package/dist/esm/pdf-reader/images.d.ts +18 -0
  52. package/dist/esm/pdf-reader/images.js +154 -11
  53. package/dist/esm/pdf-reader/jbig2.d.ts +23 -0
  54. package/dist/esm/pdf-reader/jbig2.js +126 -32
  55. package/dist/esm/pdf-reader/layout.d.ts +32 -0
  56. package/dist/esm/pdf-reader/layout.js +1316 -64
  57. package/dist/esm/pdf-reader/lexer.d.ts +2 -0
  58. package/dist/esm/pdf-reader/lexer.js +4 -0
  59. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  60. package/dist/esm/pdf-reader/math-rows.js +198 -0
  61. package/dist/esm/pdf-reader/optional-content.d.ts +36 -0
  62. package/dist/esm/pdf-reader/optional-content.js +93 -0
  63. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  64. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  65. package/dist/esm/pdf-reader/reader.d.ts +6 -6
  66. package/dist/esm/pdf-reader/reader.js +102 -16
  67. package/dist/esm/pdf-reader/shading.d.ts +139 -8
  68. package/dist/esm/pdf-reader/shading.js +309 -39
  69. package/dist/esm/pdf-reader/standard-metrics.d.ts +8 -0
  70. package/dist/esm/pdf-reader/standard-metrics.js +18 -0
  71. package/dist/esm/pdf-reader/standard-widths.d.ts +20 -0
  72. package/dist/esm/pdf-reader/standard-widths.js +62 -0
  73. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  74. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  75. package/dist/esm/pdf-reader/tagged.js +204 -32
  76. package/dist/esm/pdf-reader/text-rules.d.ts +16 -0
  77. package/dist/esm/pdf-reader/text-rules.js +185 -0
  78. package/dist/esm/pdf-reader/text.js +157 -4
  79. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  80. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  81. package/dist/esm/pdf-reader/vector.d.ts +5 -0
  82. package/dist/esm/pdf-reader/vector.js +79 -27
  83. package/dist/esm/word/document-parser.js +4 -1
  84. package/dist/esm/word/docx-writer.js +74 -11
  85. package/package.json +1 -1
@@ -1,5 +1,9 @@
1
+ import { CieSpace } from './cie-color.js';
2
+ import { IccTransform } from './icc.js';
3
+ import { PdfFunction } from './function.js';
1
4
  import { ShapeGradient } from '../core/vector.js';
2
- import { PdfFile, PdfPage } from './document.js';
5
+ import { PdfDict } from '../pdf/objects.js';
6
+ import { PdfFile } from './document.js';
3
7
  /**
4
8
  * Resolve a page's `/Pattern` resources into gradient fills (E-PDF EP16c, ISO
5
9
  * 32000-1 §8.7.4.5). Every `PatternType` 2 (shading) pattern is evaluated — its
@@ -8,9 +12,76 @@ import { PdfFile, PdfPage } from './document.js';
8
12
  * filled with `/Pattern cs /Pn scn`. The bare `sh` operator (clip-bounded) is
9
13
  * not captured.
10
14
  *
15
+ * @param file The owning file.
16
+ * @param resources The resource dictionary in force — a page's, or the form or
17
+ * annotation appearance's own.
11
18
  * @returns A map from pattern resource name to its {@link ShapeGradient}.
12
19
  */
13
- export declare function buildShadingMap(file: PdfFile, page: PdfPage): Map<string, ShapeGradient>;
20
+ export declare function buildShadingMap(file: PdfFile, resources: PdfDict | undefined): Map<string, ShapeGradient>;
21
+ /**
22
+ * §8.7.4.5.2 — an axial or radial shading as a gradient, for a bare `sh`.
23
+ *
24
+ * A `sh` paints the CLIP rather than a path, so what it needs is not a fill for
25
+ * a shape the page drew but the gradient itself, to fill the region with. It is
26
+ * the same reading `buildShadingMap` does for a pattern.
27
+ *
28
+ * @param file The owning file.
29
+ * @param sh The shading dictionary.
30
+ * @returns The gradient, or `undefined` for a type this does not read.
31
+ */
32
+ export declare function gradientShading(file: PdfFile, sh: PdfDict): ShapeGradient | undefined;
33
+ /**
34
+ * §8.7.4.5 — which kind of shading this is, as the file states it.
35
+ *
36
+ * @param file The owning file.
37
+ * @param sh The shading dictionary.
38
+ * @returns Its `/ShadingType`, or 0 where the file states none.
39
+ */
40
+ export declare function shadingTypeOf(file: PdfFile, sh: PdfDict): number;
41
+ /**
42
+ * §8.7.4.5.3 — a FUNCTION-BASED shading, sampled into a picture.
43
+ *
44
+ * Type 1 is not a ramp between two points: it is a function of two variables
45
+ * over a rectangle, and no gradient can stand for one. Painted by a bare `sh`
46
+ * it fills the clip, and nothing here lifted it at all —
47
+ * function_based_shading.pdf is nine such squares and 43% of the page's ink,
48
+ * and reconstructed to a blank sheet.
49
+ *
50
+ * Sampled it is exactly a picture, which every format downstream can show. The
51
+ * grid is fixed: the function is smooth by construction (§8.7.4.5.3 gives it a
52
+ * `/Domain` and nothing else), so more samples buy nothing a reader can see.
53
+ *
54
+ * @param file The owning file.
55
+ * @param shading The shading dictionary.
56
+ * @returns The picture and the domain it covers, or `undefined` for a shading
57
+ * of another type or one whose function cannot be run.
58
+ */
59
+ export declare function sampledShading(file: PdfFile, shading: PdfDict): {
60
+ rgb: Uint8Array;
61
+ size: number;
62
+ domain: [number, number, number, number];
63
+ } | undefined;
64
+ /**
65
+ * §8.6.8 — the colour a run of `sc` / `scn` components comes to.
66
+ *
67
+ * The space in force decides, and where it was not read the COUNT is the next
68
+ * best witness: three numbers are RGB and four are CMYK on every device space
69
+ * there is. One number is the ambiguous case — grey in a device space, but the
70
+ * strength of a colorant in a Separation, where 1 is the ink at full and reads
71
+ * dark — so a lone component is only taken where the space said what it means.
72
+ *
73
+ * @param nums The components, as the page or a shading's function states them.
74
+ * @param space The space in force, where it was read.
75
+ * @returns The colour as 6 hex digits, or `undefined` where the numbers do not
76
+ * say what colour they are.
77
+ */
78
+ export declare function spaceColor(nums: ReadonlyArray<number>, space: ColorSpaceInfo | undefined): string | undefined;
79
+ /** Three channels, each 0..1, as 6 upper-case hex digits. */
80
+ export declare function rgbHex(r: number, g: number, b: number): string;
81
+ /** One grey level, 0..1, as 6 upper-case hex digits. */
82
+ export declare function grayHex(v: number): string;
83
+ /** §8.6.4.4 — four inks, each 0..1, as 6 upper-case hex digits. */
84
+ export declare function cmykHex(c: number, m: number, y: number, k: number): string;
14
85
  /**
15
86
  * §11.6.4.4 — the constant fill alpha (`/ca`) of every `/ExtGState` the page
16
87
  * names, by name.
@@ -20,11 +91,44 @@ export declare function buildShadingMap(file: PdfFile, page: PdfPage): Map<strin
20
91
  * a green band at `ca` 0.6, meant to be read THROUGH: painted solid, the floor
21
92
  * plan under each band disappears.
22
93
  *
23
- * @param file The owning file.
24
- * @param page The page whose `/ExtGState` resources are wanted.
25
- * @returns Name → fill alpha, for the states that state one below 1.
94
+ * §11.3.5 `/BM` comes off the same dictionary. A blend nothing downstream can
95
+ * perform is still worth knowing: `Multiply` and `Darken` both let dark ink
96
+ * under the paint show through, which is what a highlighter IS, and a mark that
97
+ * only darkens belongs UNDER the words rather than over them.
98
+ *
99
+ * @param file The owning file.
100
+ * @param resources The resource dictionary in force — a page's, or the form or
101
+ * annotation appearance's own, since `gs` resolves against
102
+ * whichever is current.
103
+ * @returns Name → the fill alpha and blend worth carrying, for the states that
104
+ * state one.
26
105
  */
27
- export declare function buildAlphaMap(file: PdfFile, page: PdfPage): Map<string, number>;
106
+ export declare function buildAlphaMap(file: PdfFile, resources: PdfDict | undefined): Map<string, GsPaint>;
107
+ /**
108
+ * What a `/ExtGState` says about paint that the reconstruction can carry.
109
+ *
110
+ * Every field is absent when the state does not NAME that parameter, because a
111
+ * `gs` leaves what it does not name alone (§8.4.5).
112
+ */
113
+ export interface GsPaint {
114
+ /** §11.6.4.4 `/ca` — the constant fill alpha, where the state names one. */
115
+ readonly alpha?: number;
116
+ /** §11.3.5 — whether `/BM` is named at all, since naming `/Normal` ends a blend. */
117
+ readonly statesBlend?: boolean;
118
+ /** §11.3.5 `/BM` — the paint only darkens, so what it covers shows through. */
119
+ readonly darkens?: boolean;
120
+ /**
121
+ * §11.3.5 `/BM` — a blend NOTHING downstream can perform, named so the loss
122
+ * report can say which. `Normal` is no blend at all and is never named here.
123
+ */
124
+ readonly blend?: string;
125
+ /**
126
+ * §11.6.5 `/SMask` — the paint's opacity varies from place to place, out of
127
+ * another group's luminosity or alpha. Nothing downstream has a mask like it.
128
+ * `false` where the state names `/None`, which takes a mask off.
129
+ */
130
+ readonly masked?: boolean;
131
+ }
28
132
  /**
29
133
  * §8.6 — how many components a colour space takes, and what they mean.
30
134
  *
@@ -35,6 +139,33 @@ export declare function buildAlphaMap(file: PdfFile, page: PdfPage): Map<string,
35
139
  export interface ColorSpaceInfo {
36
140
  readonly kind: 'gray' | 'rgb' | 'cmyk' | 'tint';
37
141
  readonly components: number;
142
+ /**
143
+ * §8.6.5.6/§8.6.5.7 — the CIE parameters, for a `CalGray` or `CalRGB` space.
144
+ * Its numbers look like a device space's and are not: they mean what comes
145
+ * out of this transform.
146
+ */
147
+ readonly cie?: CieSpace;
148
+ /**
149
+ * §8.6.5.5 — the transform an `/ICCBased` profile states, where its form is
150
+ * one this reads. Its numbers mean what the PROFILE makes of them, which for
151
+ * anything but an sRGB-like profile is not what a device space would.
152
+ */
153
+ readonly icc?: IccTransform;
154
+ /**
155
+ * §8.6.6.4/§8.6.6.5 — for a `Separation` or `DeviceN`, the way OUT of it: the
156
+ * tint transform and the space its numbers land in. Absent where the file
157
+ * states a transform this cannot run, and then a tint is only "this much ink".
158
+ */
159
+ readonly tint?: {
160
+ readonly transform: PdfFunction;
161
+ readonly alternate: ColorSpaceInfo;
162
+ };
38
163
  }
39
- /** The spaces a page's `/ColorSpace` resources name, by name. */
40
- export declare function buildColorSpaceMap(file: PdfFile, page: PdfPage): Map<string, ColorSpaceInfo>;
164
+ /**
165
+ * The spaces a `/ColorSpace` resource dictionary names, by name.
166
+ *
167
+ * @param file The owning file.
168
+ * @param resources The resource dictionary in force.
169
+ * @returns Name → what the space comes to, for the spaces that are read.
170
+ */
171
+ export declare function buildColorSpaceMap(file: PdfFile, resources: PdfDict | undefined): Map<string, ColorSpaceInfo>;
@@ -1,4 +1,7 @@
1
1
  import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
2
+ import { cieToSrgb } from "./cie-color.js";
3
+ import { iccTransform } from "./icc.js";
4
+ import { readFunction } from "./function.js";
2
5
  //#region src/pdf-reader/shading.ts
3
6
  /**
4
7
  * Resolve a page's `/Pattern` resources into gradient fills (E-PDF EP16c, ISO
@@ -8,12 +11,15 @@ import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
8
11
  * filled with `/Pattern cs /Pn scn`. The bare `sh` operator (clip-bounded) is
9
12
  * not captured.
10
13
  *
14
+ * @param file The owning file.
15
+ * @param resources The resource dictionary in force — a page's, or the form or
16
+ * annotation appearance's own.
11
17
  * @returns A map from pattern resource name to its {@link ShapeGradient}.
12
18
  */
13
- function buildShadingMap(file, page) {
19
+ function buildShadingMap(file, resources) {
14
20
  const out = /* @__PURE__ */ new Map();
15
- if (!page.resources) return out;
16
- const patterns = file.get(page.resources, "Pattern");
21
+ if (!resources) return out;
22
+ const patterns = file.get(resources, "Pattern");
17
23
  if (!(patterns instanceof Map)) return out;
18
24
  for (const [nm, value] of patterns) {
19
25
  const pat = file.resolve(value);
@@ -25,10 +31,34 @@ function buildShadingMap(file, page) {
25
31
  }
26
32
  return out;
27
33
  }
34
+ /**
35
+ * §8.7.4.5.2 — an axial or radial shading as a gradient, for a bare `sh`.
36
+ *
37
+ * A `sh` paints the CLIP rather than a path, so what it needs is not a fill for
38
+ * a shape the page drew but the gradient itself, to fill the region with. It is
39
+ * the same reading `buildShadingMap` does for a pattern.
40
+ *
41
+ * @param file The owning file.
42
+ * @param sh The shading dictionary.
43
+ * @returns The gradient, or `undefined` for a type this does not read.
44
+ */
45
+ function gradientShading(file, sh) {
46
+ return parseShading(file, sh);
47
+ }
48
+ /**
49
+ * §8.7.4.5 — which kind of shading this is, as the file states it.
50
+ *
51
+ * @param file The owning file.
52
+ * @param sh The shading dictionary.
53
+ * @returns Its `/ShadingType`, or 0 where the file states none.
54
+ */
55
+ function shadingTypeOf(file, sh) {
56
+ return numOf(file.get(sh, "ShadingType"));
57
+ }
28
58
  function parseShading(file, sh) {
29
59
  const type = numOf(file.get(sh, "ShadingType"));
30
60
  if (type !== 2 && type !== 3) return void 0;
31
- const stops = parseFunction(file, sh.get("Function"));
61
+ const stops = parseFunction(file, sh.get("Function"), shadingSpace(file, sh));
32
62
  if (!stops || stops.length === 0) return void 0;
33
63
  if (type === 3) return {
34
64
  kind: "radial",
@@ -41,14 +71,14 @@ function parseShading(file, sh) {
41
71
  stops
42
72
  };
43
73
  }
44
- function parseFunction(file, value) {
74
+ function parseFunction(file, value, space) {
45
75
  const resolved = value !== void 0 ? file.resolve(value) : void 0;
46
76
  const dict = dictOf(resolved);
47
77
  if (!dict) return void 0;
48
78
  const type = numOf(file.get(dict, "FunctionType"));
49
79
  if (type === 2) {
50
- const c0 = colorOf(numArray(file, dict.get("C0")) ?? [0]);
51
- const c1 = colorOf(numArray(file, dict.get("C1")) ?? [1]);
80
+ const c0 = colorOf(numArray(file, dict.get("C0")) ?? [0], space);
81
+ const c1 = colorOf(numArray(file, dict.get("C1")) ?? [1], space);
52
82
  return [{
53
83
  offset: 0,
54
84
  colorHex: c0
@@ -73,16 +103,100 @@ function parseFunction(file, value) {
73
103
  const stops = [];
74
104
  const span = d1 - d0 || 1;
75
105
  for (let i = 0; i < subs.length; i++) {
76
- const sub = parseFunction(file, subs[i]);
77
- if (!sub || sub.length < 2) continue;
78
- pushStop(stops, ((edges[i] ?? d0) - d0) / span, sub[0].colorHex);
79
- pushStop(stops, ((edges[i + 1] ?? d1) - d0) / span, sub[sub.length - 1].colorHex);
106
+ const sub = parseFunction(file, subs[i], space);
107
+ if (!sub || sub.length === 0) continue;
108
+ const lo = ((edges[i] ?? d0) - d0) / span;
109
+ const hi = ((edges[i + 1] ?? d1) - d0) / span;
110
+ for (const stop of sub) pushStop(stops, lo + stop.offset * (hi - lo), stop.colorHex);
80
111
  }
81
112
  return stops.length > 0 ? stops : void 0;
82
113
  }
83
- if (type === 0 && resolved instanceof PdfStream) return sampleFunction(file, resolved);
114
+ if (type === 0 && resolved instanceof PdfStream) return sampleFunction(file, resolved, space);
115
+ if (type === 4) {
116
+ const fn = readFunction(file, resolved);
117
+ if (!fn) return void 0;
118
+ const domain = numArray(file, dict.get("Domain")) ?? [0, 1];
119
+ const d0 = domain[0] ?? 0;
120
+ const d1 = domain[1] ?? 1;
121
+ const stops = [];
122
+ for (let s = 0; s < PS_STOPS; s++) {
123
+ const off = s / (PS_STOPS - 1);
124
+ pushStop(stops, off, colorOf(fn([d0 + off * (d1 - d0)]), space));
125
+ }
126
+ return stops.length > 0 ? stops : void 0;
127
+ }
128
+ }
129
+ /**
130
+ * §8.7.4.5.3 — a FUNCTION-BASED shading, sampled into a picture.
131
+ *
132
+ * Type 1 is not a ramp between two points: it is a function of two variables
133
+ * over a rectangle, and no gradient can stand for one. Painted by a bare `sh`
134
+ * it fills the clip, and nothing here lifted it at all —
135
+ * function_based_shading.pdf is nine such squares and 43% of the page's ink,
136
+ * and reconstructed to a blank sheet.
137
+ *
138
+ * Sampled it is exactly a picture, which every format downstream can show. The
139
+ * grid is fixed: the function is smooth by construction (§8.7.4.5.3 gives it a
140
+ * `/Domain` and nothing else), so more samples buy nothing a reader can see.
141
+ *
142
+ * @param file The owning file.
143
+ * @param shading The shading dictionary.
144
+ * @returns The picture and the domain it covers, or `undefined` for a shading
145
+ * of another type or one whose function cannot be run.
146
+ */
147
+ function sampledShading(file, shading) {
148
+ if (numOf(file.get(shading, "ShadingType")) !== 1) return void 0;
149
+ const space = shadingSpace(file, shading);
150
+ const fn = readFunction(file, shading.get("Function"));
151
+ if (!fn) return void 0;
152
+ const d = numArray(file, shading.get("Domain")) ?? [
153
+ 0,
154
+ 1,
155
+ 0,
156
+ 1
157
+ ];
158
+ if (d.length < 4) return void 0;
159
+ const domain = [
160
+ d[0],
161
+ d[1],
162
+ d[2],
163
+ d[3]
164
+ ];
165
+ const size = SAMPLED_SIDE;
166
+ const rgb = new Uint8Array(size * size * 3);
167
+ for (let row = 0; row < size; row++) {
168
+ const y = domain[3] - (row + .5) / size * (domain[3] - domain[2]);
169
+ for (let col = 0; col < size; col++) {
170
+ const hex = colorOf(fn([domain[0] + (col + .5) / size * (domain[1] - domain[0]), y]), space);
171
+ const at = (row * size + col) * 3;
172
+ rgb[at] = Number.parseInt(hex.slice(0, 2), 16);
173
+ rgb[at + 1] = Number.parseInt(hex.slice(2, 4), 16);
174
+ rgb[at + 2] = Number.parseInt(hex.slice(4, 6), 16);
175
+ }
176
+ }
177
+ return {
178
+ rgb,
179
+ size,
180
+ domain
181
+ };
182
+ }
183
+ /**
184
+ * §8.7.4.5 — the colour space a shading's function lands in.
185
+ *
186
+ * The components alone do not say: a `/Separation` function gives ONE number
187
+ * and that number is a strength of ink, not a grey level.
188
+ * function_based_shading_cmyk.pdf's third square is a spot colour whose full
189
+ * tint is a warm red, and read as grey it came back a black-to-white ramp.
190
+ */
191
+ function shadingSpace(file, sh) {
192
+ const cs = sh.get("ColorSpace");
193
+ return cs === void 0 ? void 0 : colorSpaceAt(file, file.resolve(cs), 0);
84
194
  }
85
- function sampleFunction(file, stream) {
195
+ /** How many samples a side a function-based shading is drawn at. */
196
+ var SAMPLED_SIDE = 128;
197
+ /** How many places a type-4 gradient is sampled at along its domain. */
198
+ var PS_STOPS = 16;
199
+ function sampleFunction(file, stream, space) {
86
200
  const d = stream.dict;
87
201
  const size = numArray(file, d.get("Size"));
88
202
  const range = numArray(file, d.get("Range"));
@@ -129,18 +243,63 @@ function numArray(file, v) {
129
243
  if (!Array.isArray(r)) return void 0;
130
244
  return r.map((x) => typeof x === "number" ? x : 0);
131
245
  }
132
- function colorOf(c) {
133
- if (c.length >= 4) {
134
- const k = c[3];
135
- return hex255(255 * (1 - c[0]) * (1 - k), 255 * (1 - c[1]) * (1 - k), 255 * (1 - c[2]) * (1 - k));
246
+ function colorOf(c, space) {
247
+ return spaceColor(c, space) ?? spaceColor(c, void 0) ?? grayHex(c[0] ?? 0);
248
+ }
249
+ /**
250
+ * §8.6.8 — the colour a run of `sc` / `scn` components comes to.
251
+ *
252
+ * The space in force decides, and where it was not read the COUNT is the next
253
+ * best witness: three numbers are RGB and four are CMYK on every device space
254
+ * there is. One number is the ambiguous case — grey in a device space, but the
255
+ * strength of a colorant in a Separation, where 1 is the ink at full and reads
256
+ * dark — so a lone component is only taken where the space said what it means.
257
+ *
258
+ * @param nums The components, as the page or a shading's function states them.
259
+ * @param space The space in force, where it was read.
260
+ * @returns The colour as 6 hex digits, or `undefined` where the numbers do not
261
+ * say what colour they are.
262
+ */
263
+ function spaceColor(nums, space) {
264
+ if (nums.length === 0) return void 0;
265
+ if (space?.icc && nums.length >= space.components) {
266
+ const [r, g, b] = space.icc(nums);
267
+ return rgbHex(r, g, b);
268
+ }
269
+ if (space?.cie) {
270
+ const [r, g, b] = cieToSrgb(space.cie, nums);
271
+ return rgbHex(r, g, b);
272
+ }
273
+ switch (space?.kind ?? (nums.length === 3 ? "rgb" : nums.length === 4 ? "cmyk" : void 0)) {
274
+ case "rgb": return nums.length >= 3 ? rgbHex(nums[0], nums[1], nums[2]) : void 0;
275
+ case "cmyk": return nums.length >= 4 ? cmykHex(nums[0], nums[1], nums[2], nums[3]) : void 0;
276
+ case "gray": return grayHex(nums[0]);
277
+ case "tint":
278
+ if (space?.tint) {
279
+ const hex = spaceColor(space.tint.transform(nums), space.tint.alternate);
280
+ if (hex !== void 0) return hex;
281
+ }
282
+ return grayHex(1 - Math.max(...nums));
283
+ default: return;
136
284
  }
137
- if (c.length === 3) return hex255(c[0] * 255, c[1] * 255, c[2] * 255);
138
- const g = (c[0] ?? 0) * 255;
139
- return hex255(g, g, g);
140
285
  }
141
- function hex255(r, g, b) {
142
- const h = (x) => Math.max(0, Math.min(255, Math.round(x))).toString(16).padStart(2, "0");
143
- return (h(r) + h(g) + h(b)).toUpperCase();
286
+ function clamp255(v) {
287
+ return Math.max(0, Math.min(255, Math.round(v * 255)));
288
+ }
289
+ function hex2(v) {
290
+ return clamp255(v).toString(16).padStart(2, "0");
291
+ }
292
+ /** Three channels, each 0..1, as 6 upper-case hex digits. */
293
+ function rgbHex(r, g, b) {
294
+ return (hex2(r) + hex2(g) + hex2(b)).toUpperCase();
295
+ }
296
+ /** One grey level, 0..1, as 6 upper-case hex digits. */
297
+ function grayHex(v) {
298
+ return rgbHex(v, v, v);
299
+ }
300
+ /** §8.6.4.4 — four inks, each 0..1, as 6 upper-case hex digits. */
301
+ function cmykHex(c, m, y, k) {
302
+ return rgbHex((1 - c) * (1 - k), (1 - m) * (1 - k), (1 - y) * (1 - k));
144
303
  }
145
304
  function pushStop(stops, offset, colorHex) {
146
305
  const o = Math.max(0, Math.min(1, offset));
@@ -162,37 +321,70 @@ function pushStop(stops, offset, colorHex) {
162
321
  * a green band at `ca` 0.6, meant to be read THROUGH: painted solid, the floor
163
322
  * plan under each band disappears.
164
323
  *
165
- * @param file The owning file.
166
- * @param page The page whose `/ExtGState` resources are wanted.
167
- * @returns Name → fill alpha, for the states that state one below 1.
324
+ * §11.3.5 `/BM` comes off the same dictionary. A blend nothing downstream can
325
+ * perform is still worth knowing: `Multiply` and `Darken` both let dark ink
326
+ * under the paint show through, which is what a highlighter IS, and a mark that
327
+ * only darkens belongs UNDER the words rather than over them.
328
+ *
329
+ * @param file The owning file.
330
+ * @param resources The resource dictionary in force — a page's, or the form or
331
+ * annotation appearance's own, since `gs` resolves against
332
+ * whichever is current.
333
+ * @returns Name → the fill alpha and blend worth carrying, for the states that
334
+ * state one.
168
335
  */
169
- function buildAlphaMap(file, page) {
336
+ function buildAlphaMap(file, resources) {
170
337
  const out = /* @__PURE__ */ new Map();
171
- if (!page.resources) return out;
172
- const states = file.get(page.resources, "ExtGState");
338
+ if (!resources) return out;
339
+ const states = file.get(resources, "ExtGState");
173
340
  if (!(states instanceof Map)) return out;
174
341
  for (const [name, value] of states) {
175
342
  const state = file.resolve(value);
176
343
  if (!(state instanceof Map)) continue;
177
344
  const ca = file.resolve(state.get("ca") ?? PDF_NULL);
178
- if (typeof ca === "number" && ca >= 0 && ca < 1) out.set(name, ca);
345
+ const alpha = typeof ca === "number" && ca >= 0 && ca <= 1 ? ca : void 0;
346
+ const bm = file.resolve(state.get("BM") ?? PDF_NULL);
347
+ const first = Array.isArray(bm) ? file.resolve(bm[0] ?? PDF_NULL) : bm;
348
+ const mode = first instanceof PdfName ? first.value : void 0;
349
+ const darkens = mode === "Multiply" || mode === "Darken";
350
+ const blend = mode !== void 0 && mode !== "Normal" && mode !== "Compatible" && !darkens ? mode : void 0;
351
+ const statesMask = state.has("SMask");
352
+ const masked = statesMask ? file.resolve(state.get("SMask") ?? PDF_NULL) instanceof Map : void 0;
353
+ if (alpha === void 0 && mode === void 0 && !statesMask) continue;
354
+ out.set(name, {
355
+ ...alpha !== void 0 ? { alpha } : {},
356
+ ...mode !== void 0 ? {
357
+ statesBlend: true,
358
+ darkens
359
+ } : {},
360
+ ...blend !== void 0 ? { blend } : {},
361
+ ...masked !== void 0 ? { masked } : {}
362
+ });
179
363
  }
180
364
  return out;
181
365
  }
182
- /** The spaces a page's `/ColorSpace` resources name, by name. */
183
- function buildColorSpaceMap(file, page) {
366
+ /**
367
+ * The spaces a `/ColorSpace` resource dictionary names, by name.
368
+ *
369
+ * @param file The owning file.
370
+ * @param resources The resource dictionary in force.
371
+ * @returns Name → what the space comes to, for the spaces that are read.
372
+ */
373
+ function buildColorSpaceMap(file, resources) {
184
374
  const out = /* @__PURE__ */ new Map();
185
- if (!page.resources) return out;
186
- const spaces = file.get(page.resources, "ColorSpace");
375
+ if (!resources) return out;
376
+ const spaces = file.get(resources, "ColorSpace");
187
377
  if (!(spaces instanceof Map)) return out;
188
378
  for (const [name, value] of spaces) {
189
- const info = colorSpaceInfo(file, file.resolve(value));
379
+ const info = colorSpaceAt(file, file.resolve(value), 0);
190
380
  if (info) out.set(name, info);
191
381
  }
192
382
  return out;
193
383
  }
384
+ /** An alternate space may name another; this is where that stops. */
385
+ var MAX_ALTERNATE = 4;
194
386
  /** What a colour space object comes to, or `undefined` for one not read. */
195
- function colorSpaceInfo(file, cs) {
387
+ function colorSpaceAt(file, cs, depth) {
196
388
  if (cs instanceof PdfName) return byFamily(cs.value, 0);
197
389
  if (!Array.isArray(cs) || cs.length === 0) return void 0;
198
390
  const head = file.resolve(cs[0]);
@@ -200,18 +392,96 @@ function colorSpaceInfo(file, cs) {
200
392
  if (head.value === "ICCBased") {
201
393
  const stream = file.resolve(cs[1] ?? PDF_NULL);
202
394
  const n = stream instanceof PdfStream ? file.get(stream.dict, "N") : void 0;
203
- return byFamily("ICCBased", typeof n === "number" ? n : 3);
395
+ const base = byFamily("ICCBased", typeof n === "number" ? n : 3);
396
+ if (!base || !(stream instanceof PdfStream)) return base;
397
+ let icc;
398
+ try {
399
+ icc = iccTransform(file.streamData(stream));
400
+ } catch {
401
+ icc = void 0;
402
+ }
403
+ return icc ? {
404
+ ...base,
405
+ icc
406
+ } : base;
204
407
  }
205
408
  if (head.value === "Indexed") return;
409
+ if (head.value === "CalRGB") {
410
+ const params = file.resolve(cs[1] ?? PDF_NULL);
411
+ const cie = params instanceof Map ? cieParams(file, params, true) : void 0;
412
+ const base = {
413
+ kind: "rgb",
414
+ components: 3
415
+ };
416
+ return cie ? {
417
+ ...base,
418
+ cie
419
+ } : base;
420
+ }
421
+ if (head.value === "CalGray") {
422
+ const params = file.resolve(cs[1] ?? PDF_NULL);
423
+ const cie = params instanceof Map ? cieParams(file, params, false) : void 0;
424
+ const base = {
425
+ kind: "gray",
426
+ components: 1
427
+ };
428
+ return cie ? {
429
+ ...base,
430
+ cie
431
+ } : base;
432
+ }
206
433
  if (head.value === "Separation" || head.value === "DeviceN") {
207
434
  const names = file.resolve(cs[1] ?? PDF_NULL);
435
+ const n = head.value === "Separation" ? 1 : Array.isArray(names) ? names.length : 1;
436
+ const alternate = depth < MAX_ALTERNATE ? colorSpaceAt(file, file.resolve(cs[2] ?? PDF_NULL), depth + 1) : void 0;
437
+ const transform = readFunction(file, cs[3]);
438
+ if (alternate && transform && alternate.kind !== "tint") return {
439
+ kind: "tint",
440
+ components: n,
441
+ tint: {
442
+ transform,
443
+ alternate
444
+ }
445
+ };
208
446
  return {
209
447
  kind: "tint",
210
- components: head.value === "Separation" ? 1 : Array.isArray(names) ? names.length : 1
448
+ components: n
211
449
  };
212
450
  }
213
451
  return byFamily(head.value, 0);
214
452
  }
453
+ /** §8.6.5.6/§8.6.5.7 — the white point, gamma and matrix a CIE space states. */
454
+ function cieParams(file, params, rgb) {
455
+ const nums = (v) => Array.isArray(v) ? v.map((x) => file.resolve(x)).filter((x) => typeof x === "number") : [];
456
+ const white = nums(file.get(params, "WhitePoint"));
457
+ if (white.length < 3 || !(white[1] > 0)) return void 0;
458
+ const gammaRaw = file.get(params, "Gamma");
459
+ const gamma = typeof gammaRaw === "number" ? [gammaRaw] : nums(gammaRaw);
460
+ const matrix = nums(file.get(params, "Matrix"));
461
+ return {
462
+ white: [
463
+ white[0],
464
+ white[1],
465
+ white[2]
466
+ ],
467
+ gamma: gamma.length > 0 ? gamma : rgb ? [
468
+ 1,
469
+ 1,
470
+ 1
471
+ ] : [1],
472
+ ...rgb ? { matrix: matrix.length === 9 ? matrix : [
473
+ 1,
474
+ 0,
475
+ 0,
476
+ 0,
477
+ 1,
478
+ 0,
479
+ 0,
480
+ 0,
481
+ 1
482
+ ] } : {}
483
+ };
484
+ }
215
485
  function byFamily(family, n) {
216
486
  switch (family) {
217
487
  case "DeviceGray":
@@ -246,4 +516,4 @@ function byFamily(family, n) {
246
516
  }
247
517
  }
248
518
  //#endregion
249
- export { buildAlphaMap, buildColorSpaceMap, buildShadingMap };
519
+ export { buildAlphaMap, buildColorSpaceMap, buildShadingMap, cmykHex, gradientShading, grayHex, rgbHex, sampledShading, shadingTypeOf, spaceColor };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * One face's metrics: `name code width` triples, where the name is the
3
+ * glyph's own, the code is the one Adobe's built-in encoding gives it, and
4
+ * the width is its advance in 1000ths of an em.
5
+ */
6
+ export declare const STANDARD_METRICS: Readonly<Record<string, string>>;
7
+ /** Every Courier is 600 wide, every glyph — it is a typewriter face. */
8
+ export declare const COURIER_WIDTH = 600;