reamkit 1.30.0 → 1.32.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 (147) hide show
  1. package/README.md +70 -24
  2. package/dist/esm/core/bidi/index.d.ts +10 -0
  3. package/dist/esm/core/bidi/index.js +18 -2
  4. package/dist/esm/core/converter/facade.js +10 -5
  5. package/dist/esm/core/converter/project.js +4 -1
  6. package/dist/esm/core/converter/ream.js +17 -6
  7. package/dist/esm/core/converter/sheet-images.d.ts +17 -0
  8. package/dist/esm/core/converter/sheet-images.js +33 -0
  9. package/dist/esm/core/crypto/offcrypto.js +1 -1
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +175 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
  13. package/dist/esm/core/drawingml/chart-geometry.js +198 -108
  14. package/dist/esm/core/drawingml/chart-parser.js +73 -2
  15. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  16. package/dist/esm/core/drawingml/colors.js +23 -3
  17. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  18. package/dist/esm/core/drawingml/shape-render.js +4 -2
  19. package/dist/esm/core/font/index.d.ts +2 -0
  20. package/dist/esm/core/font/measure.d.ts +9 -0
  21. package/dist/esm/core/font/measure.js +9 -1
  22. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  23. package/dist/esm/core/font/ttf-build.js +1224 -0
  24. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  25. package/dist/esm/core/font/ttf-subset.js +14 -1
  26. package/dist/esm/core/fonts/families.js +4 -2
  27. package/dist/esm/core/fonts/provider.d.ts +17 -0
  28. package/dist/esm/core/fonts/provider.js +27 -2
  29. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  30. package/dist/esm/core/fonts/scripts.js +4 -2
  31. package/dist/esm/core/ir/flow.d.ts +77 -1
  32. package/dist/esm/core/ir/sheet.d.ts +43 -0
  33. package/dist/esm/core/number-format.js +16 -11
  34. package/dist/esm/core/numbering/index.d.ts +1 -1
  35. package/dist/esm/core/numbering/state.d.ts +11 -1
  36. package/dist/esm/core/numbering/state.js +10 -1
  37. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  38. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  39. package/dist/esm/core/style-cascade/resolver.js +39 -6
  40. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  41. package/dist/esm/core/style-cascade/types.js +3 -0
  42. package/dist/esm/excel/column-bands.d.ts +39 -6
  43. package/dist/esm/excel/column-bands.js +41 -17
  44. package/dist/esm/excel/comments-parser.js +19 -0
  45. package/dist/esm/excel/conditional-format.d.ts +7 -3
  46. package/dist/esm/excel/conditional-format.js +77 -39
  47. package/dist/esm/excel/formula/context.d.ts +18 -0
  48. package/dist/esm/excel/formula/eval.js +51 -0
  49. package/dist/esm/excel/formula/index.d.ts +2 -2
  50. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  51. package/dist/esm/excel/formula/lexer.js +41 -0
  52. package/dist/esm/excel/formula/parser.d.ts +25 -0
  53. package/dist/esm/excel/formula/parser.js +95 -0
  54. package/dist/esm/excel/print-model.d.ts +38 -1
  55. package/dist/esm/excel/print-model.js +522 -80
  56. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  57. package/dist/esm/excel/shared-strings-parser.js +11 -1
  58. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  59. package/dist/esm/excel/sheet-drawing.js +47 -13
  60. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  61. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  62. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  63. package/dist/esm/excel/sheet-to-flow.js +434 -47
  64. package/dist/esm/excel/styles-parser.d.ts +9 -0
  65. package/dist/esm/excel/styles-parser.js +80 -26
  66. package/dist/esm/excel/table-parser.js +5 -0
  67. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  68. package/dist/esm/excel/table-style-presets.js +1555 -0
  69. package/dist/esm/excel/table-style.d.ts +13 -0
  70. package/dist/esm/excel/table-style.js +75 -0
  71. package/dist/esm/excel/tint.d.ts +10 -0
  72. package/dist/esm/excel/tint.js +75 -0
  73. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  74. package/dist/esm/excel/vml-drawing.js +143 -10
  75. package/dist/esm/excel/worksheet-parser.js +89 -15
  76. package/dist/esm/excel/xls/biff-reader.js +10 -2
  77. package/dist/esm/excel/xlsx-reader.js +195 -14
  78. package/dist/esm/excel/xlsx-writer.js +8 -7
  79. package/dist/esm/html/html-writer.js +202 -20
  80. package/dist/esm/index.d.ts +2 -2
  81. package/dist/esm/layout/fit-pages.d.ts +11 -0
  82. package/dist/esm/layout/fit-pages.js +86 -0
  83. package/dist/esm/layout/line-paint.d.ts +58 -0
  84. package/dist/esm/layout/line-paint.js +107 -0
  85. package/dist/esm/layout/page-doc.d.ts +61 -3
  86. package/dist/esm/layout/page-doc.js +141 -1
  87. package/dist/esm/layout/styled-layout.d.ts +55 -7
  88. package/dist/esm/layout/styled-layout.js +1154 -169
  89. package/dist/esm/layout/turned-section.d.ts +38 -0
  90. package/dist/esm/layout/turned-section.js +205 -0
  91. package/dist/esm/markdown/markdown-writer.js +17 -6
  92. package/dist/esm/pdf/styled-page-emitter.js +114 -60
  93. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  94. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  95. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  96. package/dist/esm/pdf-reader/content.d.ts +53 -0
  97. package/dist/esm/pdf-reader/content.js +9 -1
  98. package/dist/esm/pdf-reader/display.d.ts +36 -0
  99. package/dist/esm/pdf-reader/display.js +66 -1
  100. package/dist/esm/pdf-reader/document.js +5 -1
  101. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  102. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  103. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  104. package/dist/esm/pdf-reader/encodings.js +25 -3
  105. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  106. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  107. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  108. package/dist/esm/pdf-reader/figures.js +433 -0
  109. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  110. package/dist/esm/pdf-reader/flow-build.js +345 -26
  111. package/dist/esm/pdf-reader/font.js +321 -36
  112. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  113. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  114. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  115. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  116. package/dist/esm/pdf-reader/layout.js +1703 -214
  117. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  118. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  119. package/dist/esm/pdf-reader/tagged.js +182 -23
  120. package/dist/esm/pdf-reader/text.d.ts +6 -3
  121. package/dist/esm/pdf-reader/text.js +98 -9
  122. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  123. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  124. package/dist/esm/pdf-reader/vector.js +71 -1
  125. package/dist/esm/svg/svg-text.d.ts +58 -0
  126. package/dist/esm/svg/svg-text.js +322 -0
  127. package/dist/esm/svg/svg-writer.js +135 -71
  128. package/dist/esm/word/doc/doc-reader.js +6 -2
  129. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  130. package/dist/esm/word/doc/doc-text.js +19 -1
  131. package/dist/esm/word/document-parser.d.ts +2 -2
  132. package/dist/esm/word/document-parser.js +11 -1
  133. package/dist/esm/word/docx-reader.js +5 -3
  134. package/dist/esm/word/docx-writer.js +218 -23
  135. package/dist/esm/word/drawing-parser.d.ts +5 -3
  136. package/dist/esm/word/drawing-parser.js +49 -8
  137. package/dist/esm/word/font-embed.d.ts +30 -0
  138. package/dist/esm/word/font-embed.js +173 -0
  139. package/dist/esm/word/font-table.d.ts +10 -0
  140. package/dist/esm/word/font-table.js +13 -1
  141. package/dist/esm/word/index.js +1 -1
  142. package/dist/esm/word/numbering-parser.d.ts +3 -1
  143. package/dist/esm/word/numbering-parser.js +2 -1
  144. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  145. package/dist/esm/word/paragraph-properties.js +14 -2
  146. package/dist/esm/word/run-properties.js +26 -0
  147. package/package.json +12 -3
@@ -5,6 +5,7 @@ import { textMarkupOf } from "./annot-draw.js";
5
5
  import { appearanceContent, collectPageAppearances } from "./annots.js";
6
6
  import { buildContentFont } from "./font.js";
7
7
  import { hiddenProperties, hiddenXObject } from "./optional-content.js";
8
+ import { addShown } from "./face-outlines.js";
8
9
  import { patternTint, tintedHex } from "./pattern-tint.js";
9
10
  //#region src/pdf-reader/text.ts
10
11
  var MAX_FORM_DEPTH = 8;
@@ -15,22 +16,24 @@ var MAX_FORM_DEPTH = 8;
15
16
  * origin falls inside a `/Link` annotation's `/Rect` with that link's URI (EP8)
16
17
  * so hyperlinks survive.
17
18
  *
18
- * @param file The owning {@link PdfFile}.
19
- * @param page The page to extract.
19
+ * @param file The owning {@link PdfFile}.
20
+ * @param page The page to extract.
21
+ * @param painted Where to gather the codes each font painted, for a caller
22
+ * that embeds the faces (see `./face-outlines`).
20
23
  * @returns The page's runs, each carrying an `href` when it sits under a link.
21
24
  */
22
- function extractPageText(file, page) {
25
+ function extractPageText(file, page, painted) {
23
26
  const runs = [];
24
- collectRuns(file, page.resources, file.pageContent(page), IDENTITY, 0, /* @__PURE__ */ new Set(), runs);
27
+ collectRuns(file, page.resources, file.pageContent(page), IDENTITY, 0, /* @__PURE__ */ new Set(), runs, painted);
25
28
  const own = runs.length;
26
- for (const appearance of collectPageAppearances(file, page)) collectRuns(file, appearance.resources ?? page.resources, appearanceContent(file, appearance), appearance.ctm, 1, new Set([appearance.stream]), runs);
29
+ for (const appearance of collectPageAppearances(file, page)) collectRuns(file, appearance.resources ?? page.resources, appearanceContent(file, appearance), appearance.ctm, 1, new Set([appearance.stream]), runs, painted);
27
30
  for (let i = own; i < runs.length; i++) runs[i] = {
28
31
  ...runs[i],
29
32
  annotation: true
30
33
  };
31
34
  const links = collectLinks(file, page);
32
35
  const marks = collectTextMarkup(file, page);
33
- const shown = withoutRestrikes(runs);
36
+ const shown = withAccentsComposed(withoutRestrikes(runs));
34
37
  if (links.length === 0 && marks.length === 0) return shown;
35
38
  return shown.flatMap((run) => {
36
39
  const link = links.find((l) => inRect(run.x, run.y, l.rect));
@@ -168,6 +171,91 @@ function withoutRestrikes(runs) {
168
171
  }
169
172
  /** How near, in ems, a re-strike of the same text lands to the one it thickens. */
170
173
  var RESTRIKE_EM = .08;
174
+ /**
175
+ * §9.4.3 — an accent struck over a letter, composed with it.
176
+ *
177
+ * TeX sets an accented letter its font has no glyph for as two: the accent,
178
+ * and the letter drawn back under it — "ï" is a dieresis and a dotless i.
179
+ * Read as they come, comments.pdf's "naïve" came back "na¨ıve", the accent a
180
+ * character of the word. A run that is one spacing accent, with the pen taken
181
+ * back under it for the run after it, is the accent of that run's first letter
182
+ * — or, where the accent was struck after its letter, of the last letter of the
183
+ * run before it. The two are written as the one character Unicode composes them
184
+ * into, a dotless i or j taking back its dot's place under the accent.
185
+ *
186
+ * @param runs The page's runs, in painting order.
187
+ * @returns The runs with each such accent composed into its letter.
188
+ */
189
+ function withAccentsComposed(runs) {
190
+ const out = [];
191
+ for (let i = 0; i < runs.length; i++) {
192
+ const run = runs[i];
193
+ const mark = ACCENTS.get(run.text);
194
+ const next = runs[i + 1];
195
+ const prev = out[out.length - 1];
196
+ if (mark !== void 0 && next !== void 0 && struckOver(run, next, "first")) {
197
+ const [first = "", ...rest] = [...next.text];
198
+ out.push({
199
+ ...next,
200
+ text: accented(first, mark) + rest.join("")
201
+ });
202
+ i++;
203
+ continue;
204
+ }
205
+ if (mark !== void 0 && prev !== void 0 && struckOver(run, prev, "last")) {
206
+ const chars = [...prev.text];
207
+ const last = chars.pop() ?? "";
208
+ out[out.length - 1] = {
209
+ ...prev,
210
+ text: chars.join("") + accented(last, mark)
211
+ };
212
+ continue;
213
+ }
214
+ out.push(run);
215
+ }
216
+ return out;
217
+ }
218
+ /**
219
+ * Whether an accent stands over the first letter of a run (the pen taken back
220
+ * under it) or over the last (the pen taken back to strike it): on one
221
+ * baseline, give or take the lift an accent over a capital takes, and
222
+ * overlapping it by more than a hair.
223
+ */
224
+ function struckOver(accent, base, which) {
225
+ if (accent.angleDeg !== void 0 || base.angleDeg !== void 0) return false;
226
+ const size = base.fontSizePt || accent.fontSizePt || 10;
227
+ if (Math.abs(accent.y - base.y) > size * ACCENT_LIFT_EM) return false;
228
+ if (!/^\p{L}/u.test(which === "first" ? base.text : [...base.text].at(-1) ?? "")) return false;
229
+ const hair = size * ACCENT_OVERLAP_EM;
230
+ return which === "first" ? base.x < accent.endX - hair && base.x > accent.x - size : accent.x < base.endX - hair && accent.x > base.x;
231
+ }
232
+ /** A letter with an accent composed onto it: a dotless i or j takes back its dot's place. */
233
+ function accented(letter, mark) {
234
+ return ((letter === "ı" ? "i" : letter === "ȷ" ? "j" : letter) + mark).normalize("NFC");
235
+ }
236
+ /** How far over the line, in ems, TeX lifts an accent to stand over a capital. */
237
+ var ACCENT_LIFT_EM = .6;
238
+ /** How far, in ems, an accent must overlap its letter to stand over it. */
239
+ var ACCENT_OVERLAP_EM = .05;
240
+ /** The spacing accents TeX strikes over a letter, and the combining marks they are. */
241
+ var ACCENTS = new Map([
242
+ ["`", "̀"],
243
+ ["´", "́"],
244
+ ["ˆ", "̂"],
245
+ ["^", "̂"],
246
+ ["˜", "̃"],
247
+ ["~", "̃"],
248
+ ["¯", "̄"],
249
+ ["ˉ", "̄"],
250
+ ["˘", "̆"],
251
+ ["˙", "̇"],
252
+ ["¨", "̈"],
253
+ ["˚", "̊"],
254
+ ["˝", "̋"],
255
+ ["ˇ", "̌"],
256
+ ["¸", "̧"],
257
+ ["˛", "̨"]
258
+ ]);
171
259
  var spaceCache = /* @__PURE__ */ new WeakMap();
172
260
  /** The page's shading patterns, read once per resource dictionary. */
173
261
  var shadingCache = /* @__PURE__ */ new WeakMap();
@@ -187,14 +275,15 @@ function spacesOf(file, resources) {
187
275
  spaceCache.set(resources, made);
188
276
  return made;
189
277
  }
190
- function collectRuns(file, resources, content, baseCtm, depth, visiting, out) {
278
+ function collectRuns(file, resources, content, baseCtm, depth, visiting, out, shown) {
191
279
  const result = interpretContent(content, buildFonts(file, resources), baseCtm, shadingsOf(file, resources), void 0, spacesOf(file, resources), hiddenProperties(file, resources));
192
280
  out.push(...result.texts.map((r) => withPatternColour(file, resources, r, visiting)));
281
+ if (shown) addShown(shown, result.shown);
193
282
  if (depth >= MAX_FORM_DEPTH) return;
194
283
  for (const glyph of result.glyphs) {
195
284
  if (visiting.has(glyph.stream)) continue;
196
285
  visiting.add(glyph.stream);
197
- collectRuns(file, glyph.resources ?? resources, file.streamData(glyph.stream), glyph.ctm, depth + 1, visiting, out);
286
+ collectRuns(file, glyph.resources ?? resources, file.streamData(glyph.stream), glyph.ctm, depth + 1, visiting, out, shown);
198
287
  visiting.delete(glyph.stream);
199
288
  }
200
289
  if (!resources) return;
@@ -208,7 +297,7 @@ function collectRuns(file, resources, content, baseCtm, depth, visiting, out) {
208
297
  if (!(sub instanceof PdfName) || sub.value !== "Form") continue;
209
298
  visiting.add(stream);
210
299
  const formRes = file.get(stream.dict, "Resources");
211
- collectRuns(file, formRes instanceof Map ? formRes : resources, file.streamData(stream), multiply(matrixOf(file, stream.dict), placement.ctm), depth + 1, visiting, out);
300
+ collectRuns(file, formRes instanceof Map ? formRes : resources, file.streamData(stream), multiply(matrixOf(file, stream.dict), placement.ctm), depth + 1, visiting, out, shown);
212
301
  visiting.delete(stream);
213
302
  }
214
303
  }
@@ -10,6 +10,17 @@ export interface Type1Font {
10
10
  readonly has: (name: string) => boolean;
11
11
  /** The program's own `/Encoding`: code → glyph name, where it states one. */
12
12
  readonly encoding?: ReadonlyMap<number, string>;
13
+ /**
14
+ * The embedding the program's licence allows — the OS/2 `fsType` Adobe
15
+ * writes into `FontInfo` as `/FSType`, where the program states one.
16
+ */
17
+ readonly fsType?: number;
18
+ /**
19
+ * §6.4 — how far the pen moves after a glyph, in thousandths of an em: the
20
+ * width its `hsbw` (or `sbw`) states. `undefined` where the program holds no
21
+ * such glyph or its charstring states no width first.
22
+ */
23
+ readonly advance: (name: string) => number | undefined;
13
24
  }
14
25
  /**
15
26
  * Read an embedded Type 1 program.
@@ -1,3 +1,4 @@
1
+ import { postScriptFsType } from "./cff-outline.js";
1
2
  //#region src/pdf-reader/type1-outline.ts
2
3
  /**
3
4
  * Read an embedded Type 1 program.
@@ -31,7 +32,13 @@ function type1Font(program) {
31
32
  return out;
32
33
  },
33
34
  has: (name) => parsed.charstrings.has(name),
34
- ...parsed.encoding ? { encoding: parsed.encoding } : {}
35
+ advance: (name) => {
36
+ const charstring = parsed.charstrings.get(name);
37
+ const width = charstring ? statedWidth(charstring) : void 0;
38
+ return width === void 0 ? void 0 : width * parsed.scale * 1e3;
39
+ },
40
+ ...parsed.encoding ? { encoding: parsed.encoding } : {},
41
+ ...parsed.fsType !== void 0 ? { fsType: parsed.fsType } : {}
35
42
  };
36
43
  }
37
44
  function parseType1(program) {
@@ -47,11 +54,13 @@ function parseType1(program) {
47
54
  const charstrings = readCharstrings(plain, text, lenIV);
48
55
  if (charstrings.size === 0) return void 0;
49
56
  const encoding = readEncoding(clear);
57
+ const fsType = postScriptFsType(clear);
50
58
  return {
51
59
  charstrings,
52
60
  subrs: readSubrs(plain, text, lenIV),
53
61
  ...encoding ? { encoding } : {},
54
- scale: readMatrixScale(clear) ?? .001
62
+ scale: readMatrixScale(clear) ?? .001,
63
+ ...fsType !== void 0 ? { fsType } : {}
55
64
  };
56
65
  }
57
66
  /** The `eexec` key (§7), and the one every charstring is encrypted with (§6.2). */
@@ -211,6 +220,7 @@ function runGlyph(font, program, depth) {
211
220
  let inFlex = false;
212
221
  let x = 0;
213
222
  let y = 0;
223
+ let sideBearing = 0;
214
224
  let open = false;
215
225
  let steps = 0;
216
226
  const moveTo = () => {
@@ -255,6 +265,7 @@ function runGlyph(font, program, depth) {
255
265
  case 13:
256
266
  x = stack[0] ?? 0;
257
267
  y = 0;
268
+ sideBearing = x;
258
269
  stack.length = 0;
259
270
  break;
260
271
  case 9:
@@ -404,18 +415,25 @@ function runGlyph(font, program, depth) {
404
415
  break;
405
416
  }
406
417
  if (op2 === 6) {
407
- const base = STANDARD_ENCODING[stack[3] ?? 0];
408
- const charstring = base !== void 0 ? font.charstrings.get(base) : void 0;
409
- if (charstring && depth < 2) {
418
+ const [asb = 0, adx = 0, ady = 0, bchar = 0, achar = 0] = stack;
419
+ if (open) out.push({ op: "close" });
420
+ open = false;
421
+ const part = (charCode, dx, dy) => {
422
+ const name = STANDARD_ENCODING[charCode];
423
+ const charstring = name !== void 0 ? font.charstrings.get(name) : void 0;
424
+ if (!charstring || depth >= 2) return;
410
425
  const drawn = runGlyph(font, charstring, depth + 1);
411
- if (drawn) out.push(...drawn.map((seg) => unscale(seg, font.scale)));
412
- }
426
+ if (drawn) out.push(...drawn.map((seg) => shift(unscale(seg, font.scale), dx, dy)));
427
+ };
428
+ part(bchar, 0, 0);
429
+ part(achar, adx + sideBearing - asb, ady);
413
430
  stack.length = 0;
414
431
  return false;
415
432
  }
416
433
  if (op2 === 7) {
417
434
  x = stack[0] ?? 0;
418
435
  y = stack[1] ?? 0;
436
+ sideBearing = x;
419
437
  stack.length = 0;
420
438
  break;
421
439
  }
@@ -457,10 +475,47 @@ function scaleSeg(seg, k) {
457
475
  y: seg.y * k
458
476
  };
459
477
  }
460
- /** The base of a `seac` comes back already scaled; this puts it back in glyph space. */
478
+ /**
479
+ * §6.4 — the width a charstring states before it draws: the second operand of
480
+ * `hsbw` (`sbx wx hsbw`), or the third of `sbw` (`sbx sby wx wy sbw`).
481
+ */
482
+ function statedWidth(code) {
483
+ const stack = [];
484
+ for (let i = 0; i < code.length && stack.length <= 4;) {
485
+ const b = code[i++];
486
+ if (b >= 32 && b <= 246) stack.push(b - 139);
487
+ else if (b >= 247 && b <= 250) stack.push((b - 247) * 256 + (code[i++] ?? 0) + 108);
488
+ else if (b >= 251 && b <= 254) stack.push(-(b - 251) * 256 - (code[i++] ?? 0) - 108);
489
+ else if (b === 255) {
490
+ stack.push(code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3] | 0);
491
+ i += 4;
492
+ } else if (b === 13) return stack[1];
493
+ else if (b === 12 && code[i] === 7) return stack[2];
494
+ else return void 0;
495
+ }
496
+ }
497
+ /** The parts of a `seac` come back already scaled; this puts them back in glyph space. */
461
498
  function unscale(seg, k) {
462
499
  return scaleSeg(seg, k === 0 ? 1 : 1 / k);
463
500
  }
501
+ /** A segment moved by `(dx, dy)`: where a `seac` puts its accent. */
502
+ function shift(seg, dx, dy) {
503
+ if (seg.op === "close" || dx === 0 && dy === 0) return seg;
504
+ if (seg.op === "cubic") return {
505
+ op: "cubic",
506
+ x1: seg.x1 + dx,
507
+ y1: seg.y1 + dy,
508
+ x2: seg.x2 + dx,
509
+ y2: seg.y2 + dy,
510
+ x: seg.x + dx,
511
+ y: seg.y + dy
512
+ };
513
+ return {
514
+ op: seg.op,
515
+ x: seg.x + dx,
516
+ y: seg.y + dy
517
+ };
518
+ }
464
519
  /** §C.1 — the names `seac` selects its two parts by, at the codes it states. */
465
520
  var STANDARD_ENCODING = {
466
521
  32: "space",
@@ -266,15 +266,23 @@ function overlaps(a, b) {
266
266
  * path intersection, and where the two merely overlap the answer is the smaller
267
267
  * of them — bounded, never larger than the truth.
268
268
  *
269
+ * A path is measured by where its curves reach, not where their handles stand,
270
+ * and one that reaches past its clip by no more than a sliver is inside it. An
271
+ * appearance's `/BBox` is drawn round the path it holds: comments.pdf signs its
272
+ * sixth page with a stamp of sixty-seven strokes that its box meets exactly,
273
+ * and a handle standing 0.03pt past the box made the signature a path round
274
+ * its clip — it came back a black bar the width of the text.
275
+ *
269
276
  * @param v The painted path as the interpreter saw it.
270
277
  * @returns The path to draw, or `undefined` when the clip leaves nothing.
271
278
  */
272
279
  function clipped(v) {
273
280
  const clip = v.clip;
274
281
  if (!clip) return v;
275
- const b = bbox(v.segs);
282
+ const b = reachOf(v.segs);
276
283
  if (!b) return v;
277
284
  if (clip.minX >= b.maxX || clip.maxX <= b.minX || clip.minY >= b.maxY || clip.maxY <= b.minY) return;
285
+ if (b.minX >= clip.minX - SLIVER_PT && b.maxX <= clip.maxX + SLIVER_PT && b.minY >= clip.minY - SLIVER_PT && b.maxY <= clip.maxY + SLIVER_PT) return v;
278
286
  const pathArea = Math.max(0, b.maxX - b.minX) * Math.max(0, b.maxY - b.minY);
279
287
  if (Math.max(0, clip.maxX - clip.minX) * Math.max(0, clip.maxY - clip.minY) >= pathArea) return v;
280
288
  return {
@@ -283,6 +291,68 @@ function clipped(v) {
283
291
  segs: clip.segs
284
292
  };
285
293
  }
294
+ /** How far past its clip a path may reach and still be inside it: a box's rounding. */
295
+ var SLIVER_PT = .5;
296
+ /**
297
+ * The box a path's curves reach: its points, and the turns of its curves —
298
+ * a Bézier stays inside its handles, and rarely reaches them.
299
+ *
300
+ * @param segs The path.
301
+ */
302
+ function reachOf(segs) {
303
+ let minX = Infinity;
304
+ let minY = Infinity;
305
+ let maxX = -Infinity;
306
+ let maxY = -Infinity;
307
+ const add = (x, y) => {
308
+ minX = Math.min(minX, x);
309
+ minY = Math.min(minY, y);
310
+ maxX = Math.max(maxX, x);
311
+ maxY = Math.max(maxY, y);
312
+ };
313
+ let [x, y] = [0, 0];
314
+ let [startX, startY] = [0, 0];
315
+ for (const s of segs) {
316
+ if (s.op === "close") {
317
+ [x, y] = [startX, startY];
318
+ continue;
319
+ }
320
+ if (s.op === "cubic") {
321
+ const [x0, y0] = [x, y];
322
+ for (const t of [...turnsOf(x0, s.x1, s.x2, s.x), ...turnsOf(y0, s.y1, s.y2, s.y)]) add(bezierAt(x0, s.x1, s.x2, s.x, t), bezierAt(y0, s.y1, s.y2, s.y, t));
323
+ }
324
+ add(s.x, s.y);
325
+ [x, y] = [s.x, s.y];
326
+ if (s.op === "move") [startX, startY] = [s.x, s.y];
327
+ }
328
+ return Number.isFinite(minX) ? {
329
+ minX,
330
+ minY,
331
+ maxX,
332
+ maxY
333
+ } : void 0;
334
+ }
335
+ /** Where, between its ends, one coordinate of a cubic Bézier turns back. */
336
+ function turnsOf(p0, p1, p2, p3) {
337
+ const a = p3 - 3 * p2 + 3 * p1 - p0;
338
+ const b = 2 * (p2 - 2 * p1 + p0);
339
+ const c = p1 - p0;
340
+ let roots = [];
341
+ if (Math.abs(a) < FLAT) {
342
+ if (Math.abs(b) >= FLAT) roots = [-c / b];
343
+ } else {
344
+ const d = b * b - 4 * a * c;
345
+ if (d >= 0) roots = [(-b + Math.sqrt(d)) / (2 * a), (-b - Math.sqrt(d)) / (2 * a)];
346
+ }
347
+ return roots.filter((t) => t > 0 && t < 1);
348
+ }
349
+ /** A coefficient this small is none: the curve's derivative is of lower degree. */
350
+ var FLAT = 1e-12;
351
+ /** One coordinate of a cubic Bézier at `t`. */
352
+ function bezierAt(p0, p1, p2, p3, t) {
353
+ const u = 1 - t;
354
+ return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
355
+ }
286
356
  function bbox(segs) {
287
357
  let minX = Infinity;
288
358
  let minY = Infinity;
@@ -0,0 +1,58 @@
1
+ import { FontResource, LaidOutDocument, TextLineItem } from '../layout/page-doc.js';
2
+ import { ImageCrop } from '../core/document-model/index.js';
3
+ import { VectorShape } from '../core/vector.js';
4
+ /** The glyph outlines of a page's faces, each defined once and used by id. */
5
+ export declare class SvgGlyphs {
6
+ private readonly sources;
7
+ private readonly ids;
8
+ private readonly defs;
9
+ private readonly faceIds;
10
+ /**
11
+ * The id of the path that draws glyph `gid` of `font`, defining it on first
12
+ * use: `null` for a glyph with no ink (a space), `undefined` where the face
13
+ * carries no outlines this reads — the caller then sets the token as text.
14
+ *
15
+ * @param font The face.
16
+ * @param gid The glyph.
17
+ * @returns The `<path>` id, `null`, or `undefined`.
18
+ */
19
+ use(font: FontResource, gid: number): string | null | undefined;
20
+ /** The `<defs>` holding every glyph used, or nothing when none was. */
21
+ defsXml(): string;
22
+ private sourceOf;
23
+ }
24
+ /** What drawing a line needs from the writer around it. */
25
+ export interface SvgLineCtx {
26
+ readonly glyphs: SvgGlyphs;
27
+ /** The document-wide counter generated ids are numbered from. */
28
+ readonly ids: {
29
+ n: number;
30
+ };
31
+ readonly laid: LaidOutDocument;
32
+ /** A picture in a box, as the writer draws one. */
33
+ readonly image: (out: Array<string>, placed: PlacedImage) => void;
34
+ /** A vector shape, as the writer draws one. */
35
+ readonly shape: (out: Array<string>, shape: VectorShape) => void;
36
+ }
37
+ /** A picture placed in a box on the page — its top-left corner and size, in points. */
38
+ export interface PlacedImage {
39
+ readonly resourceName: string;
40
+ readonly x: number;
41
+ readonly y: number;
42
+ readonly width: number;
43
+ readonly height: number;
44
+ readonly crop?: ImageCrop;
45
+ readonly rotationDeg?: number;
46
+ readonly flipH?: boolean;
47
+ readonly flipV?: boolean;
48
+ readonly alpha?: number;
49
+ }
50
+ /**
51
+ * Draw one laid-out line: what stands behind its glyphs, then the glyphs, a
52
+ * picture or formula wherever one stands in it.
53
+ *
54
+ * @param out The SVG fragment sink.
55
+ * @param item The line, as the layout placed it.
56
+ * @param ctx The glyph definitions and the writer's own drawing.
57
+ */
58
+ export declare function emitSvgLine(out: Array<string>, item: TextLineItem, ctx: SvgLineCtx): void;