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
@@ -17,7 +17,7 @@ export interface Display {
17
17
  };
18
18
  }
19
19
  /**
20
- * The {@link Display} a page's `/MediaBox` and `/Rotate` describe.
20
+ * The {@link Display} a page's shown box and `/Rotate` describe.
21
21
  *
22
22
  * `/Rotate` turns the page CLOCKWISE when shown, so the content turns with it —
23
23
  * counter-clockwise by the same amount as seen from the content's own frame,
@@ -1,6 +1,6 @@
1
1
  //#region src/pdf-reader/display.ts
2
2
  /**
3
- * The {@link Display} a page's `/MediaBox` and `/Rotate` describe.
3
+ * The {@link Display} a page's shown box and `/Rotate` describe.
4
4
  *
5
5
  * `/Rotate` turns the page CLOCKWISE when shown, so the content turns with it —
6
6
  * counter-clockwise by the same amount as seen from the content's own frame,
@@ -10,7 +10,7 @@
10
10
  * @returns Its size, its turn, and the map onto it.
11
11
  */
12
12
  function displayOf(page) {
13
- const [x0, y0, x1, y1] = page.mediaBox;
13
+ const [x0, y0, x1, y1] = page.cropBox;
14
14
  const left = Math.min(x0, x1);
15
15
  const bottom = Math.min(y0, y1);
16
16
  const w = Math.abs(x1 - x0);
@@ -66,7 +66,60 @@ function placeRuns(runs, d) {
66
66
  endY: end.y,
67
67
  ...Math.abs(angle) > .5 ? { angleDeg: angle } : {}
68
68
  };
69
- });
69
+ }).filter((r) => shows(d, Math.min(r.x, r.endX), Math.min(r.y, r.endY) - r.fontSizePt * .25, Math.max(r.x, r.endX), Math.max(r.y, r.endY) + r.fontSizePt * .85)).map((r) => cropped(r, d));
70
+ }
71
+ /**
72
+ * §14.11.2 — a run the crop's edge cuts through, as the letters it shows.
73
+ *
74
+ * A run that reaches into the shown page was kept whole, which is right where
75
+ * the crop trims a hair and wrong where it trims a line: endchar.pdf is one
76
+ * line of a poster, "LE HOLD-UP PLANÉTAIRE", cropped to the fourteen points
77
+ * that hold its É. Every viewer shows that one letter. Reflowed, the whole
78
+ * line was re-set into a column fourteen points wide and came back as four
79
+ * pages of one letter each.
80
+ *
81
+ * A letter is kept where any part of it is inside, so a crop that shaves an
82
+ * edge takes nothing. Where in the run each letter falls is estimated from the
83
+ * run's own width, which is exact for a face set solid and near enough for one
84
+ * that is not — the answer is a letter either way.
85
+ *
86
+ * @param run The run, already placed on the shown page.
87
+ * @param d The page's shown geometry.
88
+ * @returns The run, or the part of it the page shows.
89
+ */
90
+ function cropped(run, d) {
91
+ const from = Math.min(run.x, run.endX);
92
+ const to = Math.max(run.x, run.endX);
93
+ const chars = [...run.text];
94
+ if (from >= 0 && to <= d.width) return run;
95
+ if (run.angleDeg !== void 0 || chars.length < 2 || !(to > from)) return run;
96
+ const step = (to - from) / chars.length;
97
+ const first = Math.max(0, Math.floor((0 - from) / step));
98
+ const last = Math.min(chars.length - 1, Math.ceil((d.width - from) / step) - 1);
99
+ if (first > last) return run;
100
+ if (first === 0 && last === chars.length - 1) return run;
101
+ const backwards = run.endX < run.x;
102
+ const startX = from + first * step;
103
+ const endX = from + (last + 1) * step;
104
+ return {
105
+ ...run,
106
+ text: chars.slice(first, last + 1).join(""),
107
+ x: backwards ? endX : startX,
108
+ endX: backwards ? startX : endX
109
+ };
110
+ }
111
+ /**
112
+ * §14.11.2 — whether a box falls inside the region the page SHOWS.
113
+ *
114
+ * The crop box is the region "to which the contents of the page shall be
115
+ * clipped", and a page cropped to less than its sheet is generally a page with
116
+ * something outside the crop: freeculture.pdf carries a printer's slug —
117
+ * "14773_07_347-348_r4jm.qxd 2/10/04 4:45 PM Page 347" — in the 42 points the
118
+ * crop cuts off the top, and lifted with the rest it stood at the head of the
119
+ * page and pushed the sheet taller to hold it.
120
+ */
121
+ function shows(d, minX, minY, maxX, maxY) {
122
+ return maxX > 0 && minX < d.width && maxY > 0 && minY < d.height;
70
123
  }
71
124
  /**
72
125
  * The same pictures, placed on the shown page. A quarter turn swaps a picture's
@@ -89,14 +142,16 @@ function placeImages(images, d) {
89
142
  const ys = corners.map((c) => c.y);
90
143
  const minX = Math.min(...xs);
91
144
  const minY = Math.min(...ys);
145
+ const turned = (img.rotationDeg ?? 0) + d.turnDeg;
92
146
  return {
93
147
  ...img,
94
148
  x: minX,
95
149
  y: minY,
96
150
  widthPt: Math.max(...xs) - minX,
97
- heightPt: Math.max(...ys) - minY
151
+ heightPt: Math.max(...ys) - minY,
152
+ ...Math.abs(turned) > .5 ? { rotationDeg: turned } : {}
98
153
  };
99
- });
154
+ }).filter((img) => shows(d, img.x, img.y, img.x + img.widthPt, img.y + img.heightPt));
100
155
  }
101
156
  /**
102
157
  * The same paths, placed on the shown page. A path is geometry, so every point
@@ -156,7 +211,7 @@ function placeVectors(vectors, d) {
156
211
  maxX: Math.max(...xs),
157
212
  maxY: Math.max(...ys)
158
213
  };
159
- });
214
+ }).filter((v) => shows(d, v.minX, v.minY, v.maxX, v.maxY));
160
215
  }
161
216
  //#endregion
162
217
  export { displayOf, placeImages, placeRuns, placeVectors };
@@ -5,6 +5,12 @@ export type Rectangle = readonly [number, number, number, number];
5
5
  export interface PdfPage {
6
6
  readonly dict: PdfDict;
7
7
  readonly mediaBox: Rectangle;
8
+ /**
9
+ * §14.11.2 `/CropBox` — the region of the page a viewer SHOWS, which is what
10
+ * a page's size means to anyone looking at it. Defaults to the media box and
11
+ * is clipped to it; inherited down the page tree like the media box is.
12
+ */
13
+ readonly cropBox: Rectangle;
8
14
  /**
9
15
  * §14.11.1 `/Rotate` — how far the page turns CLOCKWISE when it is shown,
10
16
  * normalised to 0, 90, 180 or 270 and inherited down the page tree.
@@ -1,8 +1,10 @@
1
1
  import { PDF_NULL, PdfName, PdfRef, PdfStream } from "../pdf/objects.js";
2
+ import { lzwDecodeMsb } from "../core/lzw.js";
2
3
  import { buildDecryptor } from "./decrypt.js";
3
4
  import { Lexer } from "./lexer.js";
4
5
  import { parseIndirectObject, parseObject } from "./parser.js";
5
6
  import { reversePredictor } from "./predictor.js";
7
+ import { ascii85Decode, asciiHexDecode, runLengthDecode } from "./stream-filters.js";
6
8
  import { unzlibSync } from "fflate";
7
9
  //#region src/pdf-reader/document.ts
8
10
  var DEFAULT_MEDIA_BOX = [
@@ -162,6 +164,7 @@ var PdfFile = class PdfFile {
162
164
  if (out.length >= MAX_PAGES || seen.has(node)) return;
163
165
  seen.add(node);
164
166
  const mediaBox = readRectangle(this.get(node, "MediaBox")) ?? inherited.mediaBox;
167
+ const cropBox = readRectangle(this.get(node, "CropBox")) ?? inherited.cropBox;
165
168
  const resourcesVal = this.get(node, "Resources");
166
169
  const resources = resourcesVal instanceof Map ? resourcesVal : inherited.resources;
167
170
  const rotate = readRotate(this.get(node, "Rotate")) ?? inherited.rotate;
@@ -172,6 +175,7 @@ var PdfFile = class PdfFile {
172
175
  const kidNode = this.resolve(kid);
173
176
  if (kidNode instanceof Map) this.walkPageTree(kidNode, {
174
177
  mediaBox,
178
+ cropBox,
175
179
  resources,
176
180
  rotate
177
181
  }, out, seen);
@@ -179,9 +183,11 @@ var PdfFile = class PdfFile {
179
183
  }
180
184
  return;
181
185
  }
186
+ const media = mediaBox ?? DEFAULT_MEDIA_BOX;
182
187
  out.push({
183
188
  dict: node,
184
- mediaBox: mediaBox ?? DEFAULT_MEDIA_BOX,
189
+ mediaBox: media,
190
+ cropBox: shownBox(media, cropBox),
185
191
  rotate: rotate ?? 0,
186
192
  resources
187
193
  });
@@ -215,7 +221,19 @@ var PdfFile = class PdfFile {
215
221
  data = unzlibSync(data);
216
222
  flate = true;
217
223
  } catch {}
218
- else if (this.filters[f.value]) {
224
+ else if (GENERAL_FILTERS[f.value]) {
225
+ data = GENERAL_FILTERS[f.value](data);
226
+ flate = true;
227
+ } else if (f.value === "LZWDecode" || f.value === "LZW") {
228
+ const parms = this.resolve(stream.dict.get("DecodeParms") ?? PDF_NULL);
229
+ const p = Array.isArray(parms) ? this.resolve(parms[0] ?? PDF_NULL) : parms;
230
+ const early = p instanceof Map ? this.resolve(p.get("EarlyChange") ?? PDF_NULL) : void 0;
231
+ data = lzwDecodeMsb(data, {
232
+ earlyChange: early === 0 ? 0 : 1,
233
+ limit: MAX_LZW_OUT
234
+ });
235
+ flate = true;
236
+ } else if (this.filters[f.value]) {
219
237
  const decoded = runFilter(this.filters[f.value], data);
220
238
  if (decoded) data = decoded;
221
239
  else this.unknownFilters.add(f.value);
@@ -236,15 +254,18 @@ function runFilter(filter, data) {
236
254
  return;
237
255
  }
238
256
  }
257
+ /** A decompressed LZW stream larger than this is a bomb, not a document. */
258
+ var MAX_LZW_OUT = 1 << 28;
259
+ /** §7.4.2–7.4.5 — the filters that are transport, whatever the stream holds. */
260
+ var GENERAL_FILTERS = {
261
+ ASCIIHexDecode: asciiHexDecode,
262
+ AHx: asciiHexDecode,
263
+ ASCII85Decode: ascii85Decode,
264
+ A85: ascii85Decode,
265
+ RunLengthDecode: runLengthDecode,
266
+ RL: runLengthDecode
267
+ };
239
268
  var PASSTHROUGH_FILTERS = new Set([
240
- "LZWDecode",
241
- "LZW",
242
- "RunLengthDecode",
243
- "RL",
244
- "ASCII85Decode",
245
- "A85",
246
- "ASCIIHexDecode",
247
- "AHx",
248
269
  "DCTDecode",
249
270
  "DCT",
250
271
  "JPXDecode",
@@ -526,5 +547,29 @@ function lastIndexOfAscii(buf, needle) {
526
547
  }
527
548
  return -1;
528
549
  }
550
+ /**
551
+ * §14.11.2 — the box a viewer SHOWS: the crop box where there is one, clipped
552
+ * to the media box, and the media box where there is not.
553
+ *
554
+ * A crop box that states nothing usable — zero-sized, or outside the sheet
555
+ * altogether — is a crop box no viewer honours, and the media box stands.
556
+ * bug1844576.pdf is 612×792 of paper cropped to 181×53.75, and read as its
557
+ * media box the form on it arrived a fifth of the way down a letter page.
558
+ */
559
+ function shownBox(media, crop) {
560
+ if (!crop) return media;
561
+ const [mx0, my0, mx1, my1] = media;
562
+ const [cx0, cy0, cx1, cy1] = crop;
563
+ const x0 = Math.max(Math.min(mx0, mx1), Math.min(cx0, cx1));
564
+ const y0 = Math.max(Math.min(my0, my1), Math.min(cy0, cy1));
565
+ const x1 = Math.min(Math.max(mx0, mx1), Math.max(cx0, cx1));
566
+ const y1 = Math.min(Math.max(my0, my1), Math.max(cy0, cy1));
567
+ return x1 - x0 > 1 && y1 - y0 > 1 ? [
568
+ x0,
569
+ y0,
570
+ x1,
571
+ y1
572
+ ] : media;
573
+ }
529
574
  //#endregion
530
575
  export { PdfFile };
@@ -1,5 +1,6 @@
1
1
  import { PdfDict } from '../pdf/objects.js';
2
2
  import { PdfFile, PdfPage } from './document.js';
3
+ import { Loss } from '../core/ir/index.js';
3
4
  import { FontRegistry } from '../core/font/index.js';
4
5
  /**
5
6
  * Every embedded TrueType program the pages use, by the name a run will ask for
@@ -15,11 +16,19 @@ import { FontRegistry } from '../core/font/index.js';
15
16
  * (`/FontFile`) are different formats that the layout's parser does not take;
16
17
  * those keep their substitute.
17
18
  *
18
- * @param file The owning file.
19
- * @param pages The pages whose fonts are wanted.
19
+ * And only one whose OUTLINES this pipeline can carry: a program with no
20
+ * `glyf`/`loca` cannot be subset, and a face offered here is one the writer
21
+ * will be asked to embed. bug1186827.pdf ships an OpenType/CFF program under
22
+ * `/FontFile2`, which parses like any other and then killed the whole
23
+ * conversion — "Subsetting requires a TrueType font with glyf+loca tables" —
24
+ * rather than losing one face.
25
+ *
26
+ * @param file The owning file.
27
+ * @param pages The pages whose fonts are wanted.
28
+ * @param losses Appended to for a face the page embeds and this cannot carry.
20
29
  * @returns Name → a one-face registry holding that program.
21
30
  */
22
- export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray<PdfPage>): Map<string, FontRegistry>;
31
+ export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray<PdfPage>, losses?: Array<Loss>): Map<string, FontRegistry>;
23
32
  /**
24
33
  * The name a run set in `fontDict` will ask for: its `/BaseFont` without the
25
34
  * six-capital subset prefix (§9.6.4), lowercased.
@@ -29,3 +38,14 @@ export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray
29
38
  * @returns The name, or `undefined` when the font states none.
30
39
  */
31
40
  export declare function embeddedFontName(file: PdfFile, fontDict: PdfDict): string | undefined;
41
+ /**
42
+ * Whether this font carries a program this pipeline can set the page in.
43
+ *
44
+ * Only `/FontFile2` is liftable, and a run in such a face is filed under the
45
+ * face's own name — so that name must be left exactly as it is. Every other
46
+ * face is SUBSTITUTED, and its name is free to say what it is.
47
+ *
48
+ * @param file The document.
49
+ * @param fontDict The font dictionary.
50
+ */
51
+ export declare function hasLiftableProgram(file: PdfFile, fontDict: PdfDict): boolean;
@@ -1,5 +1,7 @@
1
+ import { parseTtf } from "../core/font/ttf-parser.js";
1
2
  import { FontRegistry } from "../core/font/font-registry.js";
2
3
  import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
4
+ import { FEATURES } from "../core/ir/features.js";
3
5
  //#region src/pdf-reader/embedded-fonts.ts
4
6
  var MAX_FORM_DEPTH = 8;
5
7
  /**
@@ -16,11 +18,19 @@ var MAX_FORM_DEPTH = 8;
16
18
  * (`/FontFile`) are different formats that the layout's parser does not take;
17
19
  * those keep their substitute.
18
20
  *
19
- * @param file The owning file.
20
- * @param pages The pages whose fonts are wanted.
21
+ * And only one whose OUTLINES this pipeline can carry: a program with no
22
+ * `glyf`/`loca` cannot be subset, and a face offered here is one the writer
23
+ * will be asked to embed. bug1186827.pdf ships an OpenType/CFF program under
24
+ * `/FontFile2`, which parses like any other and then killed the whole
25
+ * conversion — "Subsetting requires a TrueType font with glyf+loca tables" —
26
+ * rather than losing one face.
27
+ *
28
+ * @param file The owning file.
29
+ * @param pages The pages whose fonts are wanted.
30
+ * @param losses Appended to for a face the page embeds and this cannot carry.
21
31
  * @returns Name → a one-face registry holding that program.
22
32
  */
23
- function collectEmbeddedFonts(file, pages) {
33
+ function collectEmbeddedFonts(file, pages, losses) {
24
34
  const out = /* @__PURE__ */ new Map();
25
35
  const seen = /* @__PURE__ */ new Set();
26
36
  const visiting = /* @__PURE__ */ new Set();
@@ -32,6 +42,15 @@ function collectEmbeddedFonts(file, pages) {
32
42
  const program = fontProgram(file, fontDict);
33
43
  if (!program) return;
34
44
  try {
45
+ const parsed = parseTtf(program);
46
+ if (!parsed.tables.has("glyf") || !parsed.tables.has("loca")) {
47
+ losses?.push({
48
+ severity: "degraded",
49
+ feature: FEATURES.text,
50
+ detail: `embedded font ${name} has no TrueType outlines (a CFF program under /FontFile2); its text is re-set in a substitute face`
51
+ });
52
+ return;
53
+ }
35
54
  out.set(name, FontRegistry.fromBytes({ regular: program }));
36
55
  } catch {}
37
56
  };
@@ -73,6 +92,19 @@ function embeddedFontName(file, fontDict) {
73
92
  const name = base.value.replace(/^[A-Z]{6}\+/u, "").trim();
74
93
  return name.length > 0 ? name.toLowerCase() : void 0;
75
94
  }
95
+ /**
96
+ * Whether this font carries a program this pipeline can set the page in.
97
+ *
98
+ * Only `/FontFile2` is liftable, and a run in such a face is filed under the
99
+ * face's own name — so that name must be left exactly as it is. Every other
100
+ * face is SUBSTITUTED, and its name is free to say what it is.
101
+ *
102
+ * @param file The document.
103
+ * @param fontDict The font dictionary.
104
+ */
105
+ function hasLiftableProgram(file, fontDict) {
106
+ return fontProgram(file, fontDict) !== void 0;
107
+ }
76
108
  /** §9.9 `/FontFile2` — the TrueType program, off the font or its descendant. */
77
109
  function fontProgram(file, fontDict) {
78
110
  const owner = descendant(file, fontDict) ?? fontDict;
@@ -91,4 +123,4 @@ function descendant(file, fontDict) {
91
123
  return first instanceof Map ? first : void 0;
92
124
  }
93
125
  //#endregion
94
- export { collectEmbeddedFonts, embeddedFontName };
126
+ export { collectEmbeddedFonts, embeddedFontName, hasLiftableProgram };
@@ -0,0 +1,25 @@
1
+ /** One of the base encodings this reader has a table for (Annex D.2). */
2
+ export type BaseEncodingName = 'StandardEncoding' | 'WinAnsiEncoding' | 'MacRomanEncoding';
3
+ /**
4
+ * The glyph names of a base encoding, or `undefined` for a name this has no
5
+ * table for — `/MacExpertEncoding`, whose repertory no reader here can set.
6
+ *
7
+ * @param name The `/Encoding` or `/BaseEncoding` name, without the slash.
8
+ */
9
+ export declare function baseEncodingTable(name: string): ReadonlyMap<number, string> | undefined;
10
+ /** The encoding built into the standard Latin text faces (§9.6.2.2). */
11
+ export declare function standardEncodingTable(): ReadonlyMap<number, string>;
12
+ /**
13
+ * The name a `post` table's glyph-name index below 258 stands for.
14
+ *
15
+ * @param index The index as the table states it.
16
+ * @returns The name, or `undefined` for an index outside the standard order.
17
+ */
18
+ export declare function macGlyphName(index: number): string | undefined;
19
+ /**
20
+ * Whether a `/BaseFont` names one of the standard Latin faces, whose built-in
21
+ * encoding is StandardEncoding.
22
+ *
23
+ * @param baseFont The name as the font dictionary states it.
24
+ */
25
+ export declare function isStandardLatinFace(baseFont: string): boolean;
@@ -0,0 +1,110 @@
1
+ //#region src/pdf-reader/encodings.ts
2
+ /**
3
+ * Code → glyph name, from names read in code order. `.` marks a code the
4
+ * encoding leaves unused, and the rows are the spec's own — sixteen to a line,
5
+ * so a row can be checked against Annex D at a glance.
6
+ */
7
+ function table(start, ...rows) {
8
+ const out = /* @__PURE__ */ new Map();
9
+ let code = start;
10
+ for (const row of rows) for (const name of row.split(/\s+/u).filter(Boolean)) {
11
+ if (name !== ".") out.set(code, name);
12
+ code++;
13
+ }
14
+ return out;
15
+ }
16
+ var ASCII = table(32, "space exclam quotedbl numbersign dollar percent ampersand quotesingle parenleft parenright asterisk plus comma hyphen period slash", "zero one two three four five six seven eight nine colon semicolon less equal greater question", "at A B C D E F G H I J K L M N O", "P Q R S T U V W X Y Z bracketleft backslash bracketright asciicircum underscore", "grave a b c d e f g h i j k l m n o", "p q r s t u v w x y z braceleft bar braceright asciitilde .");
17
+ /** Annex D.2 STD — the built-in encoding of the standard Latin text faces. */
18
+ var STANDARD = new Map([
19
+ ...ASCII,
20
+ ...table(39, "quoteright"),
21
+ ...table(96, "quoteleft"),
22
+ ...table(160, ". exclamdown cent sterling fraction yen florin section currency quotesingle quotedblleft guillemotleft guilsinglleft guilsinglright fi fl", ". endash dagger daggerdbl periodcentered . paragraph bullet quotesinglbase quotedblbase quotedblright guillemotright ellipsis perthousand . questiondown", ". grave acute circumflex tilde macron breve dotaccent dieresis . ring cedilla . hungarumlaut ogonek caron", "emdash . . . . . . . . . . . . . . .", ". AE . ordfeminine . . . . Lslash Oslash OE ordmasculine . . . .", ". ae . . . dotlessi . . lslash oslash oe germandbls . . . .")
23
+ ]);
24
+ var TABLES = {
25
+ StandardEncoding: STANDARD,
26
+ WinAnsiEncoding: new Map([...ASCII, ...table(128, "Euro . quotesinglbase florin quotedblbase ellipsis dagger daggerdbl circumflex perthousand Scaron guilsinglleft OE . Zcaron .", ". quoteleft quoteright quotedblleft quotedblright bullet endash emdash tilde trademark scaron guilsinglright oe . zcaron Ydieresis")]),
27
+ MacRomanEncoding: new Map([...ASCII, ...table(128, "Adieresis Aring Ccedilla Eacute Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde aring ccedilla eacute egrave", "ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute ograve ocircumflex odieresis otilde uacute ugrave ucircumflex udieresis", "dagger degree cent sterling section bullet paragraph germandbls registered copyright trademark acute dieresis notequal AE Oslash", "infinity plusminus lessequal greaterequal yen mu partialdiff summation product pi integral ordfeminine ordmasculine Omega ae oslash", "questiondown exclamdown logicalnot radical florin approxequal Delta guillemotleft guillemotright ellipsis space Agrave Atilde Otilde OE oe", "endash emdash quotedblleft quotedblright quoteleft quoteright divide lozenge ydieresis Ydieresis fraction currency guilsinglleft guilsinglright fi fl", "daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex Ecircumflex Aacute Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex", ". Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde macron breve dotaccent ring cedilla hungarumlaut ogonek caron")])
28
+ };
29
+ /**
30
+ * The glyph names of a base encoding, or `undefined` for a name this has no
31
+ * table for — `/MacExpertEncoding`, whose repertory no reader here can set.
32
+ *
33
+ * @param name The `/Encoding` or `/BaseEncoding` name, without the slash.
34
+ */
35
+ function baseEncodingTable(name) {
36
+ return name in TABLES ? TABLES[name] : void 0;
37
+ }
38
+ /** The encoding built into the standard Latin text faces (§9.6.2.2). */
39
+ function standardEncodingTable() {
40
+ return STANDARD;
41
+ }
42
+ var STANDARD_LATIN_FACES = new Set([
43
+ "courier",
44
+ "courier-bold",
45
+ "courier-oblique",
46
+ "courier-boldoblique",
47
+ "helvetica",
48
+ "helvetica-bold",
49
+ "helvetica-oblique",
50
+ "helvetica-boldoblique",
51
+ "times-roman",
52
+ "times-bold",
53
+ "times-italic",
54
+ "times-bolditalic"
55
+ ]);
56
+ var MAC_ORDER = `
57
+ .notdef .null nonmarkingreturn space exclam quotedbl numbersign dollar
58
+ percent ampersand quotesingle parenleft parenright asterisk plus comma
59
+ hyphen period slash zero one two three four
60
+ five six seven eight nine colon semicolon less
61
+ equal greater question at A B C D
62
+ E F G H I J K L
63
+ M N O P Q R S T
64
+ U V W X Y Z bracketleft backslash
65
+ bracketright asciicircum underscore grave a b c d
66
+ e f g h i j k l
67
+ m n o p q r s t
68
+ u v w x y z braceleft bar
69
+ braceright asciitilde Adieresis Aring Ccedilla Eacute Ntilde Odieresis
70
+ Udieresis aacute agrave acircumflex adieresis atilde aring ccedilla
71
+ eacute egrave ecircumflex edieresis iacute igrave icircumflex idieresis
72
+ ntilde oacute ograve ocircumflex odieresis otilde uacute ugrave
73
+ ucircumflex udieresis dagger degree cent sterling section bullet
74
+ paragraph germandbls registered copyright trademark acute dieresis notequal
75
+ AE Oslash infinity plusminus lessequal greaterequal yen mu
76
+ partialdiff summation product pi integral ordfeminine ordmasculine Omega
77
+ ae oslash questiondown exclamdown logicalnot radical florin approxequal
78
+ Delta guillemotleft guillemotright ellipsis nonbreakingspace Agrave Atilde Otilde
79
+ OE oe endash emdash quotedblleft quotedblright quoteleft quoteright
80
+ divide lozenge ydieresis Ydieresis fraction currency guilsinglleft guilsinglright
81
+ fi fl daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex
82
+ Ecircumflex Aacute Edieresis Egrave Iacute Icircumflex Idieresis Igrave
83
+ Oacute Ocircumflex apple Ograve Uacute Ucircumflex Ugrave dotlessi
84
+ circumflex tilde macron breve dotaccent ring cedilla hungarumlaut
85
+ ogonek caron Lslash lslash Scaron scaron Zcaron zcaron
86
+ brokenbar Eth eth Yacute yacute Thorn thorn minus
87
+ multiply onesuperior twosuperior threesuperior onehalf onequarter threequarters franc
88
+ Gbreve gbreve Idotaccent Scedilla scedilla Cacute cacute Ccaron
89
+ ccaron dcroat
90
+ `.split(/\s+/u).filter(Boolean);
91
+ /**
92
+ * The name a `post` table's glyph-name index below 258 stands for.
93
+ *
94
+ * @param index The index as the table states it.
95
+ * @returns The name, or `undefined` for an index outside the standard order.
96
+ */
97
+ function macGlyphName(index) {
98
+ return MAC_ORDER[index];
99
+ }
100
+ /**
101
+ * Whether a `/BaseFont` names one of the standard Latin faces, whose built-in
102
+ * encoding is StandardEncoding.
103
+ *
104
+ * @param baseFont The name as the font dictionary states it.
105
+ */
106
+ function isStandardLatinFace(baseFont) {
107
+ return STANDARD_LATIN_FACES.has(baseFont.trim().replace(/^[A-Za-z]{6}\+/u, "").toLowerCase());
108
+ }
109
+ //#endregion
110
+ export { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable };
@@ -1,10 +1,12 @@
1
- import { BodyElement, SectionProperties, TextOutline } from '../core/document-model/index.js';
1
+ import { BodyElement, ParagraphProperties, Section, SectionProperties, TextOutline } from '../core/document-model/index.js';
2
2
  import { FlowDoc } from '../core/ir/flow.js';
3
3
  import { FontRegistry } from '../core/font/index.js';
4
4
  import { Loss, ResourceStore } from '../core/ir/index.js';
5
5
  import { PdfImage } from './images.js';
6
6
  import { PdfPage } from './document.js';
7
7
  import { PdfVector } from './vector.js';
8
+ import { TextMarkup } from './annot-draw.js';
9
+ import { TextRun } from './content.js';
8
10
  /**
9
11
  * A reconstruction's document plus the losses incurred reading it (e.g. an
10
12
  * undecodable image colour space) — surfaced through the reader's `LossReport`.
@@ -49,6 +51,14 @@ export interface TextSpan {
49
51
  readonly bold?: boolean;
50
52
  /** §9.8.1 — the face was a slanted one. */
51
53
  readonly italic?: boolean;
54
+ /**
55
+ * §17.3.2.42 — the glyphs stood OFF the baseline the line is set on, and
56
+ * smaller: a footnote mark, an exponent, an index. The page states it by
57
+ * placement, and a document by the property.
58
+ */
59
+ readonly script?: 'superscript' | 'subscript';
60
+ /** §12.5.6.10 — a text-markup annotation marks these words. */
61
+ readonly markup?: TextMarkup;
52
62
  }
53
63
  /**
54
64
  * Build a paragraph {@link BodyElement} from positioned {@link TextSpan}s,
@@ -56,13 +66,13 @@ export interface TextSpan {
56
66
  * survives as its own run) and squashing whitespace. With no hrefs this
57
67
  * collapses to a single run — the same shape {@link paragraphBlock} produces.
58
68
  */
59
- export declare function paragraphFromRuns(spans: ReadonlyArray<TextSpan>, outlineLevel?: number): BodyElement;
69
+ export declare function paragraphFromRuns(spans: ReadonlyArray<TextSpan>, outlineLevel?: number, placement?: Pick<ParagraphProperties, 'alignment' | 'spacingBefore' | 'indentLeft' | 'indentFirstLine'>): BodyElement;
60
70
  /**
61
71
  * Store a {@link PdfImage}'s bytes (content-addressed dedup) and build the image
62
72
  * {@link BodyElement} that references them, sized in points from the placement
63
73
  * CTM. `alt` becomes the block's alt text when given.
64
74
  */
65
- export declare function imageBlock(image: PdfImage, resources: ResourceStore, alt?: string, frame?: PageFrame, zOrder?: number): BodyElement;
75
+ export declare function imageBlock(image: PdfImage, resources: ResourceStore, alt?: string, frame?: PageFrame, zOrder?: number, behind?: boolean): BodyElement;
66
76
  /**
67
77
  * A line of text as an anchored box, standing where the page set it.
68
78
  *
@@ -100,7 +110,7 @@ export declare function dedupeLosses(losses: ReadonlyArray<Loss>): Array<Loss>;
100
110
  * a paragraph: 22060_A1_01_Plans.pdf is one A3 sheet of vectors, and stacking
101
111
  * its forty-nine paths one under another spilled it onto a second page.
102
112
  */
103
- export declare function shapeBlock(v: PdfVector, frame?: PageFrame, zOrder?: number): BodyElement;
113
+ export declare function shapeBlock(v: PdfVector, frame?: PageFrame, zOrder?: number, behind?: boolean): BodyElement;
104
114
  /**
105
115
  * Derive the {@link SectionProperties} geometry from the source pages so a
106
116
  * reconstructed PDF re-renders at its real page size and orientation rather than
@@ -113,6 +123,10 @@ export declare function shapeBlock(v: PdfVector, frame?: PageFrame, zOrder?: num
113
123
  * `A4`. Returns `undefined` when there is no usable first-page box.
114
124
  */
115
125
  export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage>): SectionProperties | undefined;
126
+ export declare function withMeasuredMargins(section: SectionProperties | undefined, shown: ReadonlyArray<{
127
+ width: number;
128
+ height: number;
129
+ }>, pageRuns: ReadonlyArray<ReadonlyArray<TextRun>>): SectionProperties | undefined;
116
130
  /**
117
131
  * Assemble the final {@link FlowDoc} for a reconstruction: the body elements
118
132
  * with their styles resolved against the empty style sheet, the lifted-image
@@ -120,4 +134,4 @@ export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage>): Sect
120
134
  * both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
121
135
  * path EP4).
122
136
  */
123
- export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>): FlowDoc;
137
+ export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>, sections?: ReadonlyArray<Section>, headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>): FlowDoc;