reamkit 0.1.0-alpha.0 → 1.0.1

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 (200) hide show
  1. package/README.md +107 -30
  2. package/dist/esm/{bidi → core/bidi}/algorithm.js +1 -1
  3. package/dist/esm/{bidi → core/bidi}/char-types.js +1 -1
  4. package/dist/esm/{bidi → core/bidi}/index.d.ts +2 -0
  5. package/dist/esm/{bidi → core/bidi}/index.js +2 -1
  6. package/dist/esm/core/bidi/segments.d.ts +6 -0
  7. package/dist/esm/core/bidi/segments.js +41 -0
  8. package/dist/esm/core/bytes.d.ts +1 -0
  9. package/dist/esm/core/bytes.js +11 -0
  10. package/dist/esm/core/converter/facade.d.ts +45 -0
  11. package/dist/esm/core/converter/facade.js +89 -0
  12. package/dist/esm/core/converter/index.d.ts +4 -0
  13. package/dist/esm/core/converter/project.d.ts +4 -0
  14. package/dist/esm/core/converter/project.js +17 -0
  15. package/dist/esm/core/converter/ream.d.ts +35 -0
  16. package/dist/esm/core/converter/ream.js +101 -0
  17. package/dist/esm/{crypto → core/crypto}/asn1.js +1 -1
  18. package/dist/esm/{crypto → core/crypto}/cms.js +1 -1
  19. package/dist/esm/core/document-model/index.d.ts +1 -0
  20. package/dist/esm/{document-model → core/document-model}/types.d.ts +57 -41
  21. package/dist/esm/{ooxml → core}/drawingml/chart-parser.d.ts +1 -1
  22. package/dist/esm/{ooxml → core}/drawingml/chart-parser.js +6 -29
  23. package/dist/esm/{ooxml → core}/drawingml/colors.d.ts +3 -0
  24. package/dist/esm/{ooxml → core}/drawingml/colors.js +32 -2
  25. package/dist/esm/{ooxml → core}/drawingml/theme-parser.js +2 -2
  26. package/dist/esm/{font → core/font}/arabic-joining.js +1 -1
  27. package/dist/esm/{font → core/font}/binary-reader.js +1 -1
  28. package/dist/esm/{font → core/font}/font-registry.d.ts +1 -0
  29. package/dist/esm/{font → core/font}/font-registry.js +17 -16
  30. package/dist/esm/{font → core/font}/index.d.ts +3 -0
  31. package/dist/esm/core/font/measure.d.ts +7 -0
  32. package/dist/esm/core/font/measure.js +30 -0
  33. package/dist/esm/{font → core/font}/opentype-layout.js +1 -1
  34. package/dist/esm/{font → core/font}/ttf-parser.js +1 -1
  35. package/dist/esm/{font → core/font}/ttf-subset.js +1 -1
  36. package/dist/esm/core/fonts/provider.d.ts +60 -0
  37. package/dist/esm/core/fonts/provider.js +163 -0
  38. package/dist/esm/{fonts → core/fonts}/remote-fonts.js +1 -1
  39. package/dist/esm/{hyphenation → core/hyphenation}/index.js +1 -1
  40. package/dist/esm/{hyphenation → core/hyphenation}/liang.js +1 -1
  41. package/dist/esm/{hyphenation → core/hyphenation}/patterns-en-us.js +1 -1
  42. package/dist/esm/{hyphenation → core/hyphenation}/patterns-ru.js +1 -1
  43. package/dist/esm/core/images.d.ts +17 -0
  44. package/dist/esm/core/images.js +308 -0
  45. package/dist/esm/core/ir/adapters.d.ts +37 -0
  46. package/dist/esm/core/ir/features.d.ts +25 -0
  47. package/dist/esm/core/ir/features.js +24 -0
  48. package/dist/esm/core/ir/flow.d.ts +24 -0
  49. package/dist/esm/core/ir/index.d.ts +11 -0
  50. package/dist/esm/core/ir/loss.d.ts +22 -0
  51. package/dist/esm/core/ir/loss.js +15 -0
  52. package/dist/esm/core/ir/resources.d.ts +16 -0
  53. package/dist/esm/core/ir/resources.js +50 -0
  54. package/dist/esm/core/ir/units.d.ts +16 -0
  55. package/dist/esm/core/ir/units.js +41 -0
  56. package/dist/esm/core/line-breaker/index.d.ts +2 -0
  57. package/dist/esm/{line-breaker → core/line-breaker}/knuth-plass.d.ts +1 -0
  58. package/dist/esm/{line-breaker → core/line-breaker}/knuth-plass.js +3 -3
  59. package/dist/esm/core/numbering/apply.d.ts +3 -0
  60. package/dist/esm/core/numbering/apply.js +62 -0
  61. package/dist/esm/core/numbering/index.d.ts +2 -0
  62. package/dist/esm/{numbering → core/numbering}/state.js +1 -1
  63. package/dist/esm/{opc → core/opc}/core-properties.js +1 -1
  64. package/dist/esm/{opc → core/opc}/package.d.ts +2 -1
  65. package/dist/esm/{opc → core/opc}/package.js +7 -3
  66. package/dist/esm/{opc → core/opc}/relationship-types.js +1 -1
  67. package/dist/esm/{opc → core/opc}/relationships.js +1 -1
  68. package/dist/esm/{ooxml/wordproc → core}/po-helpers.js +1 -1
  69. package/dist/esm/core/style-cascade/index.d.ts +3 -0
  70. package/dist/esm/core/style-cascade/index.js +2 -0
  71. package/dist/esm/core/style-cascade/resolver.d.ts +7 -0
  72. package/dist/esm/{style-cascade → core/style-cascade}/resolver.js +82 -10
  73. package/dist/esm/{style-cascade → core/style-cascade}/types.d.ts +10 -8
  74. package/dist/esm/{style-cascade → core/style-cascade}/types.js +9 -8
  75. package/dist/esm/core/vector.d.ts +45 -0
  76. package/dist/esm/core/vector.js +48 -0
  77. package/dist/esm/{ooxml/spreadsheet → excel}/cell-reference.js +1 -1
  78. package/dist/esm/{ooxml/spreadsheet → excel}/defined-name-ref.js +1 -1
  79. package/dist/esm/{ooxml/spreadsheet → excel}/index.d.ts +1 -1
  80. package/dist/esm/{ooxml/spreadsheet → excel}/number-format.js +1 -1
  81. package/dist/esm/excel/print-model.d.ts +18 -0
  82. package/dist/esm/{converter/xlsx-to-pdf.js → excel/print-model.js} +39 -148
  83. package/dist/esm/{ooxml/spreadsheet → excel}/shared-strings-parser.js +1 -1
  84. package/dist/esm/{ooxml/spreadsheet → excel}/styles-parser.js +1 -1
  85. package/dist/esm/{ooxml/spreadsheet → excel}/workbook-parser.js +1 -1
  86. package/dist/esm/{ooxml/spreadsheet → excel}/worksheet-parser.js +1 -1
  87. package/dist/esm/excel/xlsx-reader.d.ts +4 -0
  88. package/dist/esm/excel/xlsx-reader.js +91 -0
  89. package/dist/esm/{converter → excel}/xlsx-to-pdf.d.ts +13 -2
  90. package/dist/esm/excel/xlsx-to-pdf.js +69 -0
  91. package/dist/esm/index.d.ts +23 -8
  92. package/dist/esm/index.js +18 -10
  93. package/dist/esm/{pdf → layout}/arc-to-bezier.d.ts +1 -1
  94. package/dist/esm/{pdf → layout}/arc-to-bezier.js +2 -2
  95. package/dist/esm/{pdf → layout}/chart-geometry.d.ts +1 -1
  96. package/dist/esm/{pdf → layout}/math-layout.d.ts +2 -2
  97. package/dist/esm/{pdf → layout}/math-layout.js +1 -1
  98. package/dist/esm/layout/page-doc.d.ts +133 -0
  99. package/dist/esm/layout/page-doc.js +38 -0
  100. package/dist/esm/{pdf → layout}/preset-geometry.d.ts +2 -2
  101. package/dist/esm/{pdf → layout}/preset-geometry.js +2 -2
  102. package/dist/esm/layout/styled-layout.d.ts +84 -0
  103. package/dist/esm/layout/styled-layout.js +1729 -0
  104. package/dist/esm/pdf/cid-font.d.ts +1 -1
  105. package/dist/esm/pdf/cid-font.js +6 -21
  106. package/dist/esm/pdf/image-xobject.d.ts +4 -5
  107. package/dist/esm/pdf/image-xobject.js +21 -324
  108. package/dist/esm/pdf/index.d.ts +2 -2
  109. package/dist/esm/pdf/index.js +1 -0
  110. package/dist/esm/pdf/signature.js +4 -4
  111. package/dist/esm/pdf/styled-page-emitter.d.ts +11 -0
  112. package/dist/esm/pdf/styled-page-emitter.js +557 -0
  113. package/dist/esm/pdf/styled-page-renderer.d.ts +4 -42
  114. package/dist/esm/pdf/styled-page-renderer.js +4 -2311
  115. package/dist/esm/pdf/text-page-renderer.d.ts +1 -1
  116. package/dist/esm/pdf/vector-graphics.d.ts +3 -45
  117. package/dist/esm/pdf/vector-graphics.js +2 -46
  118. package/dist/esm/svg/svg-writer.d.ts +8 -0
  119. package/dist/esm/svg/svg-writer.js +137 -0
  120. package/dist/esm/word/document-parser.d.ts +16 -0
  121. package/dist/esm/{ooxml/wordproc → word}/document-parser.js +55 -47
  122. package/dist/esm/word/docx-reader.d.ts +4 -0
  123. package/dist/esm/word/docx-reader.js +191 -0
  124. package/dist/esm/word/docx-to-pdf.d.ts +34 -0
  125. package/dist/esm/word/docx-to-pdf.js +112 -0
  126. package/dist/esm/{ooxml/wordproc → word}/drawing-parser.d.ts +10 -9
  127. package/dist/esm/{ooxml/wordproc → word}/drawing-parser.js +15 -42
  128. package/dist/esm/{ooxml/wordproc → word}/font-table.d.ts +2 -2
  129. package/dist/esm/{ooxml/wordproc → word}/font-table.js +2 -2
  130. package/dist/esm/{ooxml/wordproc → word}/index.d.ts +3 -2
  131. package/dist/esm/{ooxml/wordproc → word}/numbering-parser.d.ts +1 -1
  132. package/dist/esm/{ooxml/wordproc → word}/numbering-parser.js +1 -1
  133. package/dist/esm/{ooxml/math → word}/omml-parser.d.ts +2 -2
  134. package/dist/esm/{ooxml/math → word}/omml-parser.js +2 -2
  135. package/dist/esm/{ooxml/wordproc → word}/paragraph-properties.d.ts +1 -1
  136. package/dist/esm/{ooxml/wordproc → word}/paragraph-properties.js +9 -8
  137. package/dist/esm/{ooxml/wordproc → word}/po-to-flat.d.ts +1 -1
  138. package/dist/esm/{ooxml/wordproc → word}/po-to-flat.js +2 -2
  139. package/dist/esm/{ooxml/wordproc → word}/run-properties.d.ts +1 -1
  140. package/dist/esm/{ooxml/wordproc → word}/run-properties.js +3 -2
  141. package/dist/esm/{ooxml/wordproc → word}/settings-parser.js +2 -2
  142. package/dist/esm/word/styles-parser.d.ts +2 -0
  143. package/dist/esm/{ooxml/wordproc → word}/styles-parser.js +4 -7
  144. package/dist/esm/word/table-parser.d.ts +4 -0
  145. package/dist/esm/{ooxml/wordproc → word}/table-parser.js +80 -26
  146. package/dist/esm/{ooxml/wordproc → word}/xml-helpers.js +1 -1
  147. package/package.json +5 -6
  148. package/dist/esm/converter/docx-to-pdf.d.ts +0 -15
  149. package/dist/esm/converter/docx-to-pdf.js +0 -227
  150. package/dist/esm/converter/index.d.ts +0 -4
  151. package/dist/esm/converter/index.js +0 -2
  152. package/dist/esm/document-model/index.d.ts +0 -1
  153. package/dist/esm/line-breaker/index.d.ts +0 -2
  154. package/dist/esm/numbering/index.d.ts +0 -1
  155. package/dist/esm/ooxml/index.d.ts +0 -1
  156. package/dist/esm/ooxml/wordproc/document-parser.d.ts +0 -9
  157. package/dist/esm/ooxml/wordproc/styles-parser.d.ts +0 -3
  158. package/dist/esm/ooxml/wordproc/table-parser.d.ts +0 -3
  159. package/dist/esm/style-cascade/index.d.ts +0 -3
  160. package/dist/esm/style-cascade/resolver.d.ts +0 -4
  161. /package/dist/esm/{bidi → core/bidi}/algorithm.d.ts +0 -0
  162. /package/dist/esm/{bidi → core/bidi}/char-types.d.ts +0 -0
  163. /package/dist/esm/{crypto → core/crypto}/asn1.d.ts +0 -0
  164. /package/dist/esm/{crypto → core/crypto}/cms.d.ts +0 -0
  165. /package/dist/esm/{crypto → core/crypto}/index.d.ts +0 -0
  166. /package/dist/esm/{crypto → core/crypto}/index.js +0 -0
  167. /package/dist/esm/{document-model → core/document-model}/index.js +0 -0
  168. /package/dist/esm/{ooxml → core}/drawingml/theme-parser.d.ts +0 -0
  169. /package/dist/esm/{font → core/font}/arabic-joining.d.ts +0 -0
  170. /package/dist/esm/{font → core/font}/binary-reader.d.ts +0 -0
  171. /package/dist/esm/{font → core/font}/opentype-layout.d.ts +0 -0
  172. /package/dist/esm/{font → core/font}/ttf-parser.d.ts +0 -0
  173. /package/dist/esm/{font → core/font}/ttf-subset.d.ts +0 -0
  174. /package/dist/esm/{fonts → core/fonts}/index.d.ts +0 -0
  175. /package/dist/esm/{fonts → core/fonts}/remote-fonts.d.ts +0 -0
  176. /package/dist/esm/{hyphenation → core/hyphenation}/index.d.ts +0 -0
  177. /package/dist/esm/{hyphenation → core/hyphenation}/liang.d.ts +0 -0
  178. /package/dist/esm/{hyphenation → core/hyphenation}/patterns-en-us.d.ts +0 -0
  179. /package/dist/esm/{hyphenation → core/hyphenation}/patterns-ru.d.ts +0 -0
  180. /package/dist/esm/{numbering → core/numbering}/state.d.ts +0 -0
  181. /package/dist/esm/{opc → core/opc}/core-properties.d.ts +0 -0
  182. /package/dist/esm/{opc → core/opc}/index.d.ts +0 -0
  183. /package/dist/esm/{opc → core/opc}/index.js +0 -0
  184. /package/dist/esm/{opc → core/opc}/relationship-types.d.ts +0 -0
  185. /package/dist/esm/{opc → core/opc}/relationships.d.ts +0 -0
  186. /package/dist/esm/{ooxml/wordproc → core}/po-helpers.d.ts +0 -0
  187. /package/dist/esm/{ooxml/spreadsheet → excel}/cell-reference.d.ts +0 -0
  188. /package/dist/esm/{ooxml/spreadsheet → excel}/defined-name-ref.d.ts +0 -0
  189. /package/dist/esm/{ooxml/spreadsheet → excel}/index.js +0 -0
  190. /package/dist/esm/{ooxml/spreadsheet → excel}/number-format.d.ts +0 -0
  191. /package/dist/esm/{ooxml/spreadsheet → excel}/shared-strings-parser.d.ts +0 -0
  192. /package/dist/esm/{ooxml/spreadsheet → excel}/styles-parser.d.ts +0 -0
  193. /package/dist/esm/{ooxml/spreadsheet → excel}/workbook-parser.d.ts +0 -0
  194. /package/dist/esm/{ooxml/spreadsheet → excel}/worksheet-parser.d.ts +0 -0
  195. /package/dist/esm/{pdf → layout}/chart-geometry.js +0 -0
  196. /package/dist/esm/{ooxml/wordproc → word}/index.js +0 -0
  197. /package/dist/esm/{ooxml/wordproc → word}/settings-parser.d.ts +0 -0
  198. /package/dist/esm/{ooxml/wordproc → word}/text-extractor.d.ts +0 -0
  199. /package/dist/esm/{ooxml/wordproc → word}/text-extractor.js +0 -0
  200. /package/dist/esm/{ooxml/wordproc → word}/xml-helpers.d.ts +0 -0
package/README.md CHANGED
@@ -18,75 +18,152 @@ Runtime dependencies are minimal: `fflate` (ZIP/Deflate) and `fast-xml-parser`.
18
18
 
19
19
  ## Usage
20
20
 
21
- Give it the document bytes, get the PDF bytes back. No fonts to wire up — an open
21
+ Parse once into the format-neutral interlayer, convert to any target. The
22
+ format (docx/xlsx) is sniffed from the bytes; no fonts to wire up — an open
22
23
  substitute font (Roboto for sans, Tinos for serif, Cousine for monospace — the
23
24
  same families LibreOffice substitutes) is fetched automatically based on the
24
25
  document's referenced fonts:
25
26
 
26
27
  ```ts
27
- import { convertDocxToPdf } from 'reamkit';
28
+ import { Ream } from 'reamkit';
28
29
 
29
30
  // e.g. from an <input type="file"> or a fetch() — anything that yields bytes.
30
- const docx = new Uint8Array(await file.arrayBuffer());
31
+ const bytes = new Uint8Array(await file.arrayBuffer());
31
32
 
32
- const pdf = await convertDocxToPdf(docx); // async — fetches a font if needed
33
+ const doc = Ream.parse(bytes); // docx or xlsx — sniffed
34
+ const pdf = await doc.convert('pdf'); // async — fetches a font if needed
35
+ const svg = await doc.convert('svg'); // same parse, different target
33
36
 
34
37
  // Hand the bytes to the browser: preview, download, upload, …
35
38
  const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
36
39
  window.open(url);
37
40
  ```
38
41
 
39
- Excel works the same way via `convertXlsxToPdf`. The input/output are plain
40
- `Uint8Array`s, so wiring this to files, the network, or disk is up to you.
42
+ `doc.flow` exposes the parsed document tree, `doc.format` the detected format,
43
+ and `doc.convertWithReport(...)` returns `{ bytes, losses }` (pass
44
+ `strict: true` to throw on the first conversion loss instead). Input/output
45
+ are plain `Uint8Array`s, so wiring this to files, the network, or disk is up
46
+ to you.
41
47
 
42
- ### Bring your own fonts (synchronous, no network)
48
+ ### Bring your own fonts (no network)
43
49
 
44
- To embed specific fonts — or to avoid the network entirely — pass the font bytes
45
- in and use the synchronous variant:
50
+ To embed specific fonts — or to avoid the network entirely — pass the font
51
+ bytes in. `convert` then does zero I/O:
46
52
 
47
53
  ```ts
48
- import { convertDocxToPdfSync } from 'reamkit';
49
-
50
54
  const fonts = {
51
55
  regular: new Uint8Array(await fetch('/fonts/MyFont-Regular.ttf').then((r) => r.arrayBuffer())),
52
56
  bold: new Uint8Array(await fetch('/fonts/MyFont-Bold.ttf').then((r) => r.arrayBuffer())),
57
+ // italic, boldItalic — optional; missing faces degrade gracefully
53
58
  };
54
- const pdf = convertDocxToPdfSync(docx, { fonts });
59
+
60
+ const pdf = await Ream.parse(bytes).convert('pdf', { fonts });
61
+ ```
62
+
63
+ ### Font resolution chain
64
+
65
+ For finer control, chain font providers — first byte answer wins. A remote or
66
+ local winner is recorded as a `substituted` loss in the report:
67
+
68
+ ```ts
69
+ import { Ream, callerFontProvider, localFontProvider, remoteFontProvider } from 'reamkit';
70
+
71
+ const pdf = await doc.convert('pdf', {
72
+ fontProviders: [
73
+ callerFontProvider(myFonts), // your bytes — highest priority
74
+ localFontProvider(), // system fonts (Chromium Local Font Access,
75
+ // embedding-restricted fonts are never used)
76
+ remoteFontProvider(), // open substitute set from CDN, last resort
77
+ ],
78
+ });
55
79
  ```
56
80
 
57
- The async `convertDocxToPdf` also accepts `fonts` (then it does no network I/O),
58
- plus `fontFamily` to force a substitute and `fontFetch` to inject a custom
59
- fetch implementation.
81
+ Fonts the document itself embeds (`w:embed`, including obfuscated `.odttf`)
82
+ are always used first — glyph-exact, no substitution.
60
83
 
61
- ### Options
84
+ ### Archival PDF/A + embedded source
62
85
 
63
- Both converters accept (beyond `fonts` / `fontBytes`):
86
+ The whole PDF/A family is supported (1a/1b, 2a/2b/2u, 3a/3b/3u —
87
+ veraPDF-validated). PDF/A-3 can carry the source document inside the PDF:
64
88
 
65
- - `info` — PDF `/Info` metadata (`title`, `author`, `subject`, …). Also read
66
- automatically from the document's `docProps/core.xml`.
67
- - `pdfA: 'PDF/A-1b'` — emit an archival PDF/A-1b file (embedded sRGB
68
- OutputIntent, XMP, deterministic `/ID`, subset-tagged fonts, no transparency).
69
- - `hyphenator` — a Liang hyphenator (see below) for nicer justified text.
89
+ ```ts
90
+ const { bytes: pdfa, losses } = await doc.convertWithReport('pdf', {
91
+ fonts,
92
+ pdfA: 'PDF/A-3b',
93
+ embedSource: true, // the parsed .docx/.xlsx rides along as /AF Source
94
+ });
95
+ ```
96
+
97
+ ### Digital signatures
98
+
99
+ PKCS#7 detached signatures (ISO 32000 §12.8) via WebCrypto — RSA or ECDSA,
100
+ optional PAdES and RFC 3161 timestamping:
101
+
102
+ ```ts
103
+ const signed = await doc.convert('pdf', {
104
+ fonts,
105
+ signature: { certificate: certDer, privateKey: cryptoKey },
106
+ });
107
+ ```
108
+
109
+ ### Strict mode and the loss report
110
+
111
+ Every conversion can report what was dropped, degraded or substituted. For
112
+ compliance-critical flows, make any loss fatal:
113
+
114
+ ```ts
115
+ const { bytes, losses } = await doc.convertWithReport('pdf', { fonts });
116
+ // losses: [{ severity: 'substituted', feature: 'fonts.substitution', … }]
117
+
118
+ await doc.convert('pdf', { fonts, strict: true }); // throws ConversionLossError on the first loss
119
+ ```
120
+
121
+ ### Inspect the interlayer
122
+
123
+ `parse` produces a format-neutral document tree (the interlayer) before any
124
+ rendering — inspect or analyze it without converting:
125
+
126
+ ```ts
127
+ const doc = Ream.parse(bytes);
128
+ doc.format; // 'docx' | 'xlsx'
129
+ doc.flow.body; // paragraphs / tables / images / charts …
130
+ doc.losses; // read-time losses
131
+ ```
70
132
 
71
133
  ### Hyphenation (optional)
72
134
 
73
135
  ```ts
74
136
  import { getHyphenator } from 'reamkit';
75
137
  const hyphenator = await getHyphenator('en-us'); // or 'ru'
76
- const pdf = await convertDocxToPdf(docx, { hyphenator });
138
+ const pdf = await doc.convert('pdf', { fonts, hyphenator });
77
139
  ```
78
140
 
79
- ### Advanced
141
+ ### More options
142
+
143
+ `convert` accepts (beyond the above): `info` (PDF `/Info` metadata — also read
144
+ automatically from the document's `docProps/core.xml`), `attachments`
145
+ (PDF/A-3 associated files), `tagged` (logical structure without full PDF/A),
146
+ `pageWidth`/`pageHeight`/margins overrides.
147
+
148
+ ### Lower-level APIs
80
149
 
81
- `renderStyledPdf` drives the layout engine directly. The typed document model is
82
- available from the `reamkit/document-model` subpath.
150
+ - `docxReader` / `xlsxReader`, `svgWriter` — the `@experimental` reader/writer
151
+ interfaces of the interlayer, for building custom pipelines (and keeping
152
+ unused formats out of your bundle); `layoutStyledDocument` produces the
153
+ frozen page model (`PageItem` pages in a top-left `Pt` frame) they consume.
154
+ - `renderStyledPdf` drives the layout engine directly; the typed document
155
+ model is on the `reamkit/document-model` subpath.
83
156
 
84
157
  ## Scope
85
158
 
86
- Implemented: WordprocessingML text/styles/tables/lists/sections/headers-footers/
87
- images, SpreadsheetML grids/number-formats/dates, Type0+CIDFontType2 font
88
- embedding with subsetting, Knuth-Plass line breaking, Liang hyphenation,
89
- OpenType ligatures + kerning, BiDi (UAX #9), and PDF/A-1b.
159
+ Implemented: WordprocessingML text/styles/tables/lists/multi-section
160
+ layout/headers-footers/images/tracked changes, SpreadsheetML grids,
161
+ number formats and the print model (gridlines, print area, fit-to-page,
162
+ repeated titles, page breaks), DrawingML shapes and charts, OMML math,
163
+ Type0+CIDFontType2 embedding with subsetting, Knuth-Plass line breaking,
164
+ Liang hyphenation, OpenType ligatures/kerning + Arabic cursive joining,
165
+ BiDi (UAX #9), tagged PDF, PDF/A-1/2/3 (a/b/u), digital signatures
166
+ (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page preview.
90
167
 
91
168
  See `handoff.md` for the full feature matrix and known limitations.
92
169
 
@@ -1,5 +1,5 @@
1
1
  import { bidiClass } from "./char-types.js";
2
- //#region src/bidi/algorithm.ts
2
+ //#region src/core/bidi/algorithm.ts
3
3
  var MAX_DEPTH = 125;
4
4
  function matchingPDI(types, openIdx) {
5
5
  let depth = 1;
@@ -1,4 +1,4 @@
1
- //#region src/bidi/char-types.ts
1
+ //#region src/core/bidi/char-types.ts
2
2
  var SINGLE = new Map([
3
3
  [9, "S"],
4
4
  [10, "B"],
@@ -12,3 +12,5 @@ export declare function analyzeString(text: string, dir?: Direction): StringBidi
12
12
  export declare function hasBidiCharacters(text: string): boolean;
13
13
  export declare function reverseByCodePoint(text: string): string;
14
14
  export declare function reorderTokens(tokenLevels: ReadonlyArray<number>): Array<number>;
15
+ export { segmentLevels } from './segments.js';
16
+ export type { BidiSegment } from './segments.js';
@@ -1,6 +1,7 @@
1
1
  import "./char-types.js";
2
2
  import { computeBidi } from "./algorithm.js";
3
- //#region src/bidi/index.ts
3
+ import "./segments.js";
4
+ //#region src/core/bidi/index.ts
4
5
  function analyzeString(text, dir = "auto") {
5
6
  const codePoints = [];
6
7
  for (const ch of text) codePoints.push(ch.codePointAt(0));
@@ -0,0 +1,6 @@
1
+ export interface BidiSegment {
2
+ readonly text: string;
3
+ readonly object?: boolean;
4
+ readonly rtl?: boolean;
5
+ }
6
+ export declare function segmentLevels(segments: ReadonlyArray<BidiSegment>, baseDir: 'ltr' | 'rtl'): Array<number>;
@@ -0,0 +1,41 @@
1
+ import { computeBidi } from "./algorithm.js";
2
+ //#region src/core/bidi/segments.ts
3
+ var OBJECT_REPLACEMENT = 65532;
4
+ var RLE = 8235;
5
+ var PDF_FMT = 8236;
6
+ var LRE = 8234;
7
+ function segmentLevels(segments, baseDir) {
8
+ const bidiCps = [];
9
+ const realIndexOfBidi = [];
10
+ let realCount = 0;
11
+ for (const seg of segments) {
12
+ const isObject = seg.object === true;
13
+ const wrap = seg.rtl ? RLE : baseDir === "rtl" && !isObject ? LRE : 0;
14
+ if (wrap) {
15
+ bidiCps.push(wrap);
16
+ realIndexOfBidi.push(-1);
17
+ }
18
+ if (isObject) {
19
+ bidiCps.push(OBJECT_REPLACEMENT);
20
+ realIndexOfBidi.push(realCount);
21
+ realCount++;
22
+ } else for (const ch of seg.text) {
23
+ bidiCps.push(ch.codePointAt(0));
24
+ realIndexOfBidi.push(realCount);
25
+ realCount++;
26
+ }
27
+ if (wrap) {
28
+ bidiCps.push(PDF_FMT);
29
+ realIndexOfBidi.push(-1);
30
+ }
31
+ }
32
+ const { levels } = computeBidi(bidiCps, baseDir);
33
+ const realLevels = new Array(realCount).fill(baseDir === "rtl" ? 1 : 0);
34
+ for (let i = 0; i < bidiCps.length; i++) {
35
+ const ri = realIndexOfBidi[i];
36
+ if (ri >= 0) realLevels[ri] = levels[i];
37
+ }
38
+ return realLevels;
39
+ }
40
+ //#endregion
41
+ export { segmentLevels };
@@ -0,0 +1 @@
1
+ export declare function bytesInclude(haystack: Uint8Array, needle: string): boolean;
@@ -0,0 +1,11 @@
1
+ //#region src/core/bytes.ts
2
+ function bytesInclude(haystack, needle) {
3
+ const n = new TextEncoder().encode(needle);
4
+ outer: for (let i = 0; i + n.length <= haystack.length; i++) {
5
+ for (let j = 0; j < n.length; j++) if (haystack[i + j] !== n[j]) continue outer;
6
+ return true;
7
+ }
8
+ return false;
9
+ }
10
+ //#endregion
11
+ export { bytesInclude };
@@ -0,0 +1,45 @@
1
+ import { ConvertDocxOptions } from '../../word/docx-to-pdf.js';
2
+ import { DocumentReader } from '../ir/adapters.js';
3
+ import { FlowDoc } from '../ir/flow.js';
4
+ import { FontProvider } from '../fonts/provider.js';
5
+ import { Loss, LossReport } from '../ir/index.js';
6
+ import { FontBytesByVariant } from '../font/index.js';
7
+ export interface ConvertOptions extends ConvertDocxOptions {
8
+ /** Target format: 'pdf' (default) or 'svg' (page-stack preview). */
9
+ readonly to?: 'pdf' | 'svg';
10
+ /**
11
+ * Strict mode (handoff v1 §5): throw ConversionLossError on the first
12
+ * recorded loss instead of returning it in the report.
13
+ */
14
+ readonly strict?: boolean;
15
+ /**
16
+ * Font resolution chain (ir-design §8). When set (and no caller `fonts`),
17
+ * the facade resolves the default font set through these providers — e.g.
18
+ * [localFontProvider(), remoteFontProvider()] — and reports a 'substituted'
19
+ * loss when anything below a caller/embedded answer wins. Local Font Access
20
+ * stays strictly opt-in via this option (it can trigger a permission
21
+ * prompt), which is why nothing wires it in by default.
22
+ */
23
+ readonly fontProviders?: ReadonlyArray<FontProvider>;
24
+ }
25
+ export interface ConvertResult {
26
+ readonly bytes: Uint8Array;
27
+ readonly losses: LossReport;
28
+ }
29
+ export interface Converter {
30
+ /** The registered readers, in sniffing order. */
31
+ readonly readers: ReadonlyArray<DocumentReader<FlowDoc>>;
32
+ /** Detect the input format by reader sniffing; undefined when unknown. */
33
+ detect: (bytes: Uint8Array) => DocumentReader<FlowDoc> | undefined;
34
+ convert: (bytes: Uint8Array, options?: ConvertOptions) => Promise<ConvertResult>;
35
+ }
36
+ export interface CreateConverterOptions {
37
+ /** Override / extend the reader registry (defaults to docx + xlsx). */
38
+ readonly readers?: ReadonlyArray<DocumentReader<FlowDoc>>;
39
+ }
40
+ export declare const DEFAULT_READERS: ReadonlyArray<DocumentReader<FlowDoc>>;
41
+ export declare function createConverter(opts?: CreateConverterOptions): Converter;
42
+ export declare function resolveFontsViaChain(providers: ReadonlyArray<FontProvider>): Promise<{
43
+ fonts?: FontBytesByVariant;
44
+ loss?: Loss;
45
+ }>;
@@ -0,0 +1,89 @@
1
+ import { FontRegistry } from "../font/font-registry.js";
2
+ import { FEATURES } from "../ir/features.js";
3
+ import { ConversionLossError } from "../ir/loss.js";
4
+ import { layoutStyledDocument } from "../../layout/styled-layout.js";
5
+ import { docxReader } from "../../word/docx-reader.js";
6
+ import { xlsxReader } from "../../excel/xlsx-reader.js";
7
+ import { chainProviders } from "../fonts/provider.js";
8
+ import { flowRenderOptions } from "./project.js";
9
+ import { writeSvg } from "../../svg/svg-writer.js";
10
+ import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
11
+ import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
12
+ //#region src/core/converter/facade.ts
13
+ var DEFAULT_READERS = [docxReader, xlsxReader];
14
+ function createConverter(opts = {}) {
15
+ const readers = opts.readers ?? DEFAULT_READERS;
16
+ const detect = (bytes) => readers.find((r) => r.sniff(bytes));
17
+ const convert = async (bytes, options = {}) => {
18
+ const { to = "pdf", strict = false, fontProviders, ...rest } = options;
19
+ const reader = detect(bytes);
20
+ if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
21
+ const losses = [];
22
+ let conv = rest;
23
+ if (fontProviders && fontProviders.length > 0 && !rest.fonts && !rest.fontBytes) {
24
+ const { fonts, loss } = await resolveFontsViaChain(fontProviders);
25
+ if (fonts) conv = {
26
+ ...rest,
27
+ fonts
28
+ };
29
+ if (loss) losses.push(loss);
30
+ }
31
+ if (to === "svg") {
32
+ const fonts = conv.fonts ?? (conv.fontBytes ? { regular: conv.fontBytes } : void 0);
33
+ if (!fonts) throw new Error("to: 'svg' requires options.fonts/fontBytes or fontProviders");
34
+ const { doc: flow } = reader.read(bytes);
35
+ const svg = writeSvg(layoutStyledDocument(flow.body, {
36
+ registry: FontRegistry.fromBytes(fonts),
37
+ ...flowRenderOptions(flow)
38
+ }));
39
+ losses.push(...svg.losses);
40
+ if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
41
+ return {
42
+ bytes: svg.bytes,
43
+ losses
44
+ };
45
+ }
46
+ const pdf = reader.id === "xlsx" ? await convertXlsxToPdf(bytes, conv) : await convertDocxToPdf(bytes, conv);
47
+ if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
48
+ return {
49
+ bytes: pdf,
50
+ losses
51
+ };
52
+ };
53
+ return {
54
+ readers,
55
+ detect,
56
+ convert
57
+ };
58
+ }
59
+ async function resolveFontsViaChain(providers) {
60
+ const chain = chainProviders(providers);
61
+ const ask = (bold, italic) => chain.resolve({
62
+ bold,
63
+ italic
64
+ });
65
+ const [regular, bold, italic, boldItalic] = await Promise.all([
66
+ ask(false, false),
67
+ ask(true, false),
68
+ ask(false, true),
69
+ ask(true, true)
70
+ ]);
71
+ if (regular.kind !== "bytes") return {};
72
+ const fonts = {
73
+ regular: regular.bytes,
74
+ ...bold.kind === "bytes" ? { bold: bold.bytes } : {},
75
+ ...italic.kind === "bytes" ? { italic: italic.bytes } : {},
76
+ ...boldItalic.kind === "bytes" ? { boldItalic: boldItalic.bytes } : {}
77
+ };
78
+ const loss = regular.providerId === "remote" || regular.providerId === "local" ? {
79
+ severity: "substituted",
80
+ feature: FEATURES.fontsSubstitution,
81
+ detail: `document fonts rendered with ${regular.faceName} (provider: ${regular.providerId})`
82
+ } : void 0;
83
+ return {
84
+ fonts,
85
+ ...loss ? { loss } : {}
86
+ };
87
+ }
88
+ //#endregion
89
+ export { DEFAULT_READERS, createConverter, resolveFontsViaChain };
@@ -0,0 +1,4 @@
1
+ export { convertDocxToPdf, convertDocxToPdfSync, detectDocxFontFamily } from '../../word/docx-to-pdf.js';
2
+ export type { ConvertDocxOptions } from '../../word/docx-to-pdf.js';
3
+ export { convertXlsxToPdf, convertXlsxToPdfSync } from '../../excel/xlsx-to-pdf.js';
4
+ export type { ConvertXlsxOptions } from '../../excel/xlsx-to-pdf.js';
@@ -0,0 +1,4 @@
1
+ import { FlowDoc } from '../ir/flow.js';
2
+ import { StyledRenderOptions } from '../../pdf/index.js';
3
+ export type FlowRenderOptions = Omit<StyledRenderOptions, 'registry'>;
4
+ export declare function flowRenderOptions(flow: FlowDoc): FlowRenderOptions;
@@ -0,0 +1,17 @@
1
+ import { EMPTY_STYLE_SHEET } from "../style-cascade/resolver.js";
2
+ import "../style-cascade/index.js";
3
+ //#region src/core/converter/project.ts
4
+ function flowRenderOptions(flow) {
5
+ return {
6
+ styles: EMPTY_STYLE_SHEET,
7
+ ...flow.sections.length > 0 ? { sections: flow.sections } : {},
8
+ ...flow.section ? { section: flow.section } : {},
9
+ ...flow.headersFooters ? { headersFooters: flow.headersFooters } : {},
10
+ resources: flow.resources,
11
+ ...flow.charts ? { charts: flow.charts } : {},
12
+ ...flow.embeddedFonts ? { embeddedFonts: flow.embeddedFonts } : {},
13
+ ...flow.language ? { language: flow.language } : {}
14
+ };
15
+ }
16
+ //#endregion
17
+ export { flowRenderOptions };
@@ -0,0 +1,35 @@
1
+ import { ConvertResult } from './facade.js';
2
+ import { FontBytesByVariant } from '../font/index.js';
3
+ import { FetchLike } from '../fonts/index.js';
4
+ import { FontProvider } from '../fonts/provider.js';
5
+ import { Loss } from '../ir/index.js';
6
+ import { DocumentReader } from '../ir/adapters.js';
7
+ import { FlowDoc } from '../ir/flow.js';
8
+ import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
9
+ export type ReamTarget = 'pdf' | 'svg';
10
+ export interface ReamParseOptions {
11
+ readonly readers?: ReadonlyArray<DocumentReader>;
12
+ }
13
+ export interface ReamConvertOptions extends Omit<StyledRenderOptions, 'registry' | 'styles'> {
14
+ readonly fonts?: FontBytesByVariant;
15
+ readonly fontBytes?: Uint8Array;
16
+ readonly fontFamily?: string;
17
+ readonly fontFetch?: FetchLike;
18
+ readonly fontProviders?: ReadonlyArray<FontProvider>;
19
+ readonly strict?: boolean;
20
+ readonly embedSource?: boolean;
21
+ readonly signature?: SignatureOptions;
22
+ }
23
+ export declare class Ream {
24
+ readonly flow: FlowDoc;
25
+ readonly losses: ReadonlyArray<Loss>;
26
+ private readonly source;
27
+ private readonly readerId;
28
+ private constructor();
29
+ static parse(bytes: Uint8Array, options?: ReamParseOptions): Ream;
30
+ get format(): string;
31
+ convert(to: ReamTarget, options?: ReamConvertOptions): Promise<Uint8Array>;
32
+ convertWithReport(to: ReamTarget, options?: ReamConvertOptions): Promise<ConvertResult>;
33
+ private resolveFonts;
34
+ private enforceStrict;
35
+ }
@@ -0,0 +1,101 @@
1
+ import { fetchFontSet } from "../fonts/remote-fonts.js";
2
+ import { FontRegistry } from "../font/font-registry.js";
3
+ import { ConversionLossError } from "../ir/loss.js";
4
+ import { layoutStyledDocument } from "../../layout/styled-layout.js";
5
+ import { signPdf } from "../../pdf/signature.js";
6
+ import { renderStyledPdf } from "../../pdf/styled-page-renderer.js";
7
+ import "../../pdf/index.js";
8
+ import { flowRenderOptions } from "./project.js";
9
+ import { writeSvg } from "../../svg/svg-writer.js";
10
+ import { resolveDocxAutoFonts } from "../../word/docx-to-pdf.js";
11
+ import { DEFAULT_READERS, resolveFontsViaChain } from "./facade.js";
12
+ //#region src/core/converter/ream.ts
13
+ var SOURCE_MIME = {
14
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
15
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
16
+ };
17
+ var Ream = class Ream {
18
+ constructor(flow, losses, source, readerId) {
19
+ this.flow = flow;
20
+ this.losses = losses;
21
+ this.source = source;
22
+ this.readerId = readerId;
23
+ }
24
+ static parse(bytes, options = {}) {
25
+ const readers = options.readers ?? DEFAULT_READERS;
26
+ const reader = readers.find((r) => r.sniff(bytes));
27
+ if (!reader) throw new Error(`Unrecognized document format (readers: ${readers.map((r) => r.id).join(", ")})`);
28
+ const { doc, losses } = reader.read(bytes);
29
+ return new Ream(doc, losses, bytes, reader.id);
30
+ }
31
+ get format() {
32
+ return this.readerId;
33
+ }
34
+ async convert(to, options = {}) {
35
+ return (await this.convertWithReport(to, options)).bytes;
36
+ }
37
+ async convertWithReport(to, options = {}) {
38
+ const losses = [...this.losses];
39
+ const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
40
+ const registry = FontRegistry.fromBytes(fonts);
41
+ if (to === "svg") {
42
+ const svg = writeSvg(layoutStyledDocument(this.flow.body, {
43
+ registry,
44
+ ...flowRenderOptions(this.flow)
45
+ }));
46
+ losses.push(...svg.losses);
47
+ this.enforceStrict(options, losses);
48
+ return {
49
+ bytes: svg.bytes,
50
+ losses
51
+ };
52
+ }
53
+ const { fonts: _a, fontBytes: _b, fontFamily: _c, fontFetch: _d, fontProviders: _e, strict: _f, embedSource, signature, info: callerInfo, attachments: callerAttachments, ...renderOptions } = options;
54
+ const info = this.flow.info || callerInfo ? {
55
+ ...this.flow.info,
56
+ ...callerInfo
57
+ } : void 0;
58
+ const attachments = [...callerAttachments ?? []];
59
+ if (embedSource && options.pdfA?.startsWith("PDF/A-3")) attachments.push({
60
+ name: `source.${this.readerId}`,
61
+ bytes: this.source,
62
+ mimeType: SOURCE_MIME[this.readerId] ?? "application/octet-stream",
63
+ relationship: "Source",
64
+ description: "Source document"
65
+ });
66
+ let pdf = renderStyledPdf(this.flow.body, {
67
+ registry,
68
+ ...registriesByFamily ? { registriesByFamily } : {},
69
+ ...flowRenderOptions(this.flow),
70
+ ...info ? { info } : {},
71
+ ...attachments.length > 0 ? { attachments } : {},
72
+ ...signature ? { signaturePlaceholder: signature } : {},
73
+ ...renderOptions
74
+ });
75
+ if (signature) pdf = await signPdf(pdf, signature);
76
+ this.enforceStrict(options, losses);
77
+ return {
78
+ bytes: pdf,
79
+ losses
80
+ };
81
+ }
82
+ async resolveFonts(options, losses) {
83
+ const explicit = options.fonts ?? (options.fontBytes ? { regular: options.fontBytes } : void 0);
84
+ if (explicit) return { fonts: explicit };
85
+ if (options.fontProviders && options.fontProviders.length > 0) {
86
+ const { fonts, loss } = await resolveFontsViaChain(options.fontProviders);
87
+ if (loss) losses.push(loss);
88
+ if (fonts) return { fonts };
89
+ }
90
+ if (this.readerId === "docx") return resolveDocxAutoFonts(this.source, options);
91
+ return { fonts: await fetchFontSet({
92
+ ...options.fontFamily ? { family: options.fontFamily } : {},
93
+ ...options.fontFetch ? { fetch: options.fontFetch } : {}
94
+ }) };
95
+ }
96
+ enforceStrict(options, losses) {
97
+ if (options.strict && losses.length > 0) throw new ConversionLossError(losses[0]);
98
+ }
99
+ };
100
+ //#endregion
101
+ export { Ream };
@@ -1,4 +1,4 @@
1
- //#region src/crypto/asn1.ts
1
+ //#region src/core/crypto/asn1.ts
2
2
  var textEncoder = new TextEncoder();
3
3
  function concat(arrays) {
4
4
  let total = 0;
@@ -1,5 +1,5 @@
1
1
  import { certIssuerAndSerial, cmsTime, concat, explicit, integer, nullValue, octetString, oid, seq, set, setOfBody, tlv } from "./asn1.js";
2
- //#region src/crypto/cms.ts
2
+ //#region src/core/crypto/cms.ts
3
3
  var OID = {
4
4
  sha256: "2.16.840.1.101.3.4.2.1",
5
5
  rsaEncryption: "1.2.840.113549.1.1.1",
@@ -0,0 +1 @@
1
+ export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, Chart, ChartBlock, ChartDataPoint, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, Run, RunProperties, RowProperties, Section, SectionProperties, ShapeBlock, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableProperties, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';