reamkit 1.27.0 → 1.29.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 (68) hide show
  1. package/README.md +6 -5
  2. package/dist/esm/core/converter/ream.d.ts +8 -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 +16 -6
  24. package/dist/esm/pdf-reader/content.d.ts +91 -0
  25. package/dist/esm/pdf-reader/content.js +156 -60
  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.d.ts +8 -0
  30. package/dist/esm/pdf-reader/document.js +26 -9
  31. package/dist/esm/pdf-reader/embedded-fonts.d.ts +11 -0
  32. package/dist/esm/pdf-reader/embedded-fonts.js +14 -1
  33. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  34. package/dist/esm/pdf-reader/encodings.js +110 -0
  35. package/dist/esm/pdf-reader/flow-build.d.ts +15 -4
  36. package/dist/esm/pdf-reader/flow-build.js +24 -11
  37. package/dist/esm/pdf-reader/font.js +329 -8
  38. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  39. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  40. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  41. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  42. package/dist/esm/pdf-reader/icc.js +210 -0
  43. package/dist/esm/pdf-reader/image-decode.d.ts +4 -3
  44. package/dist/esm/pdf-reader/image-decode.js +85 -64
  45. package/dist/esm/pdf-reader/images.d.ts +6 -0
  46. package/dist/esm/pdf-reader/images.js +50 -7
  47. package/dist/esm/pdf-reader/jbig2.js +104 -19
  48. package/dist/esm/pdf-reader/layout.js +1531 -55
  49. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  50. package/dist/esm/pdf-reader/math-rows.js +198 -0
  51. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  52. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  53. package/dist/esm/pdf-reader/reader.d.ts +6 -9
  54. package/dist/esm/pdf-reader/reader.js +34 -21
  55. package/dist/esm/pdf-reader/shading.d.ts +81 -2
  56. package/dist/esm/pdf-reader/shading.js +191 -25
  57. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  58. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  59. package/dist/esm/pdf-reader/tagged.js +1 -1
  60. package/dist/esm/pdf-reader/text-rules.js +83 -10
  61. package/dist/esm/pdf-reader/text.js +82 -3
  62. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  63. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  64. package/dist/esm/pdf-reader/vector.d.ts +2 -0
  65. package/dist/esm/pdf-reader/vector.js +44 -4
  66. package/dist/esm/word/document-parser.js +4 -1
  67. package/dist/esm/word/docx-writer.js +28 -11
  68. package/package.json +1 -1
@@ -66,7 +66,47 @@ function placeRuns(runs, d) {
66
66
  endY: end.y,
67
67
  ...Math.abs(angle) > .5 ? { angleDeg: angle } : {}
68
68
  };
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));
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
+ };
70
110
  }
71
111
  /**
72
112
  * §14.11.2 — whether a box falls inside the region the page SHOWS.
@@ -110,6 +110,14 @@ export declare class PdfFile {
110
110
  * reader takes one from whoever needs it:
111
111
  *
112
112
  * ```ts
113
+ * // browser: any wasm/JS decoder you ship — brotli-dec-wasm is ~200 KB
114
+ * import brotliPromise from 'brotli-dec-wasm';
115
+ * const brotli = await brotliPromise;
116
+ * Ream.parse(pdf, { filters: { BrotliDecode: (b) => brotli.decompress(b) } });
117
+ * ```
118
+ *
119
+ * ```ts
120
+ * // node, where the runtime already carries one
113
121
  * import { brotliDecompressSync } from 'node:zlib';
114
122
  * Ream.parse(pdf, { filters: { BrotliDecode: (b) => brotliDecompressSync(b) } });
115
123
  * ```
@@ -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 = [
@@ -219,7 +221,19 @@ var PdfFile = class PdfFile {
219
221
  data = unzlibSync(data);
220
222
  flate = true;
221
223
  } catch {}
222
- 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]) {
223
237
  const decoded = runFilter(this.filters[f.value], data);
224
238
  if (decoded) data = decoded;
225
239
  else this.unknownFilters.add(f.value);
@@ -240,15 +254,18 @@ function runFilter(filter, data) {
240
254
  return;
241
255
  }
242
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
+ };
243
268
  var PASSTHROUGH_FILTERS = new Set([
244
- "LZWDecode",
245
- "LZW",
246
- "RunLengthDecode",
247
- "RL",
248
- "ASCII85Decode",
249
- "A85",
250
- "ASCIIHexDecode",
251
- "AHx",
252
269
  "DCTDecode",
253
270
  "DCT",
254
271
  "JPXDecode",
@@ -38,3 +38,14 @@ export declare function collectEmbeddedFonts(file: PdfFile, pages: ReadonlyArray
38
38
  * @returns The name, or `undefined` when the font states none.
39
39
  */
40
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;
@@ -92,6 +92,19 @@ function embeddedFontName(file, fontDict) {
92
92
  const name = base.value.replace(/^[A-Z]{6}\+/u, "").trim();
93
93
  return name.length > 0 ? name.toLowerCase() : void 0;
94
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
+ }
95
108
  /** §9.9 `/FontFile2` — the TrueType program, off the font or its descendant. */
96
109
  function fontProgram(file, fontDict) {
97
110
  const owner = descendant(file, fontDict) ?? fontDict;
@@ -110,4 +123,4 @@ function descendant(file, fontDict) {
110
123
  return first instanceof Map ? first : void 0;
111
124
  }
112
125
  //#endregion
113
- 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,4 +1,4 @@
1
- import { BodyElement, ParagraphProperties, 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';
@@ -51,6 +51,12 @@ export interface TextSpan {
51
51
  readonly bold?: boolean;
52
52
  /** §9.8.1 — the face was a slanted one. */
53
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';
54
60
  /** §12.5.6.10 — a text-markup annotation marks these words. */
55
61
  readonly markup?: TextMarkup;
56
62
  }
@@ -60,7 +66,7 @@ export interface TextSpan {
60
66
  * survives as its own run) and squashing whitespace. With no hrefs this
61
67
  * collapses to a single run — the same shape {@link paragraphBlock} produces.
62
68
  */
63
- export declare function paragraphFromRuns(spans: ReadonlyArray<TextSpan>, outlineLevel?: number, placement?: Pick<ParagraphProperties, 'alignment' | 'spacingBefore'>): BodyElement;
69
+ export declare function paragraphFromRuns(spans: ReadonlyArray<TextSpan>, outlineLevel?: number, placement?: Pick<ParagraphProperties, 'alignment' | 'spacingBefore' | 'indentLeft' | 'indentFirstLine' | 'tabs'>): BodyElement;
64
70
  /**
65
71
  * Store a {@link PdfImage}'s bytes (content-addressed dedup) and build the image
66
72
  * {@link BodyElement} that references them, sized in points from the placement
@@ -120,7 +126,12 @@ export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage>): Sect
120
126
  export declare function withMeasuredMargins(section: SectionProperties | undefined, shown: ReadonlyArray<{
121
127
  width: number;
122
128
  height: number;
123
- }>, pageRuns: ReadonlyArray<ReadonlyArray<TextRun>>): SectionProperties | undefined;
129
+ }>, pageRuns: ReadonlyArray<ReadonlyArray<TextRun>>, pageMarks?: ReadonlyArray<ReadonlyArray<{
130
+ x: number;
131
+ y: number;
132
+ widthPt: number;
133
+ heightPt: number;
134
+ }>>): SectionProperties | undefined;
124
135
  /**
125
136
  * Assemble the final {@link FlowDoc} for a reconstruction: the body elements
126
137
  * with their styles resolved against the empty style sheet, the lifted-image
@@ -128,4 +139,4 @@ export declare function withMeasuredMargins(section: SectionProperties | undefin
128
139
  * both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
129
140
  * path EP4).
130
141
  */
131
- export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>): FlowDoc;
142
+ export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>, sections?: ReadonlyArray<Section>, headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>): FlowDoc;
@@ -31,7 +31,7 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
31
31
  const merged = [];
32
32
  for (const s of spans) {
33
33
  const last = merged[merged.length - 1];
34
- if (last && last.href === s.href && last.sizePt === s.sizePt && last.colorHex === s.colorHex && last.fontName === s.fontName && last.outline?.colorHex === s.outline?.colorHex && last.outline?.widthPt === s.outline?.widthPt && last.bold === s.bold && last.italic === s.italic && sameMarkup(last.markup, s.markup)) last.text += s.text;
34
+ if (last && last.href === s.href && last.sizePt === s.sizePt && last.colorHex === s.colorHex && last.fontName === s.fontName && last.outline?.colorHex === s.outline?.colorHex && last.outline?.widthPt === s.outline?.widthPt && last.bold === s.bold && last.italic === s.italic && last.script === s.script && sameMarkup(last.markup, s.markup)) last.text += s.text;
35
35
  else merged.push({
36
36
  text: s.text,
37
37
  ...s.href !== void 0 ? { href: s.href } : {},
@@ -41,12 +41,13 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
41
41
  ...s.outline !== void 0 ? { outline: s.outline } : {},
42
42
  ...s.bold !== void 0 ? { bold: s.bold } : {},
43
43
  ...s.italic !== void 0 ? { italic: s.italic } : {},
44
+ ...s.script !== void 0 ? { script: s.script } : {},
44
45
  ...s.markup !== void 0 ? { markup: s.markup } : {}
45
46
  });
46
47
  }
47
48
  const runs = merged.map((m) => ({
48
49
  ...m,
49
- text: m.text.replace(/\s+/g, " ")
50
+ text: m.text.replace(/[^\S\t]+/gu, " ")
50
51
  })).filter((m) => m.text.length > 0);
51
52
  if (runs.length > 0) {
52
53
  runs[0].text = runs[0].text.replace(/^ /, "");
@@ -68,6 +69,7 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
68
69
  ...r.outline !== void 0 ? { textOutline: r.outline } : {},
69
70
  ...r.bold ? { bold: true } : {},
70
71
  ...r.italic ? { italic: true } : {},
72
+ ...r.script !== void 0 ? { verticalAlign: r.script } : {},
71
73
  ...r.markup?.highlightHex !== void 0 ? { shadingColorHex: r.markup.highlightHex } : {},
72
74
  ...r.markup?.underline !== void 0 ? { underline: r.markup.underline } : {},
73
75
  ...r.markup?.underlineHex !== void 0 ? { underlineColorHex: r.markup.underlineHex } : {},
@@ -111,6 +113,7 @@ function imageBlock(image, resources, alt, frame, zOrder, behind = false) {
111
113
  height: pt(image.heightPt),
112
114
  ...image.rotationDeg !== void 0 ? { rotation60k: Math.round(-image.rotationDeg * 6e4) } : {},
113
115
  ...image.crop ? { crop: image.crop } : {},
116
+ ...image.alpha !== void 0 ? { alpha: image.alpha } : {},
114
117
  paragraphProperties: {},
115
118
  ...alt ? { altText: alt } : {}
116
119
  }
@@ -324,11 +327,13 @@ function sectionFromPdfPages(pages) {
324
327
  */
325
328
  /** What the measure gives back, so the widest line still fits when re-set. */
326
329
  var SLACK = .01;
330
+ /** How much of the sheet a margin down the page may take. */
331
+ var DEEPEST_MARGIN = .5;
327
332
  /** How far a face's ascender stands above its baseline, as a fraction of the size. */
328
333
  var ASCENDER = .8;
329
334
  /** And its descender below — the two together are a little over one em. */
330
335
  var DESCENDER = .22;
331
- function withMeasuredMargins(section, shown, pageRuns) {
336
+ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
332
337
  if (!section?.pageSize) return section;
333
338
  const width = section.pageSize.width;
334
339
  const height = section.pageSize.height;
@@ -347,8 +352,10 @@ function withMeasuredMargins(section, shown, pageRuns) {
347
352
  let bottomSize = 0;
348
353
  for (const r of runs) {
349
354
  if (!Number.isFinite(r.x) || !Number.isFinite(r.y)) continue;
350
- minX = Math.min(minX, r.x);
351
- maxX = Math.max(maxX, r.endX);
355
+ if (r.text.trim() !== "") {
356
+ minX = Math.min(minX, r.x);
357
+ maxX = Math.max(maxX, r.endX);
358
+ }
352
359
  if (r.y < minY) {
353
360
  minY = r.y;
354
361
  bottomSize = r.fontSizePt;
@@ -358,6 +365,11 @@ function withMeasuredMargins(section, shown, pageRuns) {
358
365
  topSize = r.fontSizePt;
359
366
  }
360
367
  }
368
+ for (const mark of pageMarks[i] ?? []) {
369
+ if (!Number.isFinite(mark.x) || !Number.isFinite(mark.y)) continue;
370
+ maxX = Math.max(maxX, mark.x + mark.widthPt);
371
+ minY = Math.min(minY, mark.y);
372
+ }
361
373
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) return;
362
374
  lefts.push(minX);
363
375
  rights.push(page.width - maxX);
@@ -369,14 +381,14 @@ function withMeasuredMargins(section, shown, pageRuns) {
369
381
  const s = [...xs].sort((a, b) => a - b);
370
382
  return s[Math.floor(s.length / 2)] ?? 0;
371
383
  };
372
- const clamp = (v, span) => pt(Math.max(0, Math.min(v, span / 3)));
384
+ const clamp = (v, span, most = 1 / 3) => pt(Math.max(0, Math.min(v, span * most)));
373
385
  return {
374
386
  ...section,
375
387
  margins: {
376
- left: clamp(median(lefts), width),
388
+ left: clamp(Math.min(...lefts), width),
377
389
  right: clamp(median(rights) - width * SLACK, width),
378
- top: clamp(median(tops), height),
379
- bottom: clamp(median(bottoms), height)
390
+ top: clamp(Math.min(...tops), height, DEEPEST_MARGIN),
391
+ bottom: clamp(Math.min(...bottoms) - height * SLACK, height, DEEPEST_MARGIN)
380
392
  }
381
393
  };
382
394
  }
@@ -387,11 +399,12 @@ function withMeasuredMargins(section, shown, pageRuns) {
387
399
  * both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
388
400
  * path EP4).
389
401
  */
390
- function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFonts) {
402
+ function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFonts, sections = [], headersFooters) {
391
403
  return {
392
404
  kind: "flow",
393
405
  body: resolveBodyStyles([...body], EMPTY_STYLE_SHEET),
394
- sections: [],
406
+ sections,
407
+ ...headersFooters && headersFooters.size > 0 ? { headersFooters } : {},
395
408
  ...section ? { section } : {},
396
409
  ...embeddedFonts && embeddedFonts.size > 0 ? { embeddedFonts } : {},
397
410
  styles: EMPTY_STYLE_SHEET,