reamkit 1.29.0 → 1.31.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 (106) hide show
  1. package/README.md +60 -24
  2. package/dist/esm/core/converter/project.js +3 -1
  3. package/dist/esm/core/crypto/offcrypto.js +1 -1
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +65 -0
  6. package/dist/esm/core/drawingml/shape-render.js +13 -1
  7. package/dist/esm/core/font/index.d.ts +2 -0
  8. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  9. package/dist/esm/core/font/ttf-build.js +1224 -0
  10. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  11. package/dist/esm/core/font/ttf-subset.js +14 -1
  12. package/dist/esm/core/fonts/index.d.ts +1 -1
  13. package/dist/esm/core/fonts/provider.d.ts +17 -0
  14. package/dist/esm/core/fonts/provider.js +27 -2
  15. package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
  16. package/dist/esm/core/fonts/remote-fonts.js +108 -18
  17. package/dist/esm/core/ir/flow.d.ts +88 -1
  18. package/dist/esm/core/numbering/index.d.ts +1 -1
  19. package/dist/esm/core/numbering/state.d.ts +11 -1
  20. package/dist/esm/core/numbering/state.js +10 -1
  21. package/dist/esm/core/style-cascade/resolver.js +35 -4
  22. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  23. package/dist/esm/core/style-cascade/types.js +3 -0
  24. package/dist/esm/index.d.ts +2 -2
  25. package/dist/esm/layout/page-doc.d.ts +10 -3
  26. package/dist/esm/layout/styled-layout.d.ts +48 -7
  27. package/dist/esm/layout/styled-layout.js +881 -119
  28. package/dist/esm/layout/turned-section.d.ts +38 -0
  29. package/dist/esm/layout/turned-section.js +193 -0
  30. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  31. package/dist/esm/pdf-reader/annot-draw.js +93 -1
  32. package/dist/esm/pdf-reader/annots.d.ts +18 -0
  33. package/dist/esm/pdf-reader/annots.js +86 -9
  34. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  35. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  36. package/dist/esm/pdf-reader/cmap.js +5 -2
  37. package/dist/esm/pdf-reader/content.d.ts +70 -4
  38. package/dist/esm/pdf-reader/content.js +172 -12
  39. package/dist/esm/pdf-reader/display.d.ts +36 -0
  40. package/dist/esm/pdf-reader/display.js +82 -1
  41. package/dist/esm/pdf-reader/document.js +5 -1
  42. package/dist/esm/pdf-reader/embedded-fonts.d.ts +25 -0
  43. package/dist/esm/pdf-reader/embedded-fonts.js +78 -9
  44. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  45. package/dist/esm/pdf-reader/encodings.js +25 -3
  46. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  47. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  48. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  49. package/dist/esm/pdf-reader/figures.js +433 -0
  50. package/dist/esm/pdf-reader/flow-build.d.ts +224 -7
  51. package/dist/esm/pdf-reader/flow-build.js +545 -39
  52. package/dist/esm/pdf-reader/font.d.ts +27 -1
  53. package/dist/esm/pdf-reader/font.js +632 -63
  54. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  55. package/dist/esm/pdf-reader/glyf-outline.js +148 -3
  56. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  57. package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
  58. package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
  59. package/dist/esm/pdf-reader/image-decode.js +70 -4
  60. package/dist/esm/pdf-reader/images.d.ts +5 -0
  61. package/dist/esm/pdf-reader/images.js +4 -2
  62. package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
  63. package/dist/esm/pdf-reader/jbig2.js +78 -16
  64. package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
  65. package/dist/esm/pdf-reader/jpeg.js +21 -1
  66. package/dist/esm/pdf-reader/layout.d.ts +119 -2
  67. package/dist/esm/pdf-reader/layout.js +2219 -184
  68. package/dist/esm/pdf-reader/lexer.d.ts +10 -0
  69. package/dist/esm/pdf-reader/lexer.js +17 -0
  70. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  71. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  72. package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
  73. package/dist/esm/pdf-reader/pattern-tint.js +21 -3
  74. package/dist/esm/pdf-reader/regions.d.ts +25 -0
  75. package/dist/esm/pdf-reader/regions.js +167 -0
  76. package/dist/esm/pdf-reader/shading.d.ts +58 -2
  77. package/dist/esm/pdf-reader/shading.js +181 -11
  78. package/dist/esm/pdf-reader/struct-tree.js +112 -8
  79. package/dist/esm/pdf-reader/tagged.js +207 -28
  80. package/dist/esm/pdf-reader/text-rules.js +1 -1
  81. package/dist/esm/pdf-reader/text.d.ts +6 -3
  82. package/dist/esm/pdf-reader/text.js +106 -22
  83. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  84. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  85. package/dist/esm/pdf-reader/vector.d.ts +8 -2
  86. package/dist/esm/pdf-reader/vector.js +105 -6
  87. package/dist/esm/word/doc/doc-reader.js +6 -2
  88. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  89. package/dist/esm/word/doc/doc-text.js +19 -1
  90. package/dist/esm/word/document-parser.d.ts +2 -2
  91. package/dist/esm/word/document-parser.js +11 -1
  92. package/dist/esm/word/docx-reader.js +5 -3
  93. package/dist/esm/word/docx-writer.js +341 -54
  94. package/dist/esm/word/drawing-parser.d.ts +5 -3
  95. package/dist/esm/word/drawing-parser.js +56 -10
  96. package/dist/esm/word/font-embed.d.ts +30 -0
  97. package/dist/esm/word/font-embed.js +173 -0
  98. package/dist/esm/word/font-table.d.ts +10 -0
  99. package/dist/esm/word/font-table.js +13 -1
  100. package/dist/esm/word/index.js +1 -1
  101. package/dist/esm/word/numbering-parser.d.ts +3 -1
  102. package/dist/esm/word/numbering-parser.js +2 -1
  103. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  104. package/dist/esm/word/paragraph-properties.js +16 -2
  105. package/dist/esm/word/run-properties.js +26 -0
  106. package/package.json +12 -5
@@ -22,3 +22,22 @@ export declare function subsetTtf(parsed: ParsedTtf, usedGids: Iterable<number>)
22
22
  * @returns The closed set of glyph ids.
23
23
  */
24
24
  export declare function glyphClosure(parsed: ParsedTtf, usedGids: Iterable<number>): Set<number>;
25
+ /**
26
+ * The sfnt table directory's binary-search fields for `numTables` tables.
27
+ *
28
+ * @param numTables How many tables the font holds.
29
+ * @returns `searchRange`, `entrySelector` and `rangeShift`, as the header states them.
30
+ */
31
+ export declare function directoryGeometry(numTables: number): {
32
+ searchRange: number;
33
+ entrySelector: number;
34
+ rangeShift: number;
35
+ };
36
+ /**
37
+ * An sfnt table checksum: the sum of its big-endian 32-bit words, the last one
38
+ * padded with zeros.
39
+ *
40
+ * @param data The table's bytes (or the whole font's).
41
+ * @returns The checksum, as an unsigned 32-bit value.
42
+ */
43
+ export declare function paddedChecksum(data: Uint8Array): number;
@@ -193,6 +193,12 @@ function assembleSubsetTtf(parsed, newGlyf, newLoca) {
193
193
  outView.setUint32(head.offset + HEAD_CHECKSUM_ADJUSTMENT_OFFSET, adjustment, false);
194
194
  return out;
195
195
  }
196
+ /**
197
+ * The sfnt table directory's binary-search fields for `numTables` tables.
198
+ *
199
+ * @param numTables How many tables the font holds.
200
+ * @returns `searchRange`, `entrySelector` and `rangeShift`, as the header states them.
201
+ */
196
202
  function directoryGeometry(numTables) {
197
203
  let entrySelector = 0;
198
204
  let pow2 = 1;
@@ -208,6 +214,13 @@ function directoryGeometry(numTables) {
208
214
  rangeShift
209
215
  };
210
216
  }
217
+ /**
218
+ * An sfnt table checksum: the sum of its big-endian 32-bit words, the last one
219
+ * padded with zeros.
220
+ *
221
+ * @param data The table's bytes (or the whole font's).
222
+ * @returns The checksum, as an unsigned 32-bit value.
223
+ */
211
224
  function paddedChecksum(data) {
212
225
  let sum = 0;
213
226
  const len = data.length;
@@ -244,4 +257,4 @@ function concatBytes(parts) {
244
257
  return out;
245
258
  }
246
259
  //#endregion
247
- export { glyphClosure, subsetTtf };
260
+ export { directoryGeometry, glyphClosure, paddedChecksum, subsetTtf };
@@ -1,3 +1,3 @@
1
- export { fetchFontSet, fetchScriptFont, isScriptKey, resolveFamilyKey, resolveFamilyStyle, clearFontCache, } from './remote-fonts.js';
1
+ export { fetchFontSet, fetchScriptFont, isScriptKey, knowsFamily, resolveFamilyKey, resolveFamilyStyle, clearFontCache, } from './remote-fonts.js';
2
2
  export { scriptForCodepoint, scriptsInFlow } from './scripts.js';
3
3
  export type { FamilyKey, FamilyStyle, FetchFontSetOptions, FetchLike, ScriptKey, SubstituteKey, } from './remote-fonts.js';
@@ -45,6 +45,23 @@ export declare function embeddedDocFontProvider(embedded: ReadonlyMap<string, Fo
45
45
  * Open CDN substitutes (Arimo / Tinos / Cousine / Carlito / Caladea — the
46
46
  * LibreOffice metric-compatible mapping).
47
47
  * Always answers; the chain reports it as a substitution.
48
+ *
49
+ * A family's set is shared by every request for it, those made while the
50
+ * download is in flight included, and a whole set is downloaded once per
51
+ * provider. A set whose regular face cannot be downloaded rejects `resolve`
52
+ * with {@link fetchFontSet}'s error rather than answering {@link NO_FONT}: this
53
+ * is the chain's last resort, and a chain that answers none sends the
54
+ * conversion to the auto-download path, which records no substitution. The
55
+ * failed set is not kept, so the next request asks the network again.
56
+ *
57
+ * Nor is a set kept that came without a bold or italic face. The requests that
58
+ * waited on it fall back to the faces it has, and the next request assembles
59
+ * the set anew: a face the network lost is asked for again, and one the CDN
60
+ * answered with an HTTP error is not, since the download cache beneath
61
+ * {@link fetchFontSet} remembers it as missing.
62
+ *
63
+ * @param options An injectable `fetch` (defaults to the global one).
64
+ * @returns The `'remote'` provider.
48
65
  */
49
66
  export declare function remoteFontProvider(options?: {
50
67
  readonly fetch?: FetchLike;
@@ -66,6 +66,23 @@ function embeddedDocFontProvider(embedded) {
66
66
  * Open CDN substitutes (Arimo / Tinos / Cousine / Carlito / Caladea — the
67
67
  * LibreOffice metric-compatible mapping).
68
68
  * Always answers; the chain reports it as a substitution.
69
+ *
70
+ * A family's set is shared by every request for it, those made while the
71
+ * download is in flight included, and a whole set is downloaded once per
72
+ * provider. A set whose regular face cannot be downloaded rejects `resolve`
73
+ * with {@link fetchFontSet}'s error rather than answering {@link NO_FONT}: this
74
+ * is the chain's last resort, and a chain that answers none sends the
75
+ * conversion to the auto-download path, which records no substitution. The
76
+ * failed set is not kept, so the next request asks the network again.
77
+ *
78
+ * Nor is a set kept that came without a bold or italic face. The requests that
79
+ * waited on it fall back to the faces it has, and the next request assembles
80
+ * the set anew: a face the network lost is asked for again, and one the CDN
81
+ * answered with an HTTP error is not, since the download cache beneath
82
+ * {@link fetchFontSet} remembers it as missing.
83
+ *
84
+ * @param options An injectable `fetch` (defaults to the global one).
85
+ * @returns The `'remote'` provider.
69
86
  */
70
87
  function remoteFontProvider(options = {}) {
71
88
  const cache = /* @__PURE__ */ new Map();
@@ -81,10 +98,18 @@ function remoteFontProvider(options = {}) {
81
98
  });
82
99
  cache.set(family, set);
83
100
  }
84
- const fonts = await set;
101
+ let fonts;
102
+ try {
103
+ fonts = await set;
104
+ } catch (error) {
105
+ if (cache.get(family) === set) cache.delete(family);
106
+ throw error;
107
+ }
108
+ if ((fonts.bold === void 0 || fonts.italic === void 0 || fonts.boldItalic === void 0) && cache.get(family) === set) cache.delete(family);
109
+ const picked = pickVariant((x) => fonts[x] !== void 0, req.bold, req.italic) ?? "regular";
85
110
  return {
86
111
  kind: "bytes",
87
- bytes: fonts[pickVariant((x) => fonts[x] !== void 0, req.bold, req.italic) ?? "regular"] ?? fonts.regular,
112
+ bytes: fonts[picked] ?? fonts.regular,
88
113
  faceName: family,
89
114
  providerId: "remote"
90
115
  };
@@ -52,6 +52,14 @@ export interface FamilyStyle {
52
52
  * @returns The chosen family and what the name said about the face.
53
53
  */
54
54
  export declare function resolveFamilyStyle(name: string | undefined): FamilyStyle;
55
+ /**
56
+ * Whether the tables KNOW a family name — so the substitute it maps to is the
57
+ * name's own answer, and not the sans that every name nobody knows falls to.
58
+ *
59
+ * @param name The referenced family or face name.
60
+ * @returns `true` where the name matched a twin, a class or a stem.
61
+ */
62
+ export declare function knowsFamily(name: string | undefined): boolean;
55
63
  /**
56
64
  * The curated substitute for a family name — {@link resolveFamilyStyle} without
57
65
  * the face it also carries.
@@ -98,7 +98,24 @@ var SERIF = new Set([
98
98
  "minion pro",
99
99
  "serif"
100
100
  ]);
101
+ var SANS = new Set([
102
+ "roboto",
103
+ "noto sans",
104
+ "notosans",
105
+ "dejavu sans",
106
+ "dejavusans",
107
+ "lucida sans",
108
+ "lucidasans",
109
+ "ubuntu",
110
+ "lato",
111
+ "inter",
112
+ "din",
113
+ "sans",
114
+ "sans serif"
115
+ ]);
101
116
  var MONO = new Set([
117
+ "mono",
118
+ "monospaced",
102
119
  "consolas",
103
120
  "monaco",
104
121
  "menlo",
@@ -138,14 +155,59 @@ var STEMS = [
138
155
  ["charter", "tinos"],
139
156
  ["baskerville", "tinos"],
140
157
  ["caslon", "tinos"],
141
- ["minionpro", "tinos"],
158
+ ["minion", "tinos"],
159
+ ["stix", "tinos"],
142
160
  ["stoneserif", "tinos"],
143
161
  ["stonesans", "arimo"],
144
- ["myriadpro", "arimo"]
162
+ ["myriad", "arimo"],
163
+ ["arial", "arimo"],
164
+ ["helvetica", "arimo"],
165
+ ["frutiger", "arimo"],
166
+ ["univers", "arimo"],
167
+ ["futura", "arimo"],
168
+ ["gillsans", "arimo"],
169
+ ["optima", "arimo"],
170
+ ["avenir", "arimo"],
171
+ ["verdana", "arimo"],
172
+ ["tahoma", "arimo"],
173
+ ["trebuchet", "arimo"],
174
+ ["segoeui", "arimo"],
175
+ ["franklingothic", "arimo"],
176
+ ["newsgothic", "arimo"],
177
+ ["tradegothic", "arimo"],
178
+ ["centurygothic", "arimo"],
179
+ ["akzidenz", "arimo"],
180
+ ["eurostile", "arimo"],
181
+ ["avantgarde", "arimo"],
182
+ ["gotham", "arimo"],
183
+ ["proximanova", "arimo"],
184
+ ["opensans", "arimo"],
185
+ ["sourcesans", "arimo"],
186
+ ["ptsans", "arimo"],
187
+ ["firasans", "arimo"],
188
+ ["montserrat", "arimo"],
189
+ ["candara", "arimo"],
190
+ ["corbel", "arimo"],
191
+ ["lucidagrande", "arimo"],
192
+ ["mssansserif", "arimo"],
193
+ ["microsoftsansserif", "arimo"],
194
+ ["dinpro", "arimo"],
195
+ ["dinnext", "arimo"],
196
+ ["monospace", "cousine"],
197
+ ["lucidatypewriter", "cousine"],
198
+ ["lucidasanstypewriter", "cousine"]
145
199
  ];
146
200
  /** The family a name STARTS with, for the families named by stem and size. */
147
201
  function familyFromStem(name) {
148
- for (const [stem, key] of STEMS) if (name.startsWith(stem)) return key;
202
+ for (const [stem, key] of STEMS) {
203
+ if (!name.startsWith(stem)) continue;
204
+ if (key === "arimo") {
205
+ const cut = name.slice(stem.length);
206
+ if (/mono|typewriter/u.test(cut)) return "cousine";
207
+ if (/serif|slab/u.test(cut)) return "tinos";
208
+ }
209
+ return key;
210
+ }
149
211
  }
150
212
  var WEIGHT_WORDS = new Map([
151
213
  ["black", "bold"],
@@ -185,6 +247,26 @@ var NARROW_SCALE = .82;
185
247
  */
186
248
  function resolveFamilyStyle(name) {
187
249
  if (!name) return { key: "arimo" };
250
+ const { words, bold, italic, narrow } = readName(name);
251
+ return {
252
+ key: familyOfWords(words) ?? "arimo",
253
+ ...bold ? { bold } : {},
254
+ ...italic ? { italic } : {},
255
+ ...narrow ? { widthScale: NARROW_SCALE } : {}
256
+ };
257
+ }
258
+ /**
259
+ * Whether the tables KNOW a family name — so the substitute it maps to is the
260
+ * name's own answer, and not the sans that every name nobody knows falls to.
261
+ *
262
+ * @param name The referenced family or face name.
263
+ * @returns `true` where the name matched a twin, a class or a stem.
264
+ */
265
+ function knowsFamily(name) {
266
+ return name !== void 0 && name !== "" && familyOfWords(readName(name).words) !== void 0;
267
+ }
268
+ /** A name's family words, with the face words that ended it taken off and read. */
269
+ function readName(name) {
188
270
  const words = name.trim().toLowerCase().split(/[\s\-_,]+/u).filter((w) => w !== "");
189
271
  let bold = false;
190
272
  let italic = false;
@@ -197,25 +279,26 @@ function resolveFamilyStyle(name) {
197
279
  if (word === "narrow") narrow = true;
198
280
  words.pop();
199
281
  }
282
+ return {
283
+ words,
284
+ bold,
285
+ italic,
286
+ narrow
287
+ };
288
+ }
289
+ /** The substitute a family's words name, or `undefined` where no table knows them. */
290
+ function familyOfWords(words) {
291
+ if (words.length === 0) return void 0;
292
+ const whole = words.join(" ");
200
293
  const tries = [
201
- words.join(" "),
294
+ whole,
202
295
  words[words.length - 1],
203
296
  words[0]
204
297
  ];
205
- let key = "arimo";
206
298
  for (const n of tries) {
207
- const found = EXACT[n] ?? (MONO.has(n) ? "cousine" : SERIF.has(n) ? "tinos" : void 0) ?? familyFromStem(n.replace(/[^a-z]/gu, ""));
208
- if (found) {
209
- key = found;
210
- break;
211
- }
299
+ const found = EXACT[n] ?? (MONO.has(n) ? "cousine" : SERIF.has(n) ? "tinos" : void 0) ?? (n === whole && SANS.has(n) ? "arimo" : void 0) ?? familyFromStem(n.replace(/[^a-z]/gu, ""));
300
+ if (found) return found;
212
301
  }
213
- return {
214
- key,
215
- ...bold ? { bold } : {},
216
- ...italic ? { italic } : {},
217
- ...narrow ? { widthScale: NARROW_SCALE } : {}
218
- };
219
302
  }
220
303
  /**
221
304
  * The curated substitute for a family name — {@link resolveFamilyStyle} without
@@ -247,7 +330,14 @@ async function fetchTtf(url, fetchImpl, required) {
247
330
  })();
248
331
  cache.set(url, pending);
249
332
  }
250
- const result = await pending;
333
+ let result;
334
+ try {
335
+ result = await pending;
336
+ } catch (cause) {
337
+ if (cache.get(url) === pending) cache.delete(url);
338
+ if (!required) return void 0;
339
+ throw new Error(`Failed to download font from ${url}`, { cause });
340
+ }
251
341
  if (!result && required) {
252
342
  cache.delete(url);
253
343
  throw new Error(`Failed to download font from ${url}`);
@@ -279,4 +369,4 @@ async function fetchFontSet(options = {}) {
279
369
  };
280
370
  }
281
371
  //#endregion
282
- export { fetchFontSet, fetchScriptFont, resolveFamilyKey, resolveFamilyStyle };
372
+ export { fetchFontSet, fetchScriptFont, knowsFamily, resolveFamilyKey, resolveFamilyStyle };
@@ -1,6 +1,64 @@
1
1
  import { BodyElement, Chart, Comment, DocumentInfo, Numbering, Section, SectionProperties, ShapeFill, StyleSheet } from '../document-model/index.js';
2
- import { FontRegistry } from '../font/index.js';
2
+ import { FontRegistry, GlyphSeg } from '../font/index.js';
3
3
  import { ResourceStore } from './resources.js';
4
+ /**
5
+ * A face's family as a word processor names it (ECMA-376 §17.8.3.9 `w:font`).
6
+ */
7
+ export interface FaceFamily {
8
+ /** The family's own name: `Inter` for `Inter-SemiBold`, `Arial` for `ArialMT`. */
9
+ readonly family: string;
10
+ /** §17.8.3.10 `w:family` — the kind of face, for a reader that has to substitute. */
11
+ readonly generic: 'roman' | 'swiss' | 'modern';
12
+ }
13
+ /**
14
+ * The outlines a face drew a document's characters with — what a writer needs
15
+ * to EMBED the face, so a reader that lacks it sets the text in the face the
16
+ * source was set in instead of a substitute (ECMA-376 §17.8.1).
17
+ */
18
+ export interface FaceOutlines {
19
+ /** Each character (one code point) the document shows in the face → its glyph. */
20
+ readonly glyphs: ReadonlyMap<string, FaceGlyph>;
21
+ /**
22
+ * The pairs the source KERNED the face by: two characters → the adjustment
23
+ * to the first one's advance, in thousandths of an em (negative tightens).
24
+ */
25
+ readonly kerning?: ReadonlyMap<string, number>;
26
+ /**
27
+ * The ligatures the source drew in the face: the letters one glyph stands
28
+ * for ("fi", "ffl") → that glyph.
29
+ */
30
+ readonly ligatures?: ReadonlyMap<string, FaceGlyph>;
31
+ /**
32
+ * OS/2 `fsType` — the embedding the face's licence allows, as its program
33
+ * states it; absent where the program states nothing.
34
+ */
35
+ readonly fsType?: number;
36
+ /** The style the face IS, which is the slot a family embeds it in. */
37
+ readonly bold: boolean;
38
+ readonly italic: boolean;
39
+ /** The program's own name for the face, without a subset tag. */
40
+ readonly postScriptName: string;
41
+ /**
42
+ * The line to set the face in, above and below the baseline, in thousandths
43
+ * of an em — `descent` negative. A reader that reconstructs a page gives the
44
+ * line it measured the page against, so a paragraph set in the face's single
45
+ * spacing lands where the page put it.
46
+ */
47
+ readonly ascent: number;
48
+ readonly descent: number;
49
+ readonly capHeight?: number;
50
+ readonly xHeight?: number;
51
+ /** Degrees counterclockwise from the vertical; a face slanted right is negative. */
52
+ readonly italicAngle: number;
53
+ readonly fixedPitch: boolean;
54
+ }
55
+ /** One glyph of a {@link FaceOutlines}: what it draws and how far it advances. */
56
+ export interface FaceGlyph {
57
+ /** Its contours in a one-unit em, y up, filled by the nonzero rule; empty when blank. */
58
+ readonly outline: ReadonlyArray<GlyphSeg>;
59
+ /** How far the pen moves after it, in thousandths of an em. */
60
+ readonly advance: number;
61
+ }
4
62
  /**
5
63
  * The semantic IR tree (ir-design §5): everything a reader extracts from the
6
64
  * document bytes, format-neutrally — the flow `body` plus its document-scoped
@@ -42,6 +100,19 @@ export interface FlowDoc {
42
100
  readonly resources: ResourceStore;
43
101
  /** Fonts embedded in the source document itself (docx fontTable), by name. */
44
102
  readonly embeddedFonts?: ReadonlyMap<string, FontRegistry>;
103
+ /**
104
+ * The family each face a run names belongs to, where the two are not the
105
+ * same name. A PDF names a FACE — `Inter-SemiBold` — where a word processor
106
+ * names a family and states the weight beside it (`Inter`, bold). The layout
107
+ * finds a document's own program by the face; a writer that hands the text
108
+ * to another program names the family, which is the name that program knows.
109
+ */
110
+ readonly faceFamilies?: ReadonlyMap<string, FaceFamily>;
111
+ /**
112
+ * The outlines of the faces a run names, keyed as {@link faceFamilies} is —
113
+ * for a writer that embeds them (see {@link FaceOutlines}).
114
+ */
115
+ readonly faceOutlines?: ReadonlyMap<string, FaceOutlines>;
45
116
  /** Document metadata from docProps/core.xml. */
46
117
  readonly info?: DocumentInfo;
47
118
  /** Document natural language hint (BCP-47), e.g. for tagged-PDF /Lang. */
@@ -68,4 +139,20 @@ export interface FlowDoc {
68
139
  * @w:gutter` reserves belongs to the TOP margin rather than the left.
69
140
  */
70
141
  readonly gutterAtTop?: boolean;
142
+ /**
143
+ * [MS-DOCX] `w:compatSetting` `compatibilityMode` — the version of Word
144
+ * whose layout the document asks for: 15 is Word 2013's, which every Word
145
+ * since sets a new document by. Word opens a document that states none in
146
+ * Compatibility Mode, and forms there none of the OpenType ligatures its
147
+ * faces carry.
148
+ */
149
+ readonly compatibilityMode?: number;
150
+ /**
151
+ * The application whose rules the document is set by: `'word'` for one Word
152
+ * sets — lines as tall as the faces on them make them (§17.3.1.33), table
153
+ * rows as tall as their borders make them (§17.4.38); see the layout's
154
+ * `TypesetBy`. Absent, the layout's flat 1.2× lines and borders that take no
155
+ * room.
156
+ */
157
+ readonly typesetBy?: 'word';
71
158
  }
@@ -1,2 +1,2 @@
1
- export { NumberingState, formatLevelMarker } from './state.js';
1
+ export { NumberingState, formatCounter, formatLevelMarker } from './state.js';
2
2
  export { applyNumbering, applyNumberingToHeadersFooters } from './apply.js';
@@ -1,4 +1,4 @@
1
- import { AbstractNumbering, Numbering, NumberingInstance, NumberingLevel, NumberingReference } from '../document-model/index.js';
1
+ import { AbstractNumbering, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, NumberingReference } from '../document-model/index.js';
2
2
  /**
3
3
  * §17.9.27 — the levels an INSTANCE actually numbers by: its abstract
4
4
  * definition's, with any `w:lvlOverride/w:lvl` shadowing them. Cached per
@@ -31,3 +31,13 @@ export declare class NumberingState {
31
31
  resolveMarker(numbering: Numbering, ref: NumberingReference): string | null;
32
32
  }
33
33
  export declare function formatLevelMarker(abstractNum: AbstractNumbering, currentLevel: NumberingLevel, counters: ReadonlyArray<number>): string;
34
+ /**
35
+ * A counter in the numerals a §17.18.59 format names: `lowerRoman` 4 is "iv".
36
+ * A format with no numeral for the number — a letter or a roman zero — gives
37
+ * the empty string.
38
+ *
39
+ * @param format The number format.
40
+ * @param n The counter's value.
41
+ * @returns The counter as that format writes it.
42
+ */
43
+ export declare function formatCounter(format: NumberingFormat, n: number): string;
@@ -77,6 +77,15 @@ function formatLevelMarker(abstractNum, currentLevel, counters) {
77
77
  return formatCounter(currentLevel.isLegal === true && lvlIdx !== currentLevel.ilvl ? "decimal" : level?.format ?? "decimal", counter);
78
78
  });
79
79
  }
80
+ /**
81
+ * A counter in the numerals a §17.18.59 format names: `lowerRoman` 4 is "iv".
82
+ * A format with no numeral for the number — a letter or a roman zero — gives
83
+ * the empty string.
84
+ *
85
+ * @param format The number format.
86
+ * @param n The counter's value.
87
+ * @returns The counter as that format writes it.
88
+ */
80
89
  function formatCounter(format, n) {
81
90
  if (n < 0) return "";
82
91
  switch (format) {
@@ -336,4 +345,4 @@ function normalizeBullet(lvlText) {
336
345
  return lvlText;
337
346
  }
338
347
  //#endregion
339
- export { NumberingState, effectiveAbstract, formatLevelMarker };
348
+ export { NumberingState, effectiveAbstract, formatCounter, formatLevelMarker };
@@ -137,7 +137,10 @@ function mergeRun(base, override) {
137
137
  ...lang !== void 0 ? { lang } : {},
138
138
  ...(override.shadingColorHex ?? base.shadingColorHex) !== void 0 ? { shadingColorHex: override.shadingColorHex ?? base.shadingColorHex } : {},
139
139
  ...(override.textOutline ?? base.textOutline) !== void 0 ? { textOutline: override.textOutline ?? base.textOutline } : {},
140
- ...(override.letterSpacingPt ?? base.letterSpacingPt) !== void 0 ? { letterSpacingPt: override.letterSpacingPt ?? base.letterSpacingPt } : {}
140
+ ...(override.letterSpacingPt ?? base.letterSpacingPt) !== void 0 ? { letterSpacingPt: override.letterSpacingPt ?? base.letterSpacingPt } : {},
141
+ ...(override.widthScale ?? base.widthScale) !== void 0 ? { widthScale: override.widthScale ?? base.widthScale } : {},
142
+ ...(override.kerningMinPt ?? base.kerningMinPt) !== void 0 ? { kerningMinPt: override.kerningMinPt ?? base.kerningMinPt } : {},
143
+ ...(override.ligatures ?? base.ligatures) !== void 0 ? { ligatures: override.ligatures ?? base.ligatures } : {}
141
144
  };
142
145
  }
143
146
  function mergePar(base, override) {
@@ -152,6 +155,7 @@ function mergePar(base, override) {
152
155
  const sectionBreak = override.sectionBreak ?? base.sectionBreak;
153
156
  const textDirection = override.textDirection ?? base.textDirection;
154
157
  const snapToGrid = override.snapToGrid ?? base.snapToGrid;
158
+ const beforeAuto = override.spacingBeforeAuto ?? (override.spacingBefore === void 0 ? base.spacingBeforeAuto : void 0);
155
159
  return {
156
160
  alignment: override.alignment ?? base.alignment,
157
161
  spacingBefore: override.spacingBeforeAuto ?? override.spacingBefore ?? base.spacingBefore,
@@ -162,6 +166,9 @@ function mergePar(base, override) {
162
166
  indentRight: override.indentRight ?? base.indentRight,
163
167
  indentFirstLine: override.indentFirstLine ?? base.indentFirstLine,
164
168
  pageBreakBefore: override.pageBreakBefore ?? base.pageBreakBefore,
169
+ keepNext: override.keepNext ?? base.keepNext,
170
+ keepLines: override.keepLines ?? base.keepLines,
171
+ widowControl: override.widowControl ?? base.widowControl,
165
172
  contextualSpacing: override.contextualSpacing ?? base.contextualSpacing,
166
173
  tabs: override.tabs ?? base.tabs,
167
174
  bidi: override.bidi ?? base.bidi,
@@ -174,7 +181,8 @@ function mergePar(base, override) {
174
181
  ...frame !== void 0 ? { frame } : {},
175
182
  ...sectionBreak !== void 0 ? { sectionBreak } : {},
176
183
  ...textDirection !== void 0 ? { textDirection } : {},
177
- ...snapToGrid !== void 0 ? { snapToGrid } : {}
184
+ ...snapToGrid !== void 0 ? { snapToGrid } : {},
185
+ ...beforeAuto !== void 0 ? { spacingBeforeAuto: beforeAuto } : {}
178
186
  };
179
187
  }
180
188
  function mergeRunPartial(base, override) {
@@ -222,6 +230,7 @@ function primeParagraphFixpoint(para) {
222
230
  }
223
231
  if (!bySheet.has(para)) bySheet.set(para, para);
224
232
  }
233
+ var markCascadeCache = /* @__PURE__ */ new WeakMap();
225
234
  /**
226
235
  * Resolve the style cascade across an entire body so the tree carries final
227
236
  * effective run/paragraph properties (FlowDoc transform, ir-design stage 6).
@@ -229,9 +238,25 @@ function primeParagraphFixpoint(para) {
229
238
  * resolving again over {@link EMPTY_STYLE_SHEET} is the identity.
230
239
  */
231
240
  function resolveBodyStyles(body, sheet) {
241
+ let bySheet = markCascadeCache.get(sheet);
242
+ if (!bySheet) {
243
+ bySheet = /* @__PURE__ */ new WeakMap();
244
+ markCascadeCache.set(sheet, bySheet);
245
+ }
246
+ const marks = bySheet;
247
+ const withResolvedMark = (pp) => {
248
+ const hit = marks.get(pp);
249
+ if (hit) return hit;
250
+ const resolved = {
251
+ ...resolveParagraphProperties(pp, sheet),
252
+ runProperties: resolveRunProperties(pp.runProperties ?? {}, pp, sheet)
253
+ };
254
+ marks.set(pp, resolved);
255
+ return resolved;
256
+ };
232
257
  const visitParagraph = (p) => {
233
258
  for (const r of p.runs) r.properties = resolveRunProperties(r.properties, p.properties, sheet);
234
- p.properties = resolveParagraphProperties(p.properties, sheet);
259
+ p.properties = withResolvedMark(p.properties);
235
260
  primeParagraphFixpoint(p.properties);
236
261
  for (const r of p.runs) primeResolvedFixpoint(r.properties, p.properties);
237
262
  };
@@ -244,7 +269,13 @@ function resolveBodyStyles(body, sheet) {
244
269
  for (const member of sh.children ?? []) shapeText(member.shape);
245
270
  };
246
271
  shapeText(el.shape);
247
- }
272
+ standsOn(el.shape);
273
+ } else if (el.kind === "image") standsOn(el.image);
274
+ else standsOn(el.chart);
275
+ };
276
+ const standsOn = (block) => {
277
+ if (block.float !== void 0) return;
278
+ block.paragraphProperties = withResolvedMark(block.paragraphProperties);
248
279
  };
249
280
  for (const el of body) visit(el);
250
281
  return body;
@@ -1,4 +1,4 @@
1
- import { Alignment, CellBorders, CellShading, FontFamilyMap, FrameProperties, NumberingReference, RunProperties, TabStop, TextOutline, UnderlineStyle, VerticalAlign } from '../document-model/index.js';
1
+ import { Alignment, CellBorders, CellShading, FontFamilyMap, FrameProperties, Ligatures, NumberingReference, RunProperties, TabStop, TextOutline, UnderlineStyle, VerticalAlign } from '../document-model/index.js';
2
2
  import { Pt } from '../ir/index.js';
3
3
  /**
4
4
  * A run's fully-resolved properties: every field required because the cascade
@@ -30,6 +30,12 @@ export interface ResolvedRunProperties {
30
30
  readonly shadingColorHex?: string;
31
31
  /** §17.3.2.35 — extra space between the run's characters, in points. */
32
32
  readonly letterSpacingPt?: Pt;
33
+ /** §17.3.2.43 — the share of its own width each character is set at. */
34
+ readonly widthScale?: number;
35
+ /** §17.3.2.19 — the run is kerned at this size and above (0: not at all). */
36
+ readonly kerningMinPt?: Pt;
37
+ /** [MS-DOCX] `w14:ligatures` — the face's ligatures the run is set with. */
38
+ readonly ligatures?: Ligatures;
33
39
  /** §21.1.2.3.9 — a line drawn round the glyphs themselves. */
34
40
  readonly textOutline?: TextOutline;
35
41
  }
@@ -40,6 +46,12 @@ export interface ResolvedRunProperties {
40
46
  export interface ResolvedParagraphProperties {
41
47
  readonly alignment: Alignment;
42
48
  readonly spacingBefore: Pt;
49
+ /**
50
+ * §17.3.1.3 `w:beforeAutospacing` — present, and equal to
51
+ * {@link spacingBefore}, where the space before is the automatic (HTML) one:
52
+ * Word does not give it to the document's first paragraph.
53
+ */
54
+ readonly spacingBeforeAuto?: Pt;
43
55
  readonly spacingAfter: Pt;
44
56
  readonly spacingLine: Pt;
45
57
  readonly spacingLineRule: 'auto' | 'exact' | 'atLeast';
@@ -47,6 +59,12 @@ export interface ResolvedParagraphProperties {
47
59
  readonly indentRight: Pt;
48
60
  readonly indentFirstLine: Pt;
49
61
  readonly pageBreakBefore: boolean;
62
+ /** §17.3.1.14 — on the same page as the start of the next paragraph. */
63
+ readonly keepNext: boolean;
64
+ /** §17.3.1.15 — every line on one page. */
65
+ readonly keepLines: boolean;
66
+ /** §17.3.1.44 — no first or last line left alone on a page. */
67
+ readonly widowControl: boolean;
50
68
  /** §17.3.1.9 — drop the space between this paragraph and a same-styled neighbour. */
51
69
  readonly contextualSpacing: boolean;
52
70
  /** §17.3.1.37 — the paragraph's tab stops, in ascending position order. */
@@ -25,6 +25,9 @@ var DEFAULT_RESOLVED_PARAGRAPH = {
25
25
  indentRight: twipsToPt(0),
26
26
  indentFirstLine: twipsToPt(0),
27
27
  pageBreakBefore: false,
28
+ keepNext: false,
29
+ keepLines: false,
30
+ widowControl: true,
28
31
  contextualSpacing: false,
29
32
  tabs: [],
30
33
  bidi: false
@@ -29,12 +29,12 @@ export { signPdf } from './pdf/index.js';
29
29
  export type { SignaturePlaceholder, SignerCredentials, SignatureOptions } from './pdf/index.js';
30
30
  export type { PdfEncryptOptions, PdfPermissions } from './pdf/index.js';
31
31
  export { FontRegistry, parseTtf, subsetTtf } from './core/font/index.js';
32
- export type { FontBytesByVariant, FontVariant, ParsedTtf } from './core/font/index.js';
32
+ export type { FontBytesByVariant, FontVariant, GlyphSeg, ParsedTtf } from './core/font/index.js';
33
33
  export { getHyphenator, createLanguageHyphenator, createHyphenator, splitPatternBundle, } from './core/hyphenation/index.js';
34
34
  export type { Hyphenator, HyphenatorOptions, SupportedLanguage } from './core/hyphenation/index.js';
35
35
  export type { Pt, ResourceId, Feature, KnownFeature, Loss, LossReport, LossSeverity, NativeBag, } from './core/ir/index.js';
36
36
  export { FEATURES, ResourceStore, ConversionLossError, formatLoss, pt, twipsToPt, halfPtToPt, eighthPtToPt, emuToPt, pxToPt, inchToPt, mmToPt, } from './core/ir/index.js';
37
- export type { FlowDoc } from './core/ir/flow.js';
37
+ export type { FaceFamily, FaceGlyph, FaceOutlines, FlowDoc } from './core/ir/flow.js';
38
38
  export type { DocumentReader, DocumentWriter, ReadOptions, ReadResult, WriteOptions, WriteResult, } from './core/ir/adapters.js';
39
39
  export { docxReader, readDocx } from './word/docx-reader.js';
40
40
  export { xlsxReader, readXlsx } from './excel/xlsx-reader.js';
@@ -198,12 +198,19 @@ export interface Line {
198
198
  readonly mathAscentPt?: number;
199
199
  readonly mathDescentPt?: number;
200
200
  /**
201
- * E-PARITY: metric-derived single-line height and descent (Pt), the max over
202
- * the line's text-token fonts under a non-default `layoutProfile`. Absent under
203
- * `'ream'`, where leading stays the flat 1.2×/0.2 model (byte-identical).
201
+ * §17.3.1.33 — the line's single height and descent (Pt) from the faces on
202
+ * it, pictures included, where the line model reads faces (a Word document,
203
+ * or a renderer-compat `layoutProfile`). Absent under the flat 1.2×/0.2
204
+ * model.
204
205
  */
205
206
  readonly metricHeightPt?: number;
206
207
  readonly metricDescentPt?: number;
208
+ /**
209
+ * The line of its TEXT alone (the paragraph mark's, on a line with none):
210
+ * what a spacing of more lines adds per line, where a picture on the line
211
+ * adds nothing.
212
+ */
213
+ readonly metricTextHeightPt?: number;
207
214
  }
208
215
  /** An image bound into a {@link LaidOutDocument}: its resource name plus the decoded/validated bytes. */
209
216
  /**