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
package/README.md CHANGED
@@ -27,10 +27,12 @@ Runtime dependencies are minimal: `fflate` (ZIP/Deflate) and `fast-xml-parser`.
27
27
  ## Usage
28
28
 
29
29
  Parse once into the format-neutral interlayer, convert to any target. The
30
- format (docx/xlsx) is sniffed from the bytes; no fonts to wire up — an open
30
+ format is sniffed from the bytes; no fonts to wire up — an open
31
31
  metric-compatible substitute font (Arimo for sans, Tinos for serif, Cousine for
32
32
  monospace, plus Carlito/Caladea for Calibri/Cambria — the same families LibreOffice
33
- substitutes) is fetched automatically based on the document's referenced fonts:
33
+ substitutes) is fetched automatically based on the document's referenced fonts,
34
+ with a Noto face for the Japanese, Korean, Chinese, Arabic, Hebrew or Thai text
35
+ the document holds:
34
36
 
35
37
  ```ts
36
38
  import { Ream } from 'reamkit';
@@ -38,7 +40,7 @@ import { Ream } from 'reamkit';
38
40
  // e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
39
41
  const bytes = new Uint8Array(await file.arrayBuffer());
40
42
 
41
- const doc = Ream.parse(bytes); // docx, xlsx, pptx or pdf — sniffed
43
+ const doc = Ream.parse(bytes); // docx, xlsx, pptx, pdf, doc, xls or ppt — sniffed
42
44
  const pdf = await doc.convert('pdf'); // async — fetches a font if needed
43
45
  const svg = await doc.convert('svg'); // same parse, different target
44
46
  const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
@@ -57,6 +59,13 @@ and `doc.convertWithReport(...)` returns `{ bytes, losses }` (pass
57
59
  are plain `Uint8Array`s, so wiring this to files, the network, or disk is up
58
60
  to you.
59
61
 
62
+ An encrypted source opens with its password — a PDF's user password, or an
63
+ Office package's (MS-OFFCRYPTO, Agile and Standard):
64
+
65
+ ```ts
66
+ const doc = Ream.parse(bytes, { password: 'secret' });
67
+ ```
68
+
60
69
  ### Bring your own fonts (no network)
61
70
 
62
71
  To embed specific fonts — or to avoid the network entirely — pass the font
@@ -139,7 +148,7 @@ rendering — inspect or analyze it without converting:
139
148
 
140
149
  ```ts
141
150
  const doc = Ream.parse(bytes);
142
- doc.format; // 'docx' | 'xlsx' | 'pptx' | 'pdf'
151
+ doc.format; // 'docx' | 'xlsx' | 'pptx' | 'pdf' | 'doc' | 'xls' | 'ppt'
143
152
  doc.flow.body; // paragraphs / tables / images / charts …
144
153
  doc.losses; // read-time losses
145
154
  ```
@@ -157,7 +166,9 @@ const pdf = await doc.convert('pdf', { fonts, hyphenator });
157
166
  `convert` accepts (beyond the above): `info` (PDF `/Info` metadata — also read
158
167
  automatically from the document's `docProps/core.xml`), `attachments`
159
168
  (PDF/A-3 associated files), `tagged` (logical structure without full PDF/A),
160
- `pageWidth`/`pageHeight`/margins overrides.
169
+ `encrypt` (AES-256 with a user and an owner password and the permissions a
170
+ reader keeps: printing, copying, modifying, …), `pageWidth`/`pageHeight`/margins
171
+ overrides.
161
172
 
162
173
  ### Lower-level APIs
163
174
 
@@ -166,6 +177,9 @@ automatically from the document's `docProps/core.xml`), `attachments`
166
177
  keeping unused formats out of your bundle); `layoutStyledDocument` produces the
167
178
  frozen page model (`PageItem` pages in a top-left `Pt` frame) the page-based
168
179
  writers consume (`docxWriter` works from the flow model, before layout).
180
+ - `createConverter({ readers })` — a converter over a reader registry of your
181
+ own: `detect` names the reader that recognises the bytes, and `convert` reads
182
+ and converts them in one call, returning `{ bytes, losses }`.
169
183
  - `renderStyledPdf` drives the layout engine directly; the typed document
170
184
  model is on the `reamkit/document-model` subpath.
171
185
 
@@ -187,27 +201,49 @@ encryption, digital signatures (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page
187
201
  preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
188
202
  / SpreadsheetML back out, incl. round-trips). Reads OOXML Transitional and Strict.
189
203
 
204
+ A Word document is laid out by Word's own rules, each measured in Word: a line
205
+ as tall as Word sets its faces, two paragraphs standing the larger of their
206
+ spacings apart rather than the sum, widow and orphan control, a heading kept with
207
+ what it heads, table borders that take the room they are wide, a Word 2010
208
+ table placed by its first cell's text, and sections whose lines run down the
209
+ sheet.
210
+
190
211
  **Reads PDF, too.** `Ream.parse` accepts a PDF and reconstructs a `FlowDoc` — a
191
212
  tagged PDF from its structure tree (headings, tables, lists, reading order), an
192
- untagged one heuristically from glyph positions (lines, paragraphs, headings,
193
- and a clean two-column split). It lifts back the text (via each font's
194
- `/ToUnicode`, or the embedded program's own `cmap` where there is none, or the
195
- glyph names its `/Encoding` states — which is all a PDF from TeX gives), the
196
- font programs themselves, raster images (JPEG verbatim; PNG/Flate/LZW/CCITT-fax
197
- and **JBIG2** decoded and re-encoded), `/Link` hyperlinks, form-XObject content,
198
- annotation appearances (drawing one itself where the file supplies none),
199
- colour set through a named space — device, CIE (`CalGray`, `Lab`) or a
200
- `Separation`/`DeviceN` run through its own tint transform — and the page's
201
- artwork: filled / stroked / gradient shapes, clipping paths, tiling patterns,
202
- stencil image masks, constant alpha, and the Type 3 glyphs that are drawings
203
- rather than letters. It honours the layers a file turns off (§8.11 optional
204
- content) and the box it says to show (`/CropBox`), and it decides for itself
205
- whether a file is a document to re-flow or a page to keep — a paper is mostly
206
- lines, a form is mostly marks — and there is nothing to configure: a caller
207
- cannot know which of the two it was handed. It reads modern compressed files (cross-reference
208
- + object streams) and encrypted ones (RC4 / AES — the user password is passed to
209
- `Ream.parse(bytes, { password })`, defaulting to the permissions-only case); a
210
- filter it does not carry can be handed to it through
213
+ untagged one from where its glyphs stand: lines and paragraphs at the page's own
214
+ pitch, with their indents and alignment; headings and list items; columns of
215
+ any number, breaking and balancing where the page does; tables ruled or set out
216
+ on stops, within a column or across them; code listings; figures, a drawing and
217
+ the labels set on it kept as one group; the hanging entries of an index; and a
218
+ heading's bar as its shading. Running heads and feet become headers and footers,
219
+ their numbers PAGE fields counted in the page's own numerals — a book's roman
220
+ front matter, and a foot of its own for each side of its spreads.
221
+
222
+ It lifts back the text (via each font's `/ToUnicode`, or the embedded program's
223
+ own `cmap` where there is none, or the glyph names its `/Encoding` states — which
224
+ is all a PDF from TeX gives, its Greek and mathematics included) and the font
225
+ programs themselves: written to `.docx`, each face the page drew with travels
226
+ with the document, rebuilt as TrueType with the page's own advances, kerning and
227
+ ligatures, and embedded where its licence (OS/2 `fsType`) allows — so a line
228
+ breaks where the page broke it, on a machine that has none of its fonts. Raster
229
+ images come back (JPEG verbatim, a CMYK one re-coloured; PNG/Flate/LZW/CCITT-fax
230
+ and **JBIG2** decoded and re-encoded), with `/Link` hyperlinks, form-XObject
231
+ content, annotation and form appearances (drawing one itself where the file
232
+ supplies none), colour set through a named space — device, CIE (`CalGray`,
233
+ `CalRGB`, `Lab`), ICC-based, or a `Separation`/`DeviceN` run through its own
234
+ tint transform — and the page's artwork: filled / stroked / gradient shapes,
235
+ dashes and caps, clipping paths, tiling patterns, stencil image masks, opacity,
236
+ and the Type 3 glyphs that are drawings rather than letters.
237
+
238
+ It honours the layers a file turns off (§8.11 optional content), the box it says
239
+ to show (`/CropBox`) and the way it turns a page (`/Rotate` — a page whose words
240
+ run down its sheet comes back as a section set that way), and it decides for
241
+ itself whether a file is a document to re-flow or a page to keep — a paper is
242
+ mostly lines, a form is mostly marks — and there is nothing to configure: a
243
+ caller cannot know which of the two it was handed. It reads modern compressed
244
+ files (cross-reference + object streams) and encrypted ones (RC4 / AES — the
245
+ user password is passed to `Ream.parse(bytes, { password })`, defaulting to the
246
+ permissions-only case); a filter it does not carry can be handed to it through
211
247
  `Ream.parse(bytes, { filters })`.
212
248
 
213
249
  A form or a drawing is not a reflowable document, so the reader keeps it as a
@@ -28,7 +28,9 @@ function flowRenderOptions(flow) {
28
28
  ...flow.doNotExpandShiftReturn ? { doNotExpandShiftReturn: true } : {},
29
29
  ...flow.pageBackgroundColorHex ? { pageBackgroundColorHex: flow.pageBackgroundColorHex } : {},
30
30
  ...flow.pageBackgroundFill ? { pageBackgroundFill: flow.pageBackgroundFill } : {},
31
- ...flow.gutterAtTop ? { gutterAtTop: true } : {}
31
+ ...flow.gutterAtTop ? { gutterAtTop: true } : {},
32
+ ...flow.typesetBy ? { typesetBy: flow.typesetBy } : {},
33
+ ...flow.compatibilityMode !== void 0 ? { compatibilityMode: flow.compatibilityMode } : {}
32
34
  };
33
35
  }
34
36
  //#endregion
@@ -1,5 +1,5 @@
1
- import { openCfb } from "../ole/cfb.js";
2
1
  import { aesCbcDecrypt, aesEcbDecrypt, sha1, sha256, sha384, sha512 } from "./primitives.js";
2
+ import { openCfb } from "../ole/cfb.js";
3
3
  //#region src/core/crypto/offcrypto.ts
4
4
  /** Thrown when the bytes are encrypted and the password does not open them. */
5
5
  var WrongPasswordError = class extends Error {
@@ -5,4 +5,4 @@
5
5
  *
6
6
  * @packageDocumentation
7
7
  */
8
- export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, Comment, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, CellDataBar, CellIcon, CellIconShape, CellSparkline, Chart, ChartBlock, ChartDataPoint, ChartLineStyle, ChartMarker, ChartMarkerSymbol, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, ImageCrop, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, PictureBullet, PictureOutline, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, FrameProperties, RelativeSize, TabStop, Run, RunProperties, TextOutline, RowConditionalFormat, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeShadow, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeGroupChild, ShapeLine, LineEnd, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
8
+ export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, Comment, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, CellDataBar, CellIcon, CellIconShape, CellSparkline, Chart, ChartBlock, ChartDataPoint, ChartLineStyle, ChartMarker, ChartMarkerSymbol, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, ImageCrop, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, PictureBullet, PictureOutline, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, FrameProperties, RelativeSize, TabStop, Run, RunProperties, TextOutline, Ligatures, RowConditionalFormat, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeShadow, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeGroupChild, ShapeLine, LineEnd, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
@@ -6,6 +6,12 @@ export type Alignment = 'left' | 'right' | 'center' | 'both' | 'distribute';
6
6
  export type UnderlineStyle = 'none' | 'single' | 'double' | 'thick' | 'dotted' | 'dottedHeavy' | 'dash' | 'dashHeavy' | 'wave';
7
7
  /** Run vertical alignment (`w:vertAlign`): baseline or super/subscript. */
8
8
  export type VerticalAlign = 'baseline' | 'superscript' | 'subscript';
9
+ /**
10
+ * [MS-DOCX] ST_Ligatures — which of a face's OpenType ligatures a run is set
11
+ * with (`w14:ligatures`): the standard ones (`liga`), the contextual (`clig`),
12
+ * the historical (`hlig`) and the discretionary (`dlig`), alone or together.
13
+ */
14
+ export type Ligatures = 'none' | 'standard' | 'contextual' | 'historical' | 'discretional' | 'standardContextual' | 'standardHistorical' | 'contextualHistorical' | 'standardDiscretional' | 'contextualDiscretional' | 'historicalDiscretional' | 'standardContextualHistorical' | 'standardContextualDiscretional' | 'standardHistoricalDiscretional' | 'contextualHistoricalDiscretional' | 'all';
9
15
  /**
10
16
  * The four script slots of `w:rFonts` (§17.3.2.26). A character picks its font
11
17
  * from the slot its Unicode range maps to (ASCII, high-ANSI, complex-script,
@@ -63,6 +69,21 @@ export interface RunProperties {
63
69
  * (negative tightens). Word states it in twentieths of a point.
64
70
  */
65
71
  readonly letterSpacingPt?: Pt;
72
+ /**
73
+ * §17.3.2.43 `w:w` — each character set at this share of its own width
74
+ * (1 is as the face sets it), the glyph itself narrowed or widened.
75
+ */
76
+ readonly widthScale?: number;
77
+ /**
78
+ * §17.3.2.19 `w:kern` — the run's glyph pairs are KERNED, at this size and
79
+ * above (0 turns kerning off). Word kerns nothing a run does not ask for.
80
+ */
81
+ readonly kerningMinPt?: Pt;
82
+ /**
83
+ * [MS-DOCX] `w14:ligatures` — the face's ligatures the run is set with.
84
+ * Word forms none at all in a document it opens in compatibility mode.
85
+ */
86
+ readonly ligatures?: Ligatures;
66
87
  /**
67
88
  * §21.1.2.3.9 `a:rPr/a:ln` — a line drawn round the glyphs themselves, which
68
89
  * DrawingML puts on a run and ISO 32000-1 §9.3.6 calls a text rendering mode
@@ -220,6 +241,24 @@ export interface ParagraphProperties {
220
241
  * starts on a fresh page even if there is room on the current one.
221
242
  */
222
243
  readonly pageBreakBefore?: boolean;
244
+ /**
245
+ * ECMA-376 Part 1 §17.3.1.14 — `w:keepNext`. The paragraph stands on the
246
+ * same page as the start of the one after it: a heading is not left at the
247
+ * foot of a page with its text on the next.
248
+ */
249
+ readonly keepNext?: boolean;
250
+ /**
251
+ * ECMA-376 Part 1 §17.3.1.15 — `w:keepLines`. The paragraph's lines stand on
252
+ * one page, wherever one page holds them all.
253
+ */
254
+ readonly keepLines?: boolean;
255
+ /**
256
+ * ECMA-376 Part 1 §17.3.1.44 — `w:widowControl`. When on, a page or column
257
+ * break that would leave the paragraph's first line alone at the foot of one
258
+ * page, or its last line alone at the head of the next, is moved so that
259
+ * neither stands alone. Word applies it wherever nothing says otherwise.
260
+ */
261
+ readonly widowControl?: boolean;
223
262
  /**
224
263
  * ECMA-376 §17.3.1.6 — `w:bidi`. Sets the paragraph's base direction to RTL,
225
264
  * so the BiDi paragraph embedding level is 1 and default alignment is right.
@@ -1127,6 +1166,8 @@ export interface ShapeLine {
1127
1166
  readonly customDash?: ReadonlyArray<number>;
1128
1167
  readonly cap?: 'flat' | 'round' | 'square';
1129
1168
  readonly fill?: 'solid' | 'none';
1169
+ /** §20.1.2.3.1 `a:alpha` on the line's colour — how opaque the stroke is, `0..1`. */
1170
+ readonly alpha?: number;
1130
1171
  /** §20.1.8.24 `a:headEnd` — the decoration at the line's first point. */
1131
1172
  readonly headEnd?: LineEnd;
1132
1173
  /** §20.1.8.42 `a:tailEnd` — the decoration at the line's last point. */
@@ -1174,6 +1215,13 @@ export interface ShapeTextBody {
1174
1215
  readonly insetRight?: Pt;
1175
1216
  readonly insetBottom?: Pt;
1176
1217
  readonly anchor?: 't' | 'ctr' | 'b';
1218
+ /**
1219
+ * §20.1.2.1.1 `a:bodyPr @wrap="none"` — the lines run as far as their words
1220
+ * do, whatever the box's width. A label placed where a page set it is sized
1221
+ * to the words it was measured from, and set in a face a little wider it
1222
+ * broke inside its one word.
1223
+ */
1224
+ readonly noWrap?: boolean;
1177
1225
  /**
1178
1226
  * §20.1.10.83 ST_TextVerticalType (`a:bodyPr @vert`) — text set along the
1179
1227
  * box's long axis rather than across it. `vert` reads top-to-bottom (turned a
@@ -1541,6 +1589,12 @@ export interface SectionProperties {
1541
1589
  * printed with. Absent ⇒ the count carries on from the section before.
1542
1590
  */
1543
1591
  readonly pageNumberStart?: number;
1592
+ /**
1593
+ * §17.6.12 `w:pgNumType w:fmt` — how a PAGE field prints the section's page
1594
+ * numbers (§17.18.59): `lowerRoman` numbers a book's front matter i, ii,
1595
+ * iii before its body starts again at 1. Absent ⇒ decimal.
1596
+ */
1597
+ readonly pageNumberFormat?: NumberingFormat;
1544
1598
  /** §17.6.8 `w:lnNumType` — line numbers printed in the margin beside the text. */
1545
1599
  readonly lineNumbering?: {
1546
1600
  /** Print every `countBy`-th line (default 1). */
@@ -1577,6 +1631,17 @@ export interface SectionProperties {
1577
1631
  * distinguished from `nextPage`.)
1578
1632
  */
1579
1633
  readonly sectionStart?: 'continuous' | 'nextPage' | 'oddPage' | 'evenPage';
1634
+ /**
1635
+ * §17.6.20 `w:textDirection` — which way the section's lines run. `tbRl`
1636
+ * sets each line top to bottom and the next one to the left of it: the
1637
+ * text turned a quarter clockwise on the sheet, which is how a viewer shows
1638
+ * a page turned by `/Rotate 90` whose words stood upright in its box. Word
1639
+ * lays a section out that way for `btLr` as well, so the layout reads both
1640
+ * alike. Only the body's text turns: the headers, the footers and every
1641
+ * drawing anchored to the page keep the sheet's own axes. Absent ⇒ left to
1642
+ * right, top to bottom.
1643
+ */
1644
+ readonly textDirection?: 'tbRl' | 'btLr';
1580
1645
  /**
1581
1646
  * §17.6.5 `w:docGrid` — the line grid a `lines`/`linesAndChars` section rules
1582
1647
  * its text onto, as the pitch in points. Every line of the section's text is
@@ -192,13 +192,25 @@ function buildStroke(line) {
192
192
  const widthPt = line.width ?? DEFAULT_LINE_WIDTH_EMU / 12700;
193
193
  const dash = line.customDash?.map((n) => Math.max(.01, n * widthPt)) ?? (line.dash && line.dash !== "solid" ? dashPattern(line.dash, widthPt) : void 0);
194
194
  const cap = line.cap === "flat" ? "butt" : line.cap;
195
+ const colorHex = line.colorHex ?? "000000";
195
196
  return {
196
- colorHex: line.colorHex ?? "000000",
197
+ colorHex: line.alpha !== void 0 && line.alpha < 1 ? overWhite(colorHex, line.alpha) : colorHex,
197
198
  widthPt,
198
199
  ...dash ? { dash } : {},
199
200
  ...cap ? { cap } : {}
200
201
  };
201
202
  }
203
+ /** A 6-hex colour drawn at `alpha` over white, as the opaque colour it comes to. */
204
+ function overWhite(hex, alpha) {
205
+ const n = parseInt(hex, 16);
206
+ if (Number.isNaN(n)) return hex;
207
+ const a = Math.max(0, Math.min(1, alpha));
208
+ return [
209
+ 16,
210
+ 8,
211
+ 0
212
+ ].map((shift) => Math.round(255 - (255 - (n >> shift & 255)) * a).toString(16).padStart(2, "0")).join("").toUpperCase();
213
+ }
202
214
  function dashPattern(dash, w) {
203
215
  const u = Math.max(w, .1);
204
216
  switch (dash) {
@@ -9,3 +9,5 @@ export type { KerningMap, LigatureMap, ShapedRun } from './opentype-layout.js';
9
9
  export { hasSubstitutable, lettersForLigature, substituteLetters } from './ligatures.js';
10
10
  export { createFontMeasure } from './measure.js';
11
11
  export type { FontMeasure } from './measure.js';
12
+ export { buildTrueType, editableEmbedding, MAX_BUILT_GLYPHS } from './ttf-build.js';
13
+ export type { BuiltFace, BuiltGlyph, BuiltKernPair, BuiltLigature, GlyphSeg, } from './ttf-build.js';
@@ -0,0 +1,113 @@
1
+ /** One piece of a glyph's outline, in a one-unit em with y up. */
2
+ export type GlyphSeg = {
3
+ readonly op: 'move';
4
+ readonly x: number;
5
+ readonly y: number;
6
+ } | {
7
+ readonly op: 'line';
8
+ readonly x: number;
9
+ readonly y: number;
10
+ } | {
11
+ readonly op: 'cubic';
12
+ readonly x1: number;
13
+ readonly y1: number;
14
+ readonly x2: number;
15
+ readonly y2: number;
16
+ readonly x: number;
17
+ readonly y: number;
18
+ } | {
19
+ readonly op: 'close';
20
+ };
21
+ /** One glyph of a {@link BuiltFace}. */
22
+ export interface BuiltGlyph {
23
+ /** The characters that select it, as code points. */
24
+ readonly codePoints: ReadonlyArray<number>;
25
+ /** Its contours, filled by the nonzero rule; empty for a blank glyph. */
26
+ readonly outline: ReadonlyArray<GlyphSeg>;
27
+ /** How far the pen moves after it, in thousandths of an em. */
28
+ readonly advance: number;
29
+ }
30
+ /** Everything {@link buildTrueType} needs to know about the face it builds. */
31
+ export interface BuiltFace {
32
+ /** The family the font is installed under (`name` ID 1). */
33
+ readonly family: string;
34
+ /** The style within the family: its bold and italic slot (`name` ID 2). */
35
+ readonly bold: boolean;
36
+ readonly italic: boolean;
37
+ /** The PostScript name (`name` ID 6); only its printable ASCII is kept. */
38
+ readonly postScriptName: string;
39
+ readonly glyphs: ReadonlyArray<BuiltGlyph>;
40
+ /** Above and below the baseline, in thousandths of an em — `descent` negative. */
41
+ readonly ascent: number;
42
+ readonly descent: number;
43
+ readonly capHeight?: number;
44
+ readonly xHeight?: number;
45
+ /** Degrees counterclockwise from the vertical; a face slanted right is negative. */
46
+ readonly italicAngle?: number;
47
+ readonly fixedPitch?: boolean;
48
+ /**
49
+ * OS/2 `fsType` — the embedding the face's licence allows, carried over from
50
+ * the program the outlines came from.
51
+ */
52
+ readonly fsType: number;
53
+ /** The pairs the face is kerned by: how far the second glyph moves in towards the first. */
54
+ readonly kerning?: ReadonlyArray<BuiltKernPair>;
55
+ /** The glyphs the face draws for a run of characters at once: its ligatures. */
56
+ readonly ligatures?: ReadonlyArray<BuiltLigature>;
57
+ }
58
+ /**
59
+ * One ligature of a {@link BuiltFace}: a glyph no character reaches alone,
60
+ * substituted for the characters it joins wherever they meet (GSUB `liga`).
61
+ */
62
+ export interface BuiltLigature {
63
+ /** The characters it stands for, in order — two or more. */
64
+ readonly codePoints: ReadonlyArray<number>;
65
+ readonly outline: ReadonlyArray<GlyphSeg>;
66
+ /** How far the pen moves after it, in thousandths of an em. */
67
+ readonly advance: number;
68
+ }
69
+ /** One kerning pair of a {@link BuiltFace}, by the characters it joins. */
70
+ export interface BuiltKernPair {
71
+ /** The code point of the glyph on the left, and of the one after it. */
72
+ readonly left: number;
73
+ readonly right: number;
74
+ /** The adjustment to the left glyph's advance, in thousandths of an em — negative tightens. */
75
+ readonly value: number;
76
+ }
77
+ /**
78
+ * Whether a face's licence lets it be embedded, as outlines and subset, in a
79
+ * document that stays EDITABLE: OS/2 `fsType` states installable embedding (no
80
+ * usage bit) or grants editable embedding, and neither forbids subsetting nor
81
+ * allows bitmaps only. Restricted-licence and preview-and-print faces are
82
+ * refused — the second may travel only in a document opened read-only.
83
+ *
84
+ * Several usage bits at once is an old font's way of stating permissions, and
85
+ * the least restrictive of them applies (OS/2 versions 0–2).
86
+ *
87
+ * @param fsType The field, or `undefined` where the program states none —
88
+ * which restricts nothing.
89
+ * @returns Whether an editable document may carry the face.
90
+ */
91
+ export declare function editableEmbedding(fsType: number | undefined): boolean;
92
+ /**
93
+ * The most glyphs a built face holds. A `cmap` format 4 subtable is limited to
94
+ * 64 KB, and past this many characters it may not fit; a face that large is a
95
+ * whole CJK font, which is no subset.
96
+ */
97
+ export declare const MAX_BUILT_GLYPHS = 8000;
98
+ /**
99
+ * Build a TrueType font from outlines (ISO/IEC 14496-22): `.notdef` first, then
100
+ * one glyph per {@link BuiltGlyph} in the order given, reachable through a
101
+ * Unicode `cmap`.
102
+ *
103
+ * Cubic curves become quadratic ones. A cubic that is a raised quadratic — what
104
+ * a TrueType source's outline arrives as — comes back as exactly that
105
+ * quadratic; any other is cut into as many quadratics as keep within half a
106
+ * unit of it. Each glyph's contours are turned to run clockwise round their
107
+ * ink, as TrueType's are.
108
+ *
109
+ * @param face The face: its names, metrics, licence and glyphs.
110
+ * @returns The font program's bytes.
111
+ * @throws When the face holds more than {@link MAX_BUILT_GLYPHS} glyphs.
112
+ */
113
+ export declare function buildTrueType(face: BuiltFace): Uint8Array;