reamkit 1.27.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 (65) hide show
  1. package/README.md +6 -5
  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.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 +5 -2
  24. package/dist/esm/pdf-reader/content.d.ts +76 -0
  25. package/dist/esm/pdf-reader/content.js +142 -59
  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.js +26 -9
  30. package/dist/esm/pdf-reader/embedded-fonts.d.ts +11 -0
  31. package/dist/esm/pdf-reader/embedded-fonts.js +14 -1
  32. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  33. package/dist/esm/pdf-reader/encodings.js +110 -0
  34. package/dist/esm/pdf-reader/flow-build.d.ts +9 -3
  35. package/dist/esm/pdf-reader/flow-build.js +13 -7
  36. package/dist/esm/pdf-reader/font.js +329 -8
  37. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  38. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  39. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  40. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  41. package/dist/esm/pdf-reader/icc.js +210 -0
  42. package/dist/esm/pdf-reader/image-decode.d.ts +4 -3
  43. package/dist/esm/pdf-reader/image-decode.js +85 -64
  44. package/dist/esm/pdf-reader/images.d.ts +6 -0
  45. package/dist/esm/pdf-reader/images.js +50 -7
  46. package/dist/esm/pdf-reader/jbig2.js +104 -19
  47. package/dist/esm/pdf-reader/layout.js +1148 -45
  48. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  49. package/dist/esm/pdf-reader/math-rows.js +198 -0
  50. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  51. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  52. package/dist/esm/pdf-reader/reader.d.ts +6 -9
  53. package/dist/esm/pdf-reader/reader.js +34 -21
  54. package/dist/esm/pdf-reader/shading.d.ts +81 -2
  55. package/dist/esm/pdf-reader/shading.js +191 -25
  56. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  57. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  58. package/dist/esm/pdf-reader/text-rules.js +83 -10
  59. package/dist/esm/pdf-reader/text.js +82 -3
  60. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  61. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  62. package/dist/esm/pdf-reader/vector.js +43 -4
  63. package/dist/esm/word/document-parser.js +4 -1
  64. package/dist/esm/word/docx-writer.js +1 -0
  65. package/package.json +1 -1
@@ -0,0 +1,351 @@
1
+ import { macGlyphName } from "./encodings.js";
2
+ //#region src/pdf-reader/glyf-outline.ts
3
+ /**
4
+ * Prepare an embedded TrueType program for outline reading (§9.6.6).
5
+ *
6
+ * @param program The raw `/FontFile2` bytes.
7
+ * @returns A reader, or `undefined` where the program carries no `glyf`/`loca`
8
+ * pair — an OpenType/CFF program under `/FontFile2` is one such, and
9
+ * its outlines are charstrings this does not read.
10
+ */
11
+ function outlineSource(program) {
12
+ let tables;
13
+ try {
14
+ tables = sfntTables(program);
15
+ } catch {
16
+ return;
17
+ }
18
+ const head = tables.get("head");
19
+ const loca = tables.get("loca");
20
+ const glyf = tables.get("glyf");
21
+ if (!head || !loca || !glyf) return void 0;
22
+ const view = new DataView(program.buffer, program.byteOffset, program.byteLength);
23
+ const unitsPerEm = safeUint16(view, head.offset + 18) || 1e3;
24
+ const longLoca = safeInt16(view, head.offset + 50) === 1;
25
+ const cache = /* @__PURE__ */ new Map();
26
+ const glyphRange = (gid) => {
27
+ const stride = longLoca ? 4 : 2;
28
+ const at = loca.offset + gid * stride;
29
+ if (at + stride * 2 > loca.offset + loca.length) return void 0;
30
+ const read = (o) => longLoca ? safeUint32(view, o) : safeUint16(view, o) * 2;
31
+ const start = read(at);
32
+ const end = read(at + stride);
33
+ if (end <= start) return void 0;
34
+ if (glyf.offset + end > program.length) return void 0;
35
+ return {
36
+ start: glyf.offset + start,
37
+ end: glyf.offset + end
38
+ };
39
+ };
40
+ const read = (gid, depth) => {
41
+ const range = glyphRange(gid);
42
+ if (!range) return void 0;
43
+ const contours = safeInt16(view, range.start);
44
+ if (contours >= 0) return simpleGlyph(view, range.start, contours, range.end);
45
+ if (depth >= MAX_COMPONENT_DEPTH) return void 0;
46
+ return compositeGlyph(view, range.start + 10, range.end, (sub) => read(sub, depth + 1));
47
+ };
48
+ return {
49
+ count: Math.max(0, Math.floor(loca.length / (longLoca ? 4 : 2)) - 1),
50
+ cmap: (tables.get("cmap")?.length ?? 0) > 0,
51
+ path: (gid) => {
52
+ const had = cache.get(gid);
53
+ if (had !== void 0 || cache.has(gid)) return had;
54
+ let out;
55
+ try {
56
+ const raw = read(gid, 0);
57
+ out = raw ? raw.map((seg) => scaleSeg(seg, 1 / unitsPerEm)) : void 0;
58
+ } catch {
59
+ out = void 0;
60
+ }
61
+ cache.set(gid, out);
62
+ return out;
63
+ }
64
+ };
65
+ }
66
+ /** A composite glyph may name another composite; this is where that stops. */
67
+ var MAX_COMPONENT_DEPTH = 5;
68
+ /**
69
+ * §post format 2.0 — the NAME each glyph of a TrueType program goes by.
70
+ *
71
+ * A legacy eight-bit font is reached through this table and nothing else. Its
72
+ * program carries no `cmap`, so a code cannot be turned into a character and
73
+ * then into a glyph; what it can be turned into is a glyph NAME, through the
74
+ * encoding the font dictionary states (Annex D.2) — and the name is looked up
75
+ * here. TrueType_without_cmap.pdf is an Armenian face, Masis, whose `i` draws
76
+ * ի: read as text its line came back "'>in", and drawn by name it is the line
77
+ * the page shows.
78
+ *
79
+ * @param program The raw `/FontFile2` bytes.
80
+ * @returns Glyph name → index, or `undefined` where the table is missing or
81
+ * states no names of its own (formats 1.0 and 3.0).
82
+ */
83
+ function postGlyphNames(program) {
84
+ let tables;
85
+ try {
86
+ tables = sfntTables(program);
87
+ } catch {
88
+ return;
89
+ }
90
+ const post = tables.get("post");
91
+ if (!post || post.length < 34) return void 0;
92
+ const view = new DataView(program.buffer, program.byteOffset, program.byteLength);
93
+ if (safeUint32(view, post.offset) !== POST_NAMED) return void 0;
94
+ const count = safeUint16(view, post.offset + 32);
95
+ const end = post.offset + post.length;
96
+ if (post.offset + 34 + count * 2 > end) return void 0;
97
+ const own = [];
98
+ for (let at = post.offset + 34 + count * 2; at < end;) {
99
+ const length = program[at] ?? 0;
100
+ if (at + 1 + length > end) break;
101
+ own.push(String.fromCharCode(...program.subarray(at + 1, at + 1 + length)));
102
+ at += 1 + length;
103
+ }
104
+ const out = /* @__PURE__ */ new Map();
105
+ for (let gid = 0; gid < count; gid++) {
106
+ const index = safeUint16(view, post.offset + 34 + gid * 2);
107
+ const name = index < MAC_ORDER_SIZE ? macGlyphName(index) : own[index - MAC_ORDER_SIZE];
108
+ if (name !== void 0 && name.length > 0 && !out.has(name)) out.set(name, gid);
109
+ }
110
+ return out.size > 0 ? out : void 0;
111
+ }
112
+ /** `post` version 2.0 — the only one that states names of its own. */
113
+ var POST_NAMED = 131072;
114
+ /** How many names the Macintosh standard order holds (§post). */
115
+ var MAC_ORDER_SIZE = 258;
116
+ /** The sfnt table directory: tag → where that table is. */
117
+ function sfntTables(program) {
118
+ const view = new DataView(program.buffer, program.byteOffset, program.byteLength);
119
+ const out = /* @__PURE__ */ new Map();
120
+ const count = safeUint16(view, 4);
121
+ for (let i = 0; i < count; i++) {
122
+ const at = 12 + i * 16;
123
+ if (at + 16 > program.length) break;
124
+ const tag = String.fromCharCode(program[at], program[at + 1], program[at + 2], program[at + 3]);
125
+ out.set(tag, {
126
+ offset: safeUint32(view, at + 8),
127
+ length: safeUint32(view, at + 12)
128
+ });
129
+ }
130
+ return out;
131
+ }
132
+ function simpleGlyph(view, start, contours, end) {
133
+ let at = start + 10;
134
+ const ends = [];
135
+ for (let i = 0; i < contours; i++, at += 2) ends.push(safeUint16(view, at));
136
+ const points = contours > 0 ? (ends[contours - 1] ?? -1) + 1 : 0;
137
+ if (points <= 0 || points > MAX_POINTS) return void 0;
138
+ at += 2 + safeUint16(view, at);
139
+ const flags = new Uint8Array(points);
140
+ for (let i = 0; i < points && at < end;) {
141
+ const flag = safeUint8(view, at++);
142
+ flags[i++] = flag;
143
+ if ((flag & 8) !== 0) {
144
+ let repeats = safeUint8(view, at++);
145
+ while (repeats-- > 0 && i < points) flags[i++] = flag;
146
+ }
147
+ }
148
+ const xs = new Int16Array(points);
149
+ let x = 0;
150
+ for (let i = 0; i < points; i++) {
151
+ const flag = flags[i];
152
+ if ((flag & 2) !== 0) {
153
+ const d = safeUint8(view, at++);
154
+ x += (flag & 16) !== 0 ? d : -d;
155
+ } else if ((flag & 16) === 0) {
156
+ x += safeInt16(view, at);
157
+ at += 2;
158
+ }
159
+ xs[i] = x;
160
+ }
161
+ const ys = new Int16Array(points);
162
+ let y = 0;
163
+ for (let i = 0; i < points; i++) {
164
+ const flag = flags[i];
165
+ if ((flag & 4) !== 0) {
166
+ const d = safeUint8(view, at++);
167
+ y += (flag & 32) !== 0 ? d : -d;
168
+ } else if ((flag & 32) === 0) {
169
+ y += safeInt16(view, at);
170
+ at += 2;
171
+ }
172
+ ys[i] = y;
173
+ }
174
+ const out = [];
175
+ let from = 0;
176
+ for (const last of ends) {
177
+ if (last >= points) break;
178
+ emitContour(out, flags, xs, ys, from, last);
179
+ from = last + 1;
180
+ }
181
+ return out.length > 0 ? out : void 0;
182
+ }
183
+ /** How many points one glyph may hold — a guard, not a format limit. */
184
+ var MAX_POINTS = 1e4;
185
+ function emitContour(out, flags, xs, ys, from, last) {
186
+ const n = last - from + 1;
187
+ if (n <= 0) return;
188
+ const on = (i) => (flags[from + (i % n + n) % n] & 1) !== 0;
189
+ const px = (i) => xs[from + (i % n + n) % n];
190
+ const py = (i) => ys[from + (i % n + n) % n];
191
+ let startIdx = 0;
192
+ while (startIdx < n && !on(startIdx)) startIdx++;
193
+ let cx;
194
+ let cy;
195
+ if (startIdx === n) {
196
+ startIdx = 0;
197
+ cx = (px(0) + px(n - 1)) / 2;
198
+ cy = (py(0) + py(n - 1)) / 2;
199
+ } else {
200
+ cx = px(startIdx);
201
+ cy = py(startIdx);
202
+ }
203
+ out.push({
204
+ op: "move",
205
+ x: cx,
206
+ y: cy
207
+ });
208
+ let i = startIdx + 1;
209
+ const stop = startIdx + n;
210
+ while (i <= stop) {
211
+ if (on(i)) {
212
+ out.push({
213
+ op: "line",
214
+ x: px(i),
215
+ y: py(i)
216
+ });
217
+ cx = px(i);
218
+ cy = py(i);
219
+ i++;
220
+ continue;
221
+ }
222
+ const qx = px(i);
223
+ const qy = py(i);
224
+ const nextOn = on(i + 1);
225
+ const ex = nextOn ? px(i + 1) : (qx + px(i + 1)) / 2;
226
+ const ey = nextOn ? py(i + 1) : (qy + py(i + 1)) / 2;
227
+ out.push({
228
+ op: "cubic",
229
+ x1: cx + 2 / 3 * (qx - cx),
230
+ y1: cy + 2 / 3 * (qy - cy),
231
+ x2: ex + 2 / 3 * (qx - ex),
232
+ y2: ey + 2 / 3 * (qy - ey),
233
+ x: ex,
234
+ y: ey
235
+ });
236
+ cx = ex;
237
+ cy = ey;
238
+ i += nextOn ? 2 : 1;
239
+ }
240
+ out.push({ op: "close" });
241
+ }
242
+ function compositeGlyph(view, start, end, component) {
243
+ const out = [];
244
+ let at = start;
245
+ for (let guard = 0; guard < MAX_COMPONENTS && at + 4 <= end; guard++) {
246
+ const flags = safeUint16(view, at);
247
+ const gid = safeUint16(view, at + 2);
248
+ at += 4;
249
+ let dx = 0;
250
+ let dy = 0;
251
+ if ((flags & 1) !== 0) {
252
+ if ((flags & 2) !== 0) {
253
+ dx = safeInt16(view, at);
254
+ dy = safeInt16(view, at + 2);
255
+ }
256
+ at += 4;
257
+ } else {
258
+ if ((flags & 2) !== 0) {
259
+ dx = safeInt8(view, at);
260
+ dy = safeInt8(view, at + 1);
261
+ }
262
+ at += 2;
263
+ }
264
+ let a = 1;
265
+ let b = 0;
266
+ let c = 0;
267
+ let d = 1;
268
+ if ((flags & 8) !== 0) {
269
+ a = d = f2dot14(view, at);
270
+ at += 2;
271
+ } else if ((flags & 64) !== 0) {
272
+ a = f2dot14(view, at);
273
+ d = f2dot14(view, at + 2);
274
+ at += 4;
275
+ } else if ((flags & 128) !== 0) {
276
+ a = f2dot14(view, at);
277
+ b = f2dot14(view, at + 2);
278
+ c = f2dot14(view, at + 4);
279
+ d = f2dot14(view, at + 6);
280
+ at += 8;
281
+ }
282
+ const sub = component(gid);
283
+ if (sub) for (const seg of sub) out.push(transformSeg(seg, a, b, c, d, dx, dy));
284
+ if ((flags & 32) === 0) break;
285
+ }
286
+ return out.length > 0 ? out : void 0;
287
+ }
288
+ /** How many components one composite glyph may place. */
289
+ var MAX_COMPONENTS = 32;
290
+ function transformSeg(seg, a, b, c, d, dx, dy) {
291
+ const at = (x, y) => [a * x + c * y + dx, b * x + d * y + dy];
292
+ if (seg.op === "close") return seg;
293
+ if (seg.op === "cubic") {
294
+ const [x1, y1] = at(seg.x1, seg.y1);
295
+ const [x2, y2] = at(seg.x2, seg.y2);
296
+ const [x, y] = at(seg.x, seg.y);
297
+ return {
298
+ op: "cubic",
299
+ x1,
300
+ y1,
301
+ x2,
302
+ y2,
303
+ x,
304
+ y
305
+ };
306
+ }
307
+ const [x, y] = at(seg.x, seg.y);
308
+ return {
309
+ op: seg.op,
310
+ x,
311
+ y
312
+ };
313
+ }
314
+ function scaleSeg(seg, k) {
315
+ if (seg.op === "close") return seg;
316
+ if (seg.op === "cubic") return {
317
+ op: "cubic",
318
+ x1: seg.x1 * k,
319
+ y1: seg.y1 * k,
320
+ x2: seg.x2 * k,
321
+ y2: seg.y2 * k,
322
+ x: seg.x * k,
323
+ y: seg.y * k
324
+ };
325
+ return {
326
+ op: seg.op,
327
+ x: seg.x * k,
328
+ y: seg.y * k
329
+ };
330
+ }
331
+ /** The 2.14 fixed-point a component transform is stated in. */
332
+ function f2dot14(view, at) {
333
+ return safeInt16(view, at) / 16384;
334
+ }
335
+ function safeUint8(view, at) {
336
+ return at >= 0 && at < view.byteLength ? view.getUint8(at) : 0;
337
+ }
338
+ function safeInt8(view, at) {
339
+ return at >= 0 && at < view.byteLength ? view.getInt8(at) : 0;
340
+ }
341
+ function safeUint16(view, at) {
342
+ return at >= 0 && at + 2 <= view.byteLength ? view.getUint16(at) : 0;
343
+ }
344
+ function safeInt16(view, at) {
345
+ return at >= 0 && at + 2 <= view.byteLength ? view.getInt16(at) : 0;
346
+ }
347
+ function safeUint32(view, at) {
348
+ return at >= 0 && at + 4 <= view.byteLength ? view.getUint32(at) : 0;
349
+ }
350
+ //#endregion
351
+ export { outlineSource, postGlyphNames };
@@ -1,3 +1,4 @@
1
+ import { dingbatChar } from "./dingbats.js";
1
2
  //#region src/pdf-reader/glyph-names.ts
2
3
  /** Glyph name → the text it stands for. */
3
4
  var GLYPHS = {
@@ -22,6 +23,7 @@ var GLYPHS = {
22
23
  Dcaron: "Ď",
23
24
  Dcedilla: "Ḑ",
24
25
  Ddotaccent: "Ḋ",
26
+ Delta: "∆",
25
27
  Eacute: "É",
26
28
  Ebreve: "Ĕ",
27
29
  Ecaron: "Ě",
@@ -65,6 +67,7 @@ var GLYPHS = {
65
67
  Lacute: "Ĺ",
66
68
  Lcaron: "Ľ",
67
69
  Lcedilla: "Ļ",
70
+ Lslash: "Ł",
68
71
  Macute: "Ḿ",
69
72
  Mdotaccent: "Ṁ",
70
73
  Nacute: "Ń",
@@ -83,6 +86,7 @@ var GLYPHS = {
83
86
  Ograve: "Ò",
84
87
  Ohungarumlaut: "Ő",
85
88
  Omacron: "Ō",
89
+ Omega: "Ω",
86
90
  Oogonek: "Ǫ",
87
91
  Oslash: "Ø",
88
92
  Otilde: "Õ",
@@ -143,6 +147,7 @@ var GLYPHS = {
143
147
  amacron: "ā",
144
148
  ampersand: "&",
145
149
  aogonek: "ą",
150
+ approxequal: "≈",
146
151
  aring: "å",
147
152
  asciicircum: "^",
148
153
  asciitilde: "~",
@@ -223,6 +228,7 @@ var GLYPHS = {
223
228
  gmacron: "ḡ",
224
229
  grave: "`",
225
230
  greater: ">",
231
+ greaterequal: "≥",
226
232
  guillemotleft: "«",
227
233
  guillemotright: "»",
228
234
  guilsinglleft: "‹",
@@ -240,6 +246,8 @@ var GLYPHS = {
240
246
  icircumflex: "î",
241
247
  idieresis: "ï",
242
248
  igrave: "ì",
249
+ infinity: "∞",
250
+ integral: "∫",
243
251
  imacron: "ī",
244
252
  iogonek: "į",
245
253
  itilde: "ĩ",
@@ -252,7 +260,10 @@ var GLYPHS = {
252
260
  lcaron: "ľ",
253
261
  lcedilla: "ļ",
254
262
  less: "<",
263
+ lessequal: "≤",
255
264
  logicalnot: "¬",
265
+ lozenge: "◊",
266
+ lslash: "ł",
256
267
  macron: "¯",
257
268
  macute: "ḿ",
258
269
  mdotaccent: "ṁ",
@@ -266,6 +277,7 @@ var GLYPHS = {
266
277
  ndotaccent: "ṅ",
267
278
  ngrave: "ǹ",
268
279
  nine: "9",
280
+ notequal: "≠",
269
281
  ntilde: "ñ",
270
282
  numbersign: "#",
271
283
  oacute: "ó",
@@ -292,12 +304,16 @@ var GLYPHS = {
292
304
  paragraph: "¶",
293
305
  parenleft: "(",
294
306
  parenright: ")",
307
+ partialdiff: "∂",
295
308
  pdotaccent: "ṗ",
296
309
  percent: "%",
297
310
  period: ".",
311
+ periodcentered: "·",
298
312
  perthousand: "‰",
313
+ pi: "π",
299
314
  plus: "+",
300
315
  plusminus: "±",
316
+ product: "∏",
301
317
  question: "?",
302
318
  questiondown: "¿",
303
319
  quotedbl: "\"",
@@ -308,6 +324,7 @@ var GLYPHS = {
308
324
  quoteright: "’",
309
325
  quotesinglbase: "‚",
310
326
  quotesingle: "'",
327
+ radical: "√",
311
328
  racute: "ŕ",
312
329
  rcaron: "ř",
313
330
  rcedilla: "ŗ",
@@ -328,6 +345,7 @@ var GLYPHS = {
328
345
  softhyphen: "­",
329
346
  space: " ",
330
347
  sterling: "£",
348
+ summation: "∑",
331
349
  tcaron: "ť",
332
350
  tcedilla: "ţ",
333
351
  tdieresis: "ẗ",
@@ -403,6 +421,8 @@ function textForGlyphName(name) {
403
421
  const cp = parseInt(u[1], 16);
404
422
  return cp <= 1114111 ? String.fromCodePoint(cp) : void 0;
405
423
  }
424
+ const dingbat = dingbatChar(base);
425
+ if (dingbat !== void 0) return dingbat;
406
426
  }
407
427
  //#endregion
408
428
  export { textForGlyphName };
@@ -0,0 +1,10 @@
1
+ /** How a profile turns its own components into sRGB, each 0..1. */
2
+ export type IccTransform = (comps: ReadonlyArray<number>) => [number, number, number];
3
+ /**
4
+ * Read an ICC profile into a transform to sRGB.
5
+ *
6
+ * @param profile The raw profile bytes, as `/ICCBased`'s stream holds them.
7
+ * @returns The transform, or `undefined` for a profile whose form this does not
8
+ * read — a CMYK one, a table-based one, or a damaged one.
9
+ */
10
+ export declare function iccTransform(profile: Uint8Array): IccTransform | undefined;
@@ -0,0 +1,210 @@
1
+ //#region src/pdf-reader/icc.ts
2
+ /**
3
+ * Read an ICC profile into a transform to sRGB.
4
+ *
5
+ * @param profile The raw profile bytes, as `/ICCBased`'s stream holds them.
6
+ * @returns The transform, or `undefined` for a profile whose form this does not
7
+ * read — a CMYK one, a table-based one, or a damaged one.
8
+ */
9
+ function iccTransform(profile) {
10
+ if (profile.length < HEADER_SIZE + 4) return void 0;
11
+ const view = new DataView(profile.buffer, profile.byteOffset, profile.byteLength);
12
+ const space = tag(profile, 16);
13
+ if (space !== "RGB " && space !== "GRAY") return void 0;
14
+ const tags = tagTable(profile, view);
15
+ if (!tags) return void 0;
16
+ const white = xyzTag(profile, view, tags.get("wtpt")) ?? D50;
17
+ if (space === "GRAY") {
18
+ const curve = curveTag(profile, view, tags.get("kTRC"));
19
+ if (!curve) return void 0;
20
+ return (comps) => {
21
+ const y = curve(clamp01(comps[0] ?? 0));
22
+ const [x, yy, z] = adapt([
23
+ white[0] * y,
24
+ white[1] * y,
25
+ white[2] * y
26
+ ], white);
27
+ return toSrgb(x, yy, z);
28
+ };
29
+ }
30
+ const r = xyzTag(profile, view, tags.get("rXYZ"));
31
+ const g = xyzTag(profile, view, tags.get("gXYZ"));
32
+ const b = xyzTag(profile, view, tags.get("bXYZ"));
33
+ if (!r || !g || !b) return void 0;
34
+ const rc = curveTag(profile, view, tags.get("rTRC"));
35
+ const gc = curveTag(profile, view, tags.get("gTRC"));
36
+ const bc = curveTag(profile, view, tags.get("bTRC"));
37
+ if (!rc || !gc || !bc) return void 0;
38
+ return (comps) => {
39
+ const lr = rc(clamp01(comps[0] ?? 0));
40
+ const lg = gc(clamp01(comps[1] ?? 0));
41
+ const lb = bc(clamp01(comps[2] ?? 0));
42
+ const [ax, ay, az] = adapt([
43
+ r[0] * lr + g[0] * lg + b[0] * lb,
44
+ r[1] * lr + g[1] * lg + b[1] * lb,
45
+ r[2] * lr + g[2] * lg + b[2] * lb
46
+ ], D50);
47
+ return toSrgb(ax, ay, az);
48
+ };
49
+ }
50
+ /** The profile header is fixed-length; the tag table follows it. */
51
+ var HEADER_SIZE = 128;
52
+ /** ICC.1:2010 §7.2.16 — the PCS every matrix/TRC profile is referred to. */
53
+ var D50 = [
54
+ .9642,
55
+ 1,
56
+ .8249
57
+ ];
58
+ /** The white every sRGB screen is referred to. */
59
+ var D65 = [
60
+ .9505,
61
+ 1,
62
+ 1.089
63
+ ];
64
+ /** A four-byte signature, as the profile writes them. */
65
+ function tag(data, at) {
66
+ return String.fromCharCode(data[at] ?? 0, data[at + 1] ?? 0, data[at + 2] ?? 0, data[at + 3] ?? 0);
67
+ }
68
+ /** §7.3 — the tag table: a count, then twelve bytes of signature/offset/size each. */
69
+ function tagTable(data, view) {
70
+ const count = view.getUint32(HEADER_SIZE);
71
+ if (count === 0 || count > MAX_TAGS) return void 0;
72
+ const out = /* @__PURE__ */ new Map();
73
+ for (let i = 0; i < count; i++) {
74
+ const at = HEADER_SIZE + 4 + i * 12;
75
+ if (at + 12 > data.length) break;
76
+ const offset = view.getUint32(at + 4);
77
+ const size = view.getUint32(at + 8);
78
+ if (offset + size > data.length) continue;
79
+ out.set(tag(data, at), {
80
+ offset,
81
+ size
82
+ });
83
+ }
84
+ return out.size > 0 ? out : void 0;
85
+ }
86
+ /** No profile carries more tags than this; a bigger count is a damaged file. */
87
+ var MAX_TAGS = 256;
88
+ /** §10.31 `XYZType` — a signature, four reserved bytes, then three s15Fixed16. */
89
+ function xyzTag(data, view, where) {
90
+ if (!where || where.size < 20 || tag(data, where.offset) !== "XYZ ") return void 0;
91
+ const at = where.offset + 8;
92
+ return [
93
+ s15(view, at),
94
+ s15(view, at + 4),
95
+ s15(view, at + 8)
96
+ ];
97
+ }
98
+ /** The s15Fixed16 every ICC number is written as. */
99
+ function s15(view, at) {
100
+ return view.getInt32(at) / 65536;
101
+ }
102
+ /**
103
+ * §10.6/§10.18 — a tone curve, as the function that linearises one component.
104
+ *
105
+ * `curv` is a table: no entries is the identity, one is a gamma written as
106
+ * u8Fixed8, and more is a sampled curve read by interpolation. `para` states
107
+ * one of five formulas by their parameters, of which the sRGB-style type 3 is
108
+ * the one every modern profile writes.
109
+ */
110
+ function curveTag(data, view, where) {
111
+ if (!where || where.size < 12) return void 0;
112
+ const kind = tag(data, where.offset);
113
+ if (kind === "curv") {
114
+ const count = view.getUint32(where.offset + 8);
115
+ if (count === 0) return (v) => v;
116
+ if (count === 1) {
117
+ const gamma = view.getUint16(where.offset + 12) / 256;
118
+ return (v) => v ** gamma;
119
+ }
120
+ if (where.offset + 12 + count * 2 > data.length || count > MAX_CURVE_POINTS) return void 0;
121
+ const table = new Float64Array(count);
122
+ for (let i = 0; i < count; i++) table[i] = view.getUint16(where.offset + 12 + i * 2) / 65535;
123
+ return (v) => {
124
+ const at = clamp01(v) * (count - 1);
125
+ const i = Math.floor(at);
126
+ const lo = table[i] ?? 0;
127
+ return lo + ((table[Math.min(i + 1, count - 1)] ?? lo) - lo) * (at - i);
128
+ };
129
+ }
130
+ if (kind === "para") {
131
+ const type = view.getUint16(where.offset + 8);
132
+ const p = (i) => s15(view, where.offset + 12 + i * 4);
133
+ const g = p(0);
134
+ if (type === 0) return (v) => v ** g;
135
+ if (type === 1) {
136
+ const [a, b] = [p(1), p(2)];
137
+ return (v) => v >= -b / a ? (a * v + b) ** g : 0;
138
+ }
139
+ if (type === 2) {
140
+ const [a, b, c] = [
141
+ p(1),
142
+ p(2),
143
+ p(3)
144
+ ];
145
+ return (v) => v >= -b / a ? (a * v + b) ** g + c : c;
146
+ }
147
+ if (type === 3) {
148
+ const [a, b, c, d] = [
149
+ p(1),
150
+ p(2),
151
+ p(3),
152
+ p(4)
153
+ ];
154
+ return (v) => v >= d ? (a * v + b) ** g : c * v;
155
+ }
156
+ if (type === 4) {
157
+ const [a, b, c, d, e, f] = [
158
+ p(1),
159
+ p(2),
160
+ p(3),
161
+ p(4),
162
+ p(5),
163
+ p(6)
164
+ ];
165
+ return (v) => v >= d ? (a * v + b) ** g + e : c * v + f;
166
+ }
167
+ }
168
+ }
169
+ /** No tone curve is sampled at more points than this. */
170
+ var MAX_CURVE_POINTS = 65536;
171
+ /** Bradford adaptation from the profile's white to the screen's. */
172
+ function adapt(xyz, from) {
173
+ const cone = (x, y, z) => [
174
+ .8951 * x + .2664 * y - .1614 * z,
175
+ -.7502 * x + 1.7135 * y + .0367 * z,
176
+ .0389 * x - .0685 * y + 1.0296 * z
177
+ ];
178
+ const [sx, sy, sz] = cone(from[0], from[1], from[2]);
179
+ const [dx, dy, dz] = cone(D65[0], D65[1], D65[2]);
180
+ const [cx, cy, cz] = cone(xyz[0], xyz[1], xyz[2]);
181
+ const [ax, ay, az] = [
182
+ cx * (sx === 0 ? 1 : dx / sx),
183
+ cy * (sy === 0 ? 1 : dy / sy),
184
+ cz * (sz === 0 ? 1 : dz / sz)
185
+ ];
186
+ return [
187
+ .9869929 * ax - .1470543 * ay + .1599627 * az,
188
+ .4323053 * ax + .5183603 * ay + .0492912 * az,
189
+ -.0085287 * ax + .0400428 * ay + .9684867 * az
190
+ ];
191
+ }
192
+ /** IEC 61966-2-1 — XYZ (D65) to sRGB, gamma and all. */
193
+ function toSrgb(x, y, z) {
194
+ const r = 3.2404542 * x - 1.5371385 * y - .4985314 * z;
195
+ const g = -.969266 * x + 1.8760108 * y + .041556 * z;
196
+ const b = .0556434 * x - .2040259 * y + 1.0572252 * z;
197
+ return [
198
+ transfer(clamp01(r)),
199
+ transfer(clamp01(g)),
200
+ transfer(clamp01(b))
201
+ ];
202
+ }
203
+ function transfer(v) {
204
+ return v <= .0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - .055;
205
+ }
206
+ function clamp01(v) {
207
+ return Math.min(1, Math.max(0, v));
208
+ }
209
+ //#endregion
210
+ export { iccTransform };
@@ -28,9 +28,10 @@ export type DecodedImage = {
28
28
  * colour spaces; Flate, LZW (EP12), RunLength, ASCII85, ASCIIHex and CCITT
29
29
  * Group 4 / Group 3 1-D fax (EP15) filters; PNG/TIFF predictors; bit depths
30
30
  * 1/2/4/8/16; an `/SMask` folded in as the PNG alpha channel; and a stencil
31
- * `/ImageMask` (§8.9.6.2) given back as RGBA in `fillHex`. Unsupported inputs
32
- * (Separation/DeviceN/Lab, CCITT Group 3 2-D) return a typed failure so the
33
- * caller records a loss.
31
+ * `/ImageMask` (§8.9.6.2) given back as RGBA in `fillHex`; `Lab` (§8.6.5.8) and
32
+ * a `Separation`/`DeviceN` through its own tint transform (§8.6.6.4).
33
+ * Unsupported inputs (CCITT Group 3 2-D) return a typed failure so the caller
34
+ * records a loss.
34
35
  *
35
36
  * @param file The owning file.
36
37
  * @param stream The image XObject, or an inline image wrapped as one.