rapier-markdown-kit 1.1.102 → 1.2.3

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 (82) hide show
  1. package/LICENSE +5 -2
  2. package/README.md +245 -54
  3. package/dist/agent/vendor/hyphen-en-us/LICENSE +14 -0
  4. package/dist/agent/vendor/hyphen-en-us/SOURCE.json +33 -0
  5. package/dist/agent/vendor/hyphen-en-us/hyph-en-us.hyp.txt +14 -0
  6. package/dist/agent/vendor/hyphen-en-us/hyph-en-us.pat.txt +4938 -0
  7. package/dist/agent/vendor/hyphen-en-us/patterns.js +25 -0
  8. package/dist/agent/vendor/pretext/LICENSE +208 -0
  9. package/dist/agent/vendor/pretext/NOTICE +26 -13
  10. package/dist/agent/vendor/pretext/SOURCE.json +213 -45
  11. package/dist/agent/vendor/pretext/analysis.js +675 -1224
  12. package/dist/agent/vendor/pretext/entry-geometry.js +183 -0
  13. package/dist/agent/vendor/pretext/gecko-line-breaks.js +688 -0
  14. package/dist/agent/vendor/pretext/generated/engine-break-data.js +57 -0
  15. package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +80 -0
  16. package/dist/agent/vendor/pretext/graphemes.js +98 -0
  17. package/dist/agent/vendor/pretext/han-kerning.js +253 -0
  18. package/dist/agent/vendor/pretext/layout.js +352 -290
  19. package/dist/agent/vendor/pretext/line-break.js +1154 -588
  20. package/dist/agent/vendor/pretext/line-breaks.js +892 -0
  21. package/dist/agent/vendor/pretext/line-text.js +81 -39
  22. package/dist/agent/vendor/pretext/measurement.js +1105 -176
  23. package/dist/agent/vendor/pretext/prepare.js +807 -0
  24. package/dist/agent/vendor/pretext/rich-inline.js +1242 -273
  25. package/dist/agent/will.mjs +2 -0
  26. package/dist/images/gif.mjs +47 -0
  27. package/dist/images/playback.mjs +101 -0
  28. package/dist/kit/assets.mjs +5 -1
  29. package/dist/kit/conformance/03-wrap-around-silhouette.md +1 -1
  30. package/dist/kit/conformance/04-wrap-box.md +1 -1
  31. package/dist/kit/conformance/05-behind.md +1 -1
  32. package/dist/kit/conformance/06-front.md +1 -1
  33. package/dist/kit/conformance/07-rotate-raster.md +1 -1
  34. package/dist/kit/conformance/08-drawing-rotation.md +1 -1
  35. package/dist/kit/conformance/09-drawing-ring-interior.expected.json +18 -18
  36. package/dist/kit/conformance/09-drawing-ring-interior.md +1 -1
  37. package/dist/kit/conformance/10-both-sides.md +1 -1
  38. package/dist/kit/conformance/11-neighbour-skips-image.md +1 -1
  39. package/dist/kit/conformance/{12-heading-barrier.expected.json → 12-rule-barrier.expected.json} +1 -1
  40. package/dist/kit/conformance/{12-heading-barrier.md → 12-rule-barrier.md} +2 -2
  41. package/dist/kit/conformance/README.md +19 -28
  42. package/dist/kit/conformance/run.mjs +13 -11
  43. package/dist/kit/export-fonts.mjs +26 -11
  44. package/dist/kit/index.mjs +1 -0
  45. package/dist/kit/ledger/carried.mjs +17 -10
  46. package/dist/kit/ledger/data.mjs +73 -13
  47. package/dist/kit/ledger/format.mjs +176 -54
  48. package/dist/kit/ledger/hash.mjs +91 -18
  49. package/dist/kit/ledger/history.mjs +156 -116
  50. package/dist/kit/ledger/inverse-source.mjs +158 -19
  51. package/dist/kit/ledger/journal-records.mjs +292 -67
  52. package/dist/kit/ledger/merge.mjs +4 -3
  53. package/dist/kit/model.mjs +1 -1
  54. package/dist/kit/pdf-glyphs.mjs +4 -4
  55. package/dist/kit/pdf-vectors.mjs +4 -1
  56. package/dist/kit/rapier-ledger.mjs +2 -2
  57. package/dist/kit/render-ink.mjs +1 -1
  58. package/dist/kit/render-markdown.mjs +400 -148
  59. package/dist/kit/render-print.mjs +131 -55
  60. package/dist/kit/render-sanitize.mjs +30 -7
  61. package/dist/kit/render-work.mjs +99 -20
  62. package/dist/kit/render.mjs +133 -140
  63. package/dist/kit/typography.mjs +2 -0
  64. package/dist/layout/hyphenation.mjs +127 -0
  65. package/dist/layout/line-plan.mjs +88 -96
  66. package/dist/layout/model.mjs +71 -88
  67. package/dist/layout/paragraph-fit.mjs +191 -0
  68. package/dist/layout/preparation.mjs +373 -0
  69. package/dist/layout/prepared-paragraph.mjs +223 -0
  70. package/dist/layout/typography.mjs +345 -111
  71. package/dist/packages/rapier-diff/index.mjs +359 -224
  72. package/dist/spec/document-fonts.mjs +14 -5
  73. package/dist/spec/document-settings.mjs +11 -164
  74. package/dist/spec/html-blocks.mjs +618 -79
  75. package/dist/spec/md-assets.mjs +178 -36
  76. package/dist/spec/md-layout.mjs +13 -2
  77. package/dist/spec/md-marks.mjs +220 -127
  78. package/dist/spec/md-source.mjs +157 -0
  79. package/package.json +2 -1
  80. package/style.css +15 -50
  81. package/dist/agent/vendor/pretext/bidi.js +0 -151
  82. package/dist/agent/vendor/pretext/generated/bidi-data.js +0 -3831
package/LICENSE CHANGED
@@ -21,8 +21,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
22
 
23
23
  Files under this licence carry `SPDX-License-Identifier: MIT` in their first lines: the standard's
24
- grammar, marks and reference style (`spec/md-layout.mjs`, `spec/md-marks.mjs`, `spec/flowchart.mjs`, `spec/markdown-style.css`), the layout model and line planner
25
- (`layout/model.mjs`, `layout/line-plan.mjs`, `layout/typography.mjs`),
24
+ grammar, marks and reference style (`spec/md-layout.mjs`, `spec/md-marks.mjs`, `spec/md-source.mjs`, `spec/flowchart.mjs`, `spec/markdown-style.css`), ink geometry
25
+ (`spec/ink.mjs`),
26
+ the layout model and line planner (`layout/model.mjs`, `layout/line-plan.mjs`, `layout/typography.mjs`, `layout/preparation.mjs`,
27
+ `layout/prepared-paragraph.mjs`, `layout/hyphenation.mjs`, `layout/paragraph-fit.mjs`), the animated-picture reader and playback
28
+ the HTML blocks use (`images/gif.mjs`, `images/playback.mjs`),
26
29
  the portable HTML and document metadata helpers (`spec/html-blocks.mjs`, `spec/frontmatter.mjs`,
27
30
  `spec/document-settings.mjs`, `spec/document-fonts.mjs`), the Will grammar (`agent/will.mjs`), the picture appendix reader and writer
28
31
  (`spec/md-assets.mjs`), the two shared-page reference readers (`tools/read-shared-page.mjs`,
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # rapier-markdown-kit
2
2
 
3
3
  Read, render and write Self-contained Markdown in browsers and Node: one `.md` file with pictures, editable SVG
4
- drawings, layout and colour. Build portable documents for people and agents. Will/1 regions mark what an agent
4
+ drawings, layout and precise inline typography. Build portable documents for people and agents. Will/1 regions mark what an agent
5
5
  may edit, append to or preserve. MIT, no dependencies.
6
6
 
7
- - Everything past CommonMark and GFM is an HTML comment other readers ignore or an ordinary reference
8
- definition, so every Markdown app still reads the file.
7
+ - Typography uses ordinary inline HTML spans with validated size, colour and family. Comments and references
8
+ carry other optional facts; each reader chooses its HTML, style, extension and resource support.
9
9
  - One stylesheet renders the document as the editor and its exported page do.
10
10
  - The line planner flows text around a picture's real shape; a conformance suite of thirteen documents runs
11
11
  any implementation against it.
@@ -26,7 +26,7 @@ npm install rapier-markdown-kit
26
26
 
27
27
  ```js
28
28
  import {parseLayout} from 'rapier-markdown-kit/layout'; // the md-layout comment a picture carries
29
- import {scanColorMarkers, formatPageBreak} from 'rapier-markdown-kit/marks'; // colour runs and page breaks
29
+ import {typography, formatPageBreak} from 'rapier-markdown-kit/marks'; // inline typography and page breaks
30
30
  import {flowLines} from 'rapier-markdown-kit/model'; // the line planner: text around pictures
31
31
  import {parseWill} from 'rapier-markdown-kit/will'; // Will/1 markers: keep, append, edit
32
32
  import {parseAssets} from 'rapier-markdown-kit/assets'; // the picture appendix
@@ -37,7 +37,7 @@ import {parseAssets} from 'rapier-markdown-kit/assets'; // the picture appendi
37
37
  Rapier is a phone-first Markdown editor for notes, diagrams, drawing and watercolor painting. It runs offline
38
38
  as one HTML file on Android, Web and Windows.
39
39
 
40
- 1. **Work together live.** Connect to `https://mcp.rapier.website/mcp`, then call `open` to share one document with the person.
40
+ 1. **Work together live.** Connect to `https://mcp.rapier.website/door/mcp`, then call `open` to share one document with the person.
41
41
  2. **Deliver an offline editor.** Run `npx rapier-html notes.md` to put the document and editor in one HTML file ([rapier-html](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-html)).
42
42
  3. **Embed in your app.** Install [rapier-embed](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-embed); mount the editor or reader, with your app's agent over WebMCP in the editor.
43
43
  4. **Keep portable Markdown.** Use [rapier-markdown-kit](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-markdown-kit) for pictures, editable SVG drawings and layout in one `.md` file. Respect [Will/1](https://github.com/jackskip22/will) `keep`, `append` and `edit` regions.
@@ -64,6 +64,27 @@ const {html, filename} = await writer.render(source, {filename: 'report.md'});
64
64
  ```
65
65
 
66
66
  `createMarkdownRenderer(host).render(source, metadata)` returns the semantic HTML.
67
+ `createMarkdownRenderer(host).renderWithMathML(source, metadata)` returns
68
+ `{html, equations}` using the ready host math provider's `renderWithMathML` operation.
69
+ The provider must also support the existing `renderToString` operation.
70
+ Each equation is parsed once for its SVG and MathML. The HTML stays identical to
71
+ ordinary SVG rendering; `equations` follows render order and carries `source`
72
+ (the admitted token spelling, including its delimiters), `tex`, `displayMode`, `semantics`, and SVG
73
+ `errors`. Pass `{docKind: 'markdown'}` as metadata for Markdown documents.
74
+ The existing Markdown grammar may normalize display-block indentation; the original
75
+ document remains the source of untouched document bytes.
76
+ Inspect both `errors` and `semantics.status`: a rendered SVG can have refused or
77
+ partial semantic output. The method throws if the combined provider is unavailable
78
+ or fails, rather than returning an empty semantic success. It does not return an AST.
79
+
80
+ This explicit method makes structured output available to consuming applications;
81
+ Rapier's live editor and ordinary HTML/DOCX/print exports still use the current
82
+ engine's Latin Modern SVG path. They do not insert hidden MathML, synthesize speech,
83
+ or claim screen-reader qualification. A consumer that inserts MathML must preserve
84
+ its namespace and semantics/annotation elements, name enclosing links meaningfully,
85
+ and qualify its own accessibility presentation. The current ordinary document
86
+ sanitizer is not a MathML composition API.
87
+
67
88
  The page writer's `render` returns `{html, filename}` asynchronously and preserves the
68
89
  original source, including BOM and line endings, in the SHA-256-verified source carrier.
69
90
  Every existing lower-level method is also returned for hosts that compose the stages.
@@ -119,17 +140,138 @@ family name share the host's font namespace. Print retains its existing factory
119
140
  | Import | What it is |
120
141
  | --- | --- |
121
142
  | `rapier-markdown-kit/layout` | The `md-layout:v1` comment a picture carries: parse it, validate it, write it back. |
122
- | `rapier-markdown-kit/marks` | Text colour and page-break markers. |
143
+ | `rapier-markdown-kit/marks` | Unified typography values, span writer and parser installer; preserved colour/font comments, ink and page breaks. |
123
144
  | `rapier-markdown-kit/model` | Occupancy profiles and the line planner: the wrapping itself. |
145
+ | `rapier-markdown-kit/typography` | Source-preserving paragraph refinement through `createTypography()`. |
124
146
  | `rapier-markdown-kit/will` | The Will/1 grammar (a separate, optional standard): the markers around an agent's intent regions. |
125
147
  | `rapier-markdown-kit/assets` | The picture appendix: the reference definitions a document's pictures live in. It never decodes a picture. |
126
148
  | `rapier-markdown-kit/style.css` | The reference style: type, lists, tables, tasks, pictures and the document's other markup, scoped to `.md-render`. |
127
149
  | `rapier-markdown-kit/rapier-ledger` | The MIT edit-ledger format, replay, authorship projection and offline merge. |
128
150
  | `rapier-markdown-kit/ledger/carried` | Checked optional parts for pages and saved Markdown. |
129
- | `rapier-markdown-kit` | Named exports from the layout, marks, model, Will, assets and render modules, plus the `ledger` namespace. |
151
+ | `rapier-markdown-kit` | Named exports from the layout, marks, model, typography, Will, assets and render modules, plus the `ledger` namespace. |
152
+
153
+ The canonical inline form is ordinary HTML inside Markdown. CSS support varies; the host's font metrics still
154
+ determine line layout (below).
155
+
156
+ ## Refined paragraphs
157
+
158
+ The existing comment expresses the paragraph's intent without storing font-dependent line breaks:
159
+
160
+ ```md
161
+ Words stay in their original source. <!--md-layout:v1 align=justify hyphens=auto hanging=on-->
162
+ ```
163
+
164
+ `align=justify` alone keeps native justification. With `hyphens=auto`, the browser adapter chooses a paragraph-wide
165
+ plan using the same pinned Pretext analysis, shaping and actual-font authority as picture wrapping. It prefers
166
+ whole words, bounds ordinary spaces to 60–180% of their natural advance, permits at most two consecutive
167
+ discretionary endings, and starts the final line with a whole word. A short final line costs more; the final line
168
+ is never stretched. Authored compound hyphens are ordinary break opportunities, distinct from discretionary ones.
169
+ In very narrow measures, bounded spaces can leave some ragged edges. There is no paragraph-length switch.
170
+
171
+ Automatic cuts keep at least three graphemes on each side, with at most one automatic split per word. ASCII words
172
+ under `en` and `en-US` use the included Kuiken/Plain TeX pattern data and exceptions. Other English regions,
173
+ non-ASCII words and other admitted languages ask the browser's offline hyphenator for legal positions. That path
174
+ requires native `hyphenate-limit-chars` support; otherwise the paragraph stays native. Available dictionaries and
175
+ their divisions depend on the host. The included American patterns are a deliberate alternative to native data,
176
+ not a promise of identical opportunities in every browser. No language or font data is downloaded.
177
+
178
+ The projection displays a hyphen-minus from the affected text face, measured by Pretext as part of the whole
179
+ prefix. It never adds a source hyphen. Explicit authored `hyphenate-character` stays native. With `hanging=on`,
180
+ measured punctuation extends only into available unclipped margins, by at most half an em. Refinement adds no
181
+ letter tracking; authored supported letter spacing remains part of its shaping contract.
182
+
183
+ ```js
184
+ import {createTypography} from 'rapier-markdown-kit/typography';
185
+
186
+ const refinement = createTypography();
187
+ await document.fonts.ready;
188
+ function update() {
189
+ if (refinement.apply(root).pending) requestAnimationFrame(update);
190
+ }
191
+ update();
192
+ for (const event of ['copy', 'cut']) document.addEventListener(event, () => {
193
+ if (refinement.clearSelection(root)) requestAnimationFrame(update);
194
+ }, true);
195
+ // On a width change, call update() again. Source/style/font changes are revalidated.
196
+ // Before native editing or consuming this disposable DOM projection:
197
+ refinement.clear(root);
198
+ // When retiring the root:
199
+ refinement.retire(root);
200
+ ```
201
+
202
+ `apply()` works on visible paragraphs and yields at completed preparation phases or word batches after an 8 ms
203
+ allowance. A single native shaping call can exceed that allowance; it is not a hard execution deadline. Pending
204
+ paragraphs remain native and readable. Each continuation revalidates source, style and font authority, and chooses
205
+ lines at the current width only after all required metrics finish. `{all:true}` performs the same complete work
206
+ synchronously for explicit capture or a print host with an established content width. Width changes reuse current measured geometry, and unchanged break
207
+ topology reuses projection nodes. Bind font loading, completion and failure to `invalidateFonts(document)` and
208
+ schedule `apply()`; a changed font generation removes stale projections before new preparation.
209
+
210
+ The adapter retains original text, semantic elements and listeners, backward selections, hard breaks, protected
211
+ code/nowrap atoms, NBSP, SHY and Unicode source coordinates. `clearSelection()` restores selected inline trees
212
+ before native clipboard capture; schedule refinement afterward as above. The editor and complete exported pages
213
+ bind this lifecycle themselves. Active editing/composition stays native. Unsupported
214
+ scripts, direction, shaping controls, initials and picture-flow paragraphs also stay native. The current adapter
215
+ admits horizontal LTR Latin, Greek, Cyrillic and common/inherited characters on Chrome/Chromium; native font
216
+ coverage and language support still apply. A font-family name is never a substitute for a loaded actual face.
130
217
 
131
- Every convention is plain Markdown or an HTML comment other readers ignore. Laying lines out as Rapier does takes
132
- the host's font metrics (below).
218
+ Complete exported pages carry the same single module graph, CSP nonce, offline language data and font lifecycle.
219
+ Their readiness promise completes after the visible refinement queue; scrolling refines newly visible paragraphs.
220
+ Before print, an authored paper size supplies the fixed content width and all paragraphs finish synchronously.
221
+ Automatic-paper browser printing clears the projection and uses native CSS: `beforeprint` may still report the
222
+ screen viewport rather than the selected paper. Scoped fragments and `scripts:false` output also retain native CSS. The American data's separate permissive notice and exact provenance accompany the kit;
223
+ Pretext retains its own complete notices. These vendor notices are not replaced by the kit's MIT grant.
224
+
225
+ ## One inline typography form
226
+
227
+ ```md
228
+ Plain prose needs no typography markup.
229
+
230
+ A <span style="font-size:13.5pt;color:#175dc4">precise **phrase**</span>.
231
+
232
+ ## <span style="font-size:9.25pt;font-family:serif">Still a semantic H2</span>
233
+
234
+ <span style="font-size:18px;color:#175dc4;font-family:&quot;Coastal Text&quot;">An offline custom run</span>
235
+
236
+ [font:Coastal Text]: data:font/woff2;base64,<font-bytes> '{"family":"Coastal Text","sourceId":"f0123456789abcdef01234567"}'
237
+ ```
238
+
239
+ The font bytes and identity in that last example are placeholders; a host derives identity from admitted bytes.
240
+ A family name alone is not a portable font. Default prose needs no font reference. Custom faces keep the existing
241
+ font-data authority, including a name that collides with a factory face.
242
+
243
+ `typography.normalize({size, color, family})` validates a style. `typography.wrap(style, inlineMarkdown)` writes one
244
+ inline span; split block selections and keep their `#`, list markers and separators outside. `typography.parseStyle`
245
+ returns only admitted facts. `typography.install(md)` installs that interpretation on the caller's markdown-it
246
+ parser, and `typographySourceRanges(source, md)` reports original UTF-16 wrapper ranges through that parser.
247
+ These calls neither mutate the source nor load resources. The installer also reads already-authored colour and
248
+ font comments into the same facts; an ordinary open/save must preserve their bytes, not rewrite them as spans.
249
+
250
+ Sizes accept positive decimal `pt`/`px`, at most 12 fractional digits, up to 1638pt/2184px; 96px = 72pt. New values
251
+ use lowercase units and omit insignificant zeros. Colours become lowercase six-digit RGB hex; opaque integer
252
+ RGB CSSOM values are also read. Families are one validated name, an unquoted CSS generic, or an existing preset
253
+ stack. `sans`, `serif`, `mono`, `system`, `hand` are reserved choice aliases, not quoted custom identities. Styles
254
+ are bounded to 4096 characters, 32 declarations and 64 paired levels. Invalid declarations and unknown properties
255
+ are ignored; invalid/unclosed style strings admit no facts. Inner spans override individual properties. An editor
256
+ clearing a selection removes that property's overrides and preserves the surrounding words' styles.
257
+
258
+ Keep each opener with its first words and each closer with its last words. Soft/hard breaks may stay inside one paragraph; blank lines cannot. An opener alone on a line can become a raw HTML block.
259
+ With inline HTML enabled, the new form preserves words, emphasis,
260
+ links and heading rank; GitHub strips the styles, and HTML-disabled readers may show the tags. Rapier's **Copy
261
+ markdown without typography** removes recognized wrappers while retaining semantic Markdown and literal code.
262
+ Its separate Pandoc/Quarto copy option writes bracketed spans deliberately; Pandoc HTML carries the CSS, but
263
+ ordinary LaTeX and native DOCX styling do not follow automatically.
264
+
265
+ Rapier's own Word adapter writes native run properties, rounding to the nearest half-point with positive ties up
266
+ and refusing results outside 1–3276 half-points. The unchanged DOCX source carrier keeps original decimals and units;
267
+ native import reports represented direct-run/character-style sizes without repeating document or paragraph defaults
268
+ as inline marks. It does not reproduce an arbitrary Word style sheet. HTML and print/PDF retain the same admitted facts at the default reference
269
+ unit, independent of editor zoom. No mapping claims identical glyphs or line breaks across applications.
270
+
271
+ See the original [typography guidance](https://github.com/jackskip22/rapier/blob/main/docs/markdown-standard.md#inline-typography),
272
+ [CommonMark raw HTML](https://spec.commonmark.org/0.31.2/#raw-html), [CSS absolute units](https://www.w3.org/TR/css-values-4/#absolute-lengths),
273
+ [Pandoc spans](https://pandoc.org/MANUAL.html#extension-bracketed_spans) and [Quarto spans](https://quarto.org/docs/authoring/markdown-basics.html#divs-and-spans).
274
+ Those specifications retain their own licences.
133
275
 
134
276
  ## Render with the reference style
135
277
 
@@ -142,19 +284,19 @@ are named font families with system fallbacks; no font or external asset is fetc
142
284
  The example below renders trusted Markdown with `markdown-it` and the kit's readers: run
143
285
  `npm install rapier-markdown-kit markdown-it@15`, save it as `render.mjs`, run `node render.mjs` and open
144
286
  `styled.html`, which uses only `style.css`. It shows core Markdown, GFM tables, an aligned paragraph, a sized
145
- picture, a named colour and a page break; tasks, footnotes and diagrams are the caller's plugins. The full markup
287
+ picture, a precise combined run, a semantic heading and a page break; tasks, footnotes and diagrams are the caller's plugins. The full markup
146
288
  contract is [Reference style](https://github.com/jackskip22/rapier/blob/main/docs/markdown-standard.md#reference-style).
147
289
 
148
290
  <!-- reference-style-example -->
149
291
  ```js
150
292
  import MarkdownIt from 'markdown-it';
151
293
  import {parseLayout, formatLayout, imageStyle} from 'rapier-markdown-kit/layout';
152
- import {TEXT_COLOR_NAMES, formatColorRun, formatPageBreak, parseColorOpen, isColorClose, isPageBreakBlock} from 'rapier-markdown-kit/marks';
294
+ import {TEXT_COLOR_NAMES, typography, formatColorRun, formatPageBreak, isPageBreakBlock} from 'rapier-markdown-kit/marks';
153
295
  import {installMarkdownImages} from 'rapier-markdown-kit/assets';
154
296
  import {readFile, writeFile} from 'node:fs/promises';
155
297
  import {pathToFileURL} from 'node:url';
156
298
 
157
- const md = installMarkdownImages(new MarkdownIt({html: true, linkify: true}));
299
+ const md = typography.install(installMarkdownImages(new MarkdownIt({html: true, linkify: true})));
158
300
  md.renderer.rules.table_open = (tokens, index, options, env, renderer) =>
159
301
  '<div class="table-scroll-wrap">\n' + renderer.renderToken(tokens, index, options);
160
302
  md.renderer.rules.table_close = (tokens, index, options, env, renderer) =>
@@ -175,29 +317,6 @@ md.core.ruler.after('inline', 'reference-layout', state => {
175
317
  }
176
318
  }
177
319
  });
178
- md.core.ruler.after('inline', 'reference-colour', state => {
179
- for (const inline of state.tokens) {
180
- if (inline.type !== 'inline') continue;
181
- let open = null, depth = 0;
182
- for (const token of inline.children) {
183
- if (token.type !== 'html_inline') {
184
- depth += token.nesting;
185
- if (open && depth < open.depth) open = null;
186
- continue;
187
- }
188
- if (isColorClose(token.content) && open && depth === open.depth) {
189
- Object.assign(open.token, {type: 'reference_colour_open', tag: 'span', nesting: 1, content: ''});
190
- open.token.attrSet('data-md-color', open.hex);
191
- open.token.attrSet('style', '--md-color:' + open.hex);
192
- Object.assign(token, {type: 'reference_colour_close', tag: 'span', nesting: -1, content: ''});
193
- open = null;
194
- } else {
195
- const hex = parseColorOpen(token.content);
196
- if (hex && !open) open = {token, hex, depth};
197
- }
198
- }
199
- }
200
- });
201
320
  const htmlBlock = md.renderer.rules.html_block;
202
321
  md.renderer.rules.html_block = (tokens, index, options, env, renderer) =>
203
322
  isPageBreakBlock(tokens[index].content)
@@ -213,7 +332,7 @@ export const exampleSource = [
213
332
  '',
214
333
  'One paragraph with **weight**, *emphasis*, `inline code` and a [link](https://example.com).',
215
334
  '',
216
- '## Lists and tables',
335
+ '## ' + typography.wrap({size: '21.5pt', family: 'serif'}, 'Lists and tables'),
217
336
  '',
218
337
  '1. First numbered item',
219
338
  '2. Second numbered item',
@@ -234,7 +353,9 @@ export const exampleSource = [
234
353
  '',
235
354
  'A centred paragraph.' + formatLayout({align: 'center'}),
236
355
  '',
237
- 'A ' + formatColorRun(TEXT_COLOR_NAMES.blue, 'blue phrase') + ' keeps its named colour.',
356
+ 'A ' + formatColorRun(TEXT_COLOR_NAMES.blue, 'blue phrase') + ' keeps its colour.',
357
+ '',
358
+ 'And ' + typography.wrap({size: '13.5pt', color: '#175dc4', family: 'serif'}, '**precise words**') + ' keep their emphasis.',
238
359
  '',
239
360
  '![A square](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aDAkAAAAASUVORK5CYII=)' + formatLayout({width: 45, align: 'center'}),
240
361
  '',
@@ -257,12 +378,12 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
257
378
 
258
379
  ## Example
259
380
 
260
- Save this as `example.mjs` in the folder you installed into and run `node example.mjs`. It prints
261
- `lines: 4 height: 80`.
381
+ Save this as `example.mjs` in the folder you installed into and run `node example.mjs`. It validates
382
+ the fragments' source custody and prints the resulting line count and height for its supplied measurements.
262
383
 
263
384
  ```js
264
385
  import {parseLayout} from 'rapier-markdown-kit/layout';
265
- import {parseProfile, pictureSlices, prepareRun, flowLines, prepareRichInline} from 'rapier-markdown-kit/model';
386
+ import {parseProfile, pictureSlices, createPreparationScope, flowLines, mapFragment} from 'rapier-markdown-kit/model';
266
387
  const {loadDocuments, installMeasurer} = await import(new URL(
267
388
  './dist/kit/conformance/measurer.mjs', import.meta.resolve('rapier-markdown-kit/package.json')));
268
389
 
@@ -281,27 +402,82 @@ const obstacles = pictureSlices(profile, /* x */ 0, /* y */ 0, /* width */ 120,
281
402
 
282
403
  // 3. Plan the lines of a paragraph around those obstacles.
283
404
  const text = 'This anchor paragraph carries enough ordinary prose', font = 'normal 400 17.6px Geist, system-ui, sans-serif';
284
- const run = prepareRun(text, font);
285
- const flow = prepareRichInline([{text: run.raw, font, letterSpacing: 0}]);
405
+ const preparation = createPreparationScope(), paragraph = {};
406
+ const flow = preparation.prepare(paragraph, [{text, font, letterSpacing: 0}], {language: 'en'});
407
+ if (!flow) throw new Error('The host could not prepare this paragraph');
286
408
  const plan = flowLines(flow, /* column width */ 300, /* top */ 0, obstacles, /* line height */ 20, /* min slot */ 40);
287
409
  // Each line carries its own x, y and width, and the text fragments laid into it.
288
- console.log('lines:', plan.lines.length, 'height:', plan.height); // lines: 4 height: 80
410
+ if (!plan || !preparation.current(flow)) throw new Error('No current plan');
411
+ let end = 0;
412
+ for (const line of plan.lines) for (const fragment of line.fragments) {
413
+ const mapped = mapFragment(text, fragment);
414
+ if (!mapped || mapped.start < end || text.slice(end, mapped.start).trim()) throw new Error('Unmapped source');
415
+ end = mapped.end;
416
+ }
417
+ if (text.slice(end).trim()) throw new Error('Unmapped source tail');
418
+ console.log('lines:', plan.lines.length, 'height:', plan.height);
289
419
  ```
290
420
 
291
- A heading, which the style pack balances, takes its font size as an eighth argument
292
- (`flowLines(flow, width, top, obstacles, lineHeight, minSlot, direction, fontSize)`): its lines come out as
293
- Chromium lays `text-wrap: balance`, and greedy wherever an obstacle narrows a line. The block's `text-align` is the ninth
294
- argument (`'start'` by default). A picture moves a block's words only where its outline meets them: when no line of the
421
+ The block's `text-align` is the ninth argument (`'start'` by default). Native balanced headings retain native
422
+ wrapping: the current rich line-range API does not identify every legal balancing candidate. A picture moves a
423
+ block's words only where its outline meets them: when no line of the
295
424
  block, laid and aligned in the whole column as with no picture, meets an obstacle, the plan is that layout (`x` 0, the
296
425
  column's width); otherwise every line flows around the obstacles.
297
426
 
298
427
  ## What the host supplies
299
428
 
300
- `prepareRun` and `prepareRichInline` measure text the way a browser does: they need a global
429
+ Rich preparation measures text through one pinned Pretext graph. It needs a global
301
430
  `OffscreenCanvas` (or a DOM `document`) whose `getContext('2d')` has a settable `font` and a
302
431
  `measureText(text)` returning `{width}`. A browser has one already; in Node, put those two members
303
- in front of whatever shaper you have. The example replays recorded measurements instead, and a
304
- query they never recorded falls back to summing single characters, without kerning or shaping.
432
+ in front of the host's actual shaper and admit its fonts before preparation. The example replays historical
433
+ measurements instead; an unrecorded query falls back to summing single characters. That fallback demonstrates API
434
+ execution only and does not qualify kerning, ligatures, complex scripts or actual browser line selection.
435
+
436
+ Create one `createPreparationScope(document)` for a host document in the same measurement realm. Call
437
+ `scope.prepare(owner, items, {whiteSpace, wordBreak, language})` with a stable paragraph owner and original rich
438
+ items; omitted document support is available for declared Node measurement hosts. Text items accept
439
+ `{text, font, letterSpacing?, break?: 'normal' | 'never', extraWidth?}`; box items accept `{width}`. Paragraph
440
+ `whiteSpace` is `normal` or `pre-wrap`, and `wordBreak` is `normal` or `keep-all`. `language` is the content language,
441
+ not the interface locale. Mixed run languages require native fallback. The scope retains owners weakly and reuses
442
+ preparation across widths. Changes to exact text, item shaping settings or language require a new preparation.
443
+
444
+ The returned handle is opaque. Pass it to `flowLines`, or retain the original ranges from
445
+ `layoutNextRichInlineLineRange` for `materializeRichInlineLineRange`. Materialized fragments give `itemIndex`,
446
+ `sourceStart` and `sourceEnd` in UTF-16 offsets of that item's **original** text; `mapFragment(originalText, fragment)`
447
+ provides the shared display-to-source mapping. `gapItemIndex` names the source style for a collapsed gap. Never decode
448
+ range cursors as old segment/grapheme fields, resegment text to interpret them, JSON-serialize or deep-freeze the
449
+ handle. Storage segments and emergency overflow advances are not a list of legal breaks.
450
+
451
+ `scope.current(handle)` checks the shared actual-font authority before projection. Bind font loading, completion and
452
+ failure to `scope.invalidateFonts()` and schedule reflow. Actual face identity, descriptors and status detect
453
+ same-name replacement and invalidate the vendor's metric cache as well as prepared handles. Width changes do
454
+ neither. Call `scope.forget(owner)` when a block stops participating. One imported graph owns the realm's vendor
455
+ metrics; another bundle or copied vendor tree cannot share that authority. Call `scope.retire()` when replacing or
456
+ retiring a projection scope, so both its handles and the shared vendor metrics are retired. This retains the
457
+ document's known custom-family history, allowing a missing previously declared face to be refused instead of
458
+ silently measured through a fallback. Only the owner replacing the whole document calls
459
+ `scope.retire({documentChanged: true})`, which also resets that document's expected-family history.
460
+
461
+ The browser adapter calls `supportsRichStyle(style, language)` and refuses settings the pinned API cannot represent.
462
+ The API has no independent controls for kerning, OpenType features, variation settings, optical sizing, word spacing
463
+ or mixed item language; a matching font-family string alone is not proof of matching actual font data. Unsupported
464
+ settings, unavailable or failed faces, complex objects, authored `br`, preserved-whitespace DOM paragraphs and
465
+ balanced headings keep selectable native DOM. Direct prepared-text hosts can use `pre-wrap` with original CRLF and
466
+ hard breaks. The reference combines browser-derived break tables and Canvas metrics with JavaScript flow. The DOM
467
+ adapter admits Chrome/Chromium profiles; Firefox, WebKit and Chrome on iOS retain native DOM. It does not invoke live
468
+ DOM line selection or promise identical pixels in other engines.
469
+ Editor soft-break source tokens and generated-content shells stay native. The semantic export resolves verified
470
+ soft-break tokens to ordinary rendered spaces through its existing owner before wrapping; its source carrier keeps
471
+ the original Markdown unchanged.
472
+ Ordinary picture flow keeps its greedy policy instead of the reference sheet's generic `pretty` preference;
473
+ explicit authored inline `text-wrap:pretty` retains native wrapping. No native pretty optimization is claimed.
474
+ A lone Latin, Greek or Cyrillic word prefix, including an authored no-break-space group, may defer to a later
475
+ wider picture-side slot on the same row when the public engine output fits the entire group there. Decorated
476
+ atoms, mixed-item words, punctuation, discretionary breaks and next-row placement retain the engine policy.
477
+
478
+ `md-layout:v1` remains semantic and engine-independent. No plan or font-dependent pixel carrier is saved. Offline
479
+ complete pages load their admitted embedded faces before enhanced wrapping; fragments and `scripts:false` pages
480
+ retain native wrapping. Fonts and codecs remain explicit host capabilities as described above.
305
481
 
306
482
  `alphaProfile(image)` samples a decoded `<img>` through a canvas, so it needs a browser. Without
307
483
  one, read the saved descriptor with `parseProfile`, or sample the picture with your own image
@@ -317,15 +493,19 @@ node node_modules/rapier-markdown-kit/dist/kit/conformance/run.mjs
317
493
  ```
318
494
 
319
495
  Thirteen small documents, each with the line boxes Rapier's own export produced for it. **Preservation**: every
320
- layout comment round-trips exactly (12 of 12 that carry one). **Presentation**: how closely the planned lines match
321
- (3 agree within 2px, 10 are close with a named cause, none differ; `dist/kit/conformance/README.md`).
496
+ layout comment round-trips exactly (12 of 12 that carry one). **Presentation** compares the plan with historical
497
+ measurements and reports real-trace hits separately from character-sum misses (`dist/kit/conformance/README.md`).
498
+ Its historical line counts are not a current-engine or font-shaping acceptance gate.
322
499
  `--impl path/to/module.mjs` runs another implementation against the same documents; `--tolerance 2` sets the pixel
323
500
  tolerance.
324
501
 
325
502
  ## Licence
326
503
 
327
- MIT, the full text in `LICENSE`; Rapier-owned modules and the stylesheet keep their MIT line. Pretext's licence, notice and
328
- pinned source inventory are in `dist/agent/vendor/pretext/`. The history projection uses the MIT diff core;
504
+ MIT, the full text in `LICENSE`; Rapier-owned modules and the stylesheet keep their MIT line. Pretext's complete licence,
505
+ notice and pinned source inventory are in `dist/agent/vendor/pretext/`. The selected upstream is unreleased main
506
+ `bac1774c0f8b7a92e44b9da43b6d5443d55d0d9b`, despite its package version `0.0.9`. Its upstream licence includes
507
+ Chromium/WebKit BSD, Unicode and ICU notices for derived code and generated data; those notices travel with the kit
508
+ and the exported wrapping runtime. The history projection uses the MIT diff core;
329
509
  its full grant is in `dist/packages/rapier-diff/LICENSE`. The Rapier editor is AGPL-3.0-only and
330
510
  none of it is in this package. The kit carries Rapier's release number, written from the one
331
511
  value the editor's release reads.
@@ -403,6 +583,8 @@ no accessors, sparse arrays, cycles, undefined, negative zero or unpaired surrog
403
583
  Readers reject unknown envelope/start/head fields, bad checksums, invalid records,
404
584
  invalid source boundaries, a replay/root mismatch, or a head different from the
405
585
  actual document. `historyEnvelope` additionally proves the editor's Undo/Redo branch.
586
+ Its optional third and fourth arguments are the expected text and metadata, as in
587
+ `readLedger`; installing history can prove both source and navigation in one pass.
406
588
 
407
589
  **Roots are path-dependent.** `textRoot(text)` mints the existing
408
590
  `chars:fnv-base36:adler-base36` initial root. Each splice advances it with
@@ -468,6 +650,15 @@ The comparison page's `#rapier-base` is independent: `text/markdown`, with
468
650
  display reference. It is not reconstructed from the optional ledger and never
469
651
  replaces current source. `rapier-html --compare original.md revised.md` packages both texts.
470
652
 
653
+ A comparison may also carry `data-fonts`, an HTML-attribute-escaped JSON string of
654
+ canonical embedded `[font:]` reference definitions. The admitted base record exposes
655
+ this as `fonts`. These resources belong to the exact comparison, outside its text,
656
+ digest and revision; they never become a source edit or ledger record. Font preparation
657
+ validates their actual bytes and reads font usage only from the original comparison.
658
+ Existing source-owned assets remain authoritative. A missing personal-library font is
659
+ embedded here when available, otherwise export refuses. Reopening and resaving need
660
+ no library or network, and never substitute the current device's default font.
661
+
471
662
  ## Merging copies without a server
472
663
 
473
664
  `merge(first, second)` locates a common checkpoint with the same root **and exact
@@ -0,0 +1,14 @@
1
+ American English hyphenation patterns (hyph-en-us)
2
+ Copyright (C) 1990, 2004, 2005 Gerard D.C. Kuiken
3
+
4
+ Copying and distribution of this file, with or without modification,
5
+ are permitted in any medium without royalty provided the copyright
6
+ notice and this notice are preserved.
7
+
8
+ Source: hyphenation/tex-hyphen, commit 5684c0f51c0b81133db2efbe60a408b4155a3ff5
9
+ hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-en-us.tex
10
+ The accompanying .pat.txt and .hyp.txt inputs are unchanged. patterns.js is a
11
+ mechanical numeric representation generated by tools/compile-hyphens.mjs.
12
+ These data include the original Plain TeX patterns and the additional patterns
13
+ and exceptions described in the upstream file. No hyphenation implementation
14
+ from that repository or Justice is included.
@@ -0,0 +1,33 @@
1
+ {
2
+ "repository": "https://github.com/hyphenation/tex-hyphen",
3
+ "commit": "5684c0f51c0b81133db2efbe60a408b4155a3ff5",
4
+ "upstreamDirectory": "hyph-utf8/tex/generic/hyph-utf8/patterns/txt/",
5
+ "licenseSource": "hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-en-us.tex",
6
+ "licenseSourceGitBlob": "bd9b757d89074e3e11b590311ce13ba6667544aa",
7
+ "license": "LicenseRef-Kuiken",
8
+ "licenseSha256": "0cf036759a4fb63a3d0bdb919f0dacbe137e225cb6f8f268d2928e368a8c1731",
9
+ "inputs": [
10
+ {
11
+ "path": "hyph-en-us.pat.txt",
12
+ "sourceGitBlob": "f69e835d9dacea688fdc51956ea9243b9d042e51",
13
+ "bytes": 31489,
14
+ "sha256": "0f57318b878b132547ae92db39a6e1d1cf2a05d9008874955d6ecb910007a463"
15
+ },
16
+ {
17
+ "path": "hyph-en-us.hyp.txt",
18
+ "sourceGitBlob": "b87163d4425b7b5b7e2d9f170e486131988706c4",
19
+ "bytes": 169,
20
+ "sha256": "5e785c27e151f878c312991d937b5582615b77737ab30bac413d5967d6aa21da"
21
+ }
22
+ ],
23
+ "generator": "tools/compile-hyphens.mjs",
24
+ "generatorSha256": "b5758c3ceac76ac39a8ba0bf2f45bc88854e4779d4f443a9c0957694305571dd",
25
+ "policy": "Complete pinned data in a static prefix trie, with original MIT Rapier traversal and matcher; en and en-US ASCII words. Other regional English, non-ASCII words and other languages use the native offline hyphenator.",
26
+ "files": [
27
+ {
28
+ "output": "patterns.js",
29
+ "outputBytes": 46360,
30
+ "outputSha256": "e0360f1552704657d79a670f1e2e0b263f6eea42e209866b521cd8ec2aff387d"
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,14 @@
1
+ as-so-ciate
2
+ as-so-ciates
3
+ dec-li-na-tion
4
+ oblig-a-tory
5
+ phil-an-thropic
6
+ present
7
+ presents
8
+ project
9
+ projects
10
+ reci-procity
11
+ re-cog-ni-zance
12
+ ref-or-ma-tion
13
+ ret-ri-bu-tion
14
+ ta-ble