reamkit 1.30.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 (78) 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 +63 -0
  6. package/dist/esm/core/font/index.d.ts +2 -0
  7. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  8. package/dist/esm/core/font/ttf-build.js +1224 -0
  9. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  10. package/dist/esm/core/font/ttf-subset.js +14 -1
  11. package/dist/esm/core/fonts/provider.d.ts +17 -0
  12. package/dist/esm/core/fonts/provider.js +27 -2
  13. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  14. package/dist/esm/core/ir/flow.d.ts +71 -1
  15. package/dist/esm/core/numbering/index.d.ts +1 -1
  16. package/dist/esm/core/numbering/state.d.ts +11 -1
  17. package/dist/esm/core/numbering/state.js +10 -1
  18. package/dist/esm/core/style-cascade/resolver.js +35 -4
  19. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  20. package/dist/esm/core/style-cascade/types.js +3 -0
  21. package/dist/esm/index.d.ts +2 -2
  22. package/dist/esm/layout/page-doc.d.ts +10 -3
  23. package/dist/esm/layout/styled-layout.d.ts +48 -7
  24. package/dist/esm/layout/styled-layout.js +881 -119
  25. package/dist/esm/layout/turned-section.d.ts +38 -0
  26. package/dist/esm/layout/turned-section.js +193 -0
  27. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  28. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  29. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  30. package/dist/esm/pdf-reader/content.d.ts +53 -0
  31. package/dist/esm/pdf-reader/content.js +9 -1
  32. package/dist/esm/pdf-reader/display.d.ts +36 -0
  33. package/dist/esm/pdf-reader/display.js +66 -1
  34. package/dist/esm/pdf-reader/document.js +5 -1
  35. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  36. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  37. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  38. package/dist/esm/pdf-reader/encodings.js +25 -3
  39. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  40. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  41. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  42. package/dist/esm/pdf-reader/figures.js +433 -0
  43. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  44. package/dist/esm/pdf-reader/flow-build.js +345 -26
  45. package/dist/esm/pdf-reader/font.js +321 -36
  46. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  47. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  48. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  49. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  50. package/dist/esm/pdf-reader/layout.js +1703 -214
  51. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  52. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  53. package/dist/esm/pdf-reader/tagged.js +182 -23
  54. package/dist/esm/pdf-reader/text.d.ts +6 -3
  55. package/dist/esm/pdf-reader/text.js +98 -9
  56. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  57. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  58. package/dist/esm/pdf-reader/vector.js +71 -1
  59. package/dist/esm/word/doc/doc-reader.js +6 -2
  60. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  61. package/dist/esm/word/doc/doc-text.js +19 -1
  62. package/dist/esm/word/document-parser.d.ts +2 -2
  63. package/dist/esm/word/document-parser.js +11 -1
  64. package/dist/esm/word/docx-reader.js +5 -3
  65. package/dist/esm/word/docx-writer.js +207 -21
  66. package/dist/esm/word/drawing-parser.d.ts +5 -3
  67. package/dist/esm/word/drawing-parser.js +49 -8
  68. package/dist/esm/word/font-embed.d.ts +30 -0
  69. package/dist/esm/word/font-embed.js +173 -0
  70. package/dist/esm/word/font-table.d.ts +10 -0
  71. package/dist/esm/word/font-table.js +13 -1
  72. package/dist/esm/word/index.js +1 -1
  73. package/dist/esm/word/numbering-parser.d.ts +3 -1
  74. package/dist/esm/word/numbering-parser.js +2 -1
  75. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  76. package/dist/esm/word/paragraph-properties.js +14 -2
  77. package/dist/esm/word/run-properties.js +26 -0
  78. package/package.json +8 -3
@@ -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 };
@@ -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
  };
@@ -330,7 +330,14 @@ async function fetchTtf(url, fetchImpl, required) {
330
330
  })();
331
331
  cache.set(url, pending);
332
332
  }
333
- 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
+ }
334
341
  if (!result && required) {
335
342
  cache.delete(url);
336
343
  throw new Error(`Failed to download font from ${url}`);
@@ -1,5 +1,5 @@
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
4
  /**
5
5
  * A face's family as a word processor names it (ECMA-376 §17.8.3.9 `w:font`).
@@ -10,6 +10,55 @@ export interface FaceFamily {
10
10
  /** §17.8.3.10 `w:family` — the kind of face, for a reader that has to substitute. */
11
11
  readonly generic: 'roman' | 'swiss' | 'modern';
12
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
+ }
13
62
  /**
14
63
  * The semantic IR tree (ir-design §5): everything a reader extracts from the
15
64
  * document bytes, format-neutrally — the flow `body` plus its document-scoped
@@ -59,6 +108,11 @@ export interface FlowDoc {
59
108
  * to another program names the family, which is the name that program knows.
60
109
  */
61
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>;
62
116
  /** Document metadata from docProps/core.xml. */
63
117
  readonly info?: DocumentInfo;
64
118
  /** Document natural language hint (BCP-47), e.g. for tagged-PDF /Lang. */
@@ -85,4 +139,20 @@ export interface FlowDoc {
85
139
  * @w:gutter` reserves belongs to the TOP margin rather than the left.
86
140
  */
87
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';
88
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 { FaceFamily, 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
  /**
@@ -7,6 +7,7 @@ import { LaidOutDocument, PageItem } from './page-doc.js';
7
7
  import { AttachedFile } from '../pdf/embedded-file.js';
8
8
  import { SignaturePlaceholder } from '../pdf/signature.js';
9
9
  import { PdfEncryptOptions } from '../pdf/encryption.js';
10
+ import { Sheet } from './turned-section.js';
10
11
  import { StructTreeBuilder } from '../pdf/struct-tree.js';
11
12
  /**
12
13
  * PDF/A conformance string: part 1 (ISO 19005-1, PDF 1.4) / 2 (ISO 19005-2) / 3
@@ -24,15 +25,27 @@ export interface PdfAProfile {
24
25
  readonly version: '1.4' | '1.7';
25
26
  }
26
27
  /**
27
- * E-PARITY: renderer-compatibility profile for the line-height model.
28
- * `'ream'` (default) — Ream's flat 1.2× leading; byte-identical to before.
29
- * `'word'` — leading from the font's OS/2 usWin metrics (the GDI cell box).
30
- * `'libreoffice'` — leading from hhea (or OS/2 typo when USE_TYPO_METRICS is set).
28
+ * E-PARITY: renderer-compatibility profile — how lines are measured, broken
29
+ * and stacked.
30
+ * `'ream'` (default) — kerned measuring, Knuth-Plass breaking, and the line
31
+ * height the document asks for (see {@link TypesetBy}).
32
+ * `'word'` — kern-free measuring, first-fit breaking, and lines as Word sets
33
+ * them whatever the document.
34
+ * `'libreoffice'` — first-fit breaking, lines from the face in hand: hhea, or
35
+ * the OS/2 typo line when USE_TYPO_METRICS is set.
31
36
  *
32
- * Opt-in: a profile emulates that renderer's vertical rhythm for closer visual
33
- * parity. It never changes default (`'ream'`) output.
37
+ * Opt-in: a profile emulates that renderer for closer visual parity.
34
38
  */
35
39
  export type LayoutProfile = 'ream' | 'word' | 'libreoffice';
40
+ /**
41
+ * The application whose rules a document is set by: `'word'` stands its
42
+ * lines as tall as the faces on them make them (§17.3.1.33, see
43
+ * `fontLeadingPt`) rather than Ream's flat 1.2× of the size, and gives every
44
+ * horizontal table border the room it is wide (§17.4.38, see
45
+ * `withBorderBands`) rather than none. A Word document's reader asks for it
46
+ * (see `FlowDoc.typesetBy`).
47
+ */
48
+ export type TypesetBy = 'word';
36
49
  /**
37
50
  * The full option set the layout engine and PDF emitter consume: the resolved
38
51
  * font registry, the style/numbering tables, the section model and page
@@ -46,6 +59,8 @@ export interface StyledRenderOptions {
46
59
  readonly registry: FontRegistry;
47
60
  /** Renderer-compatibility profile for the line-height model (default `'ream'`). */
48
61
  readonly layoutProfile?: LayoutProfile;
62
+ /** The application whose rules the document is set by (see {@link TypesetBy}). */
63
+ readonly typesetBy?: TypesetBy;
49
64
  /**
50
65
  * Per-run font resolution: when supplied, each text run picks the registry of
51
66
  * its declared family (sans→arimo / serif→tinos / mono→cousine via the run's
@@ -173,6 +188,12 @@ export interface StyledRenderOptions {
173
188
  * TOP margin, not the left.
174
189
  */
175
190
  readonly gutterAtTop?: boolean;
191
+ /**
192
+ * [MS-DOCX] `compatibilityMode` — the version of Word whose layout the
193
+ * document asks for; a Word document that states none is an older Word's
194
+ * (see {@link legacyTableOutdent}).
195
+ */
196
+ readonly compatibilityMode?: number;
176
197
  /**
177
198
  * §7.6 PDF encryption (AES-256, R6). Only honoured on the ASYNC conversion
178
199
  * path (WebCrypto); mutually exclusive with `pdfA` (ISO 19005 forbids
@@ -302,6 +323,12 @@ export interface SectionRenderCtx {
302
323
  }>;
303
324
  readonly headerSet: HeaderFooterSet;
304
325
  readonly footerSet: HeaderFooterSet;
326
+ /**
327
+ * §17.10 — where the body starts and ends on a page that shows each band:
328
+ * below the header band and above the footer band that page carries, each
329
+ * as tall as it is. Absent ⇒ every page takes `marginTop`/`marginBottom`.
330
+ */
331
+ readonly bandMargins?: Readonly<Record<HfBand, BodyMargins>>;
305
332
  readonly titlePg: boolean;
306
333
  readonly evenAndOddHeaders: boolean;
307
334
  /**
@@ -316,10 +343,24 @@ export interface SectionRenderCtx {
316
343
  * paragraph in it. Absent on a bare context built outside a layout run.
317
344
  */
318
345
  readonly options?: StyledRenderOptions;
346
+ /**
347
+ * §17.6.20 — the sheet a section whose lines run DOWN it prints on. Such a
348
+ * section's body is laid out in the frame its text reads in, which the
349
+ * geometry above describes — the sheet turned back a quarter — and turned
350
+ * onto this sheet page by page, while its header and footer are laid out
351
+ * on the sheet itself (see ./turned-section). Absent ⇒ the frame is the sheet.
352
+ */
353
+ readonly sheet?: Sheet;
354
+ }
355
+ type HfBand = 'default' | 'first' | 'even';
356
+ /** The distances from the paper's top and bottom edges to the body's. */
357
+ interface BodyMargins {
358
+ readonly top: number;
359
+ readonly bottom: number;
319
360
  }
320
361
  interface HfBandEntry {
321
362
  readonly commands: Array<PageItem>;
322
- readonly renderDynamic?: (pageNumber: number, totalPages: number) => Array<PageItem>;
363
+ readonly renderDynamic?: (pageText: string, totalPages: number) => Array<PageItem>;
323
364
  /** Laid-out height of the band, so the body can be kept clear of it. */
324
365
  readonly heightPt?: number;
325
366
  }