rapier-markdown-kit 1.2.33 → 1.2.42

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 (45) hide show
  1. package/README.md +15 -9
  2. package/dist/agent/vendor/pretext/NOTICE +2 -2
  3. package/dist/agent/vendor/pretext/SOURCE.json +62 -78
  4. package/dist/agent/vendor/pretext/analysis.js +24 -6
  5. package/dist/agent/vendor/pretext/gecko-line-breaks.js +1 -1
  6. package/dist/agent/vendor/pretext/generated/engine-break-data.js +1 -1
  7. package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +1 -1
  8. package/dist/agent/vendor/pretext/graphemes.js +1 -1
  9. package/dist/agent/vendor/pretext/han-kerning.js +1 -1
  10. package/dist/agent/vendor/pretext/layout.js +14 -11
  11. package/dist/agent/vendor/pretext/line-break.js +170 -195
  12. package/dist/agent/vendor/pretext/line-breaks.js +1 -7
  13. package/dist/agent/vendor/pretext/line-text.js +2 -2
  14. package/dist/agent/vendor/pretext/measurement.js +454 -113
  15. package/dist/agent/vendor/pretext/prepare.js +164 -244
  16. package/dist/agent/vendor/pretext/rich-inline.js +57 -37
  17. package/dist/agent/will.mjs +73 -0
  18. package/dist/kit/ledger/data.mjs +57 -4
  19. package/dist/kit/ledger/format.mjs +114 -47
  20. package/dist/kit/ledger/hash.mjs +43 -2
  21. package/dist/kit/ledger/history.mjs +6 -0
  22. package/dist/kit/ledger/inverse-source.mjs +15 -2
  23. package/dist/kit/ledger/journal-records.mjs +32 -9
  24. package/dist/kit/ledger/merge.mjs +3 -17
  25. package/dist/kit/ledger/text-merge.mjs +28 -2
  26. package/dist/kit/rapier-ledger.mjs +2 -2
  27. package/dist/kit/render-markdown.mjs +170 -12
  28. package/dist/kit/render-print.mjs +3 -3
  29. package/dist/kit/render-sanitize.mjs +66 -23
  30. package/dist/kit/render.mjs +10 -10
  31. package/dist/kit/wiki.mjs +34 -0
  32. package/dist/kit/will.d.mts +3 -0
  33. package/dist/layout/hyphenation.mjs +6 -4
  34. package/dist/layout/line-plan.mjs +96 -0
  35. package/dist/layout/model.mjs +2 -1
  36. package/dist/layout/paragraph-fit.mjs +4 -4
  37. package/dist/layout/prepared-paragraph.mjs +19 -2
  38. package/dist/layout/typography.mjs +57 -63
  39. package/dist/spec/document-fonts.mjs +1 -0
  40. package/dist/spec/document-settings.mjs +1 -0
  41. package/dist/spec/html-blocks.mjs +10 -7
  42. package/dist/spec/md-assets.mjs +52 -12
  43. package/package.json +1 -1
  44. package/style.css +33 -23
  45. package/dist/agent/vendor/pretext/entry-geometry.js +0 -183
@@ -1,6 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {htmlPlaceholder, mountHtmlBlocks, materializeObjects, objectPosterLabel, htmlImageRecord, htmlLayoutEdit, htmlModeEdit, mergeHtmlObjectBlocks} from '../spec/html-blocks.mjs';
3
3
  import {finish, finishAsync, cloneTree, cleanTree, serializeTree, htmlParts, admitHtml, htmlBoundary, pause, check} from './render-work.mjs';
4
+ import {installWikiParser, wikiAddress} from './wiki.mjs';
4
5
  const RAPIER_TABLE_CAPTION_PREFIX_RE = /^(?:Table:|:) /;
5
6
  const RAPIER_PORTABLE_HIGHLIGHT_HEX = Object.freeze({
6
7
  green: '#35c57a', red: '#f05a62', blue: '#4f91f7', yellow: '#e6b91e', purple: '#9b6dea',
@@ -9,6 +10,51 @@ const _RAPIER_RTL_SCRIPT = /[\p{Script=Hebrew}\p{Script=Arabic}\p{Script=Syriac}
9
10
  function createMarkdownRenderer(runtime) {
10
11
  const {Node, _rapierPrefixPortableAnchors, RAPIER_HIGHLIGHT_COLOR_BY_MARKER, RAPIER_MARKDOWN_SPEC, RAPIER_RENDERED_HEADING_SELECTOR, _rapierApplyMarkdownSpec, _rapierChromeOwnsId, _rapierCodeHtml, _rapierDeriveDarkColor, _rapierDormantHeadings, _rapierEmbedAssetSource, _rapierHeadingSlugBase, _rapierNextHeadingSlug, _rapierHighlightAdmitted, _rapierInstallMarkdownMath, _rapierMarkdownPreview, _rapierPlainLayout, _rapierProviders, _rapierRemoteContent, _rapierRemoteImagePlaceholder, _rapierRemoteSubresourceOrigin, _rapierRenderedAnchorOriginalIds, _rapierSourceCharEscaped, _rapierSplitOpeningFrontmatter, _rapierUiDiagram, _rapierUiMath, document, escapeRapierHtmlText, globalThis, sanitizeRapierHtml, window} = runtime;
11
12
  let md = runtime.md;
13
+ let wikiTimer = null, wikiAttempt = 0;
14
+ const wikiBusy = new WeakSet();
15
+ function scheduleWikiEmbeds() {
16
+ if (!globalThis.RapierPlatform || !document || wikiTimer !== null) return;
17
+ wikiTimer = setTimeout(async () => {
18
+ wikiTimer = null;
19
+ const original = globalThis.RapierOriginalFolderCore?.currentOriginal();
20
+ if (!original) { if (++wikiAttempt < 8) scheduleWikiEmbeds(); return; }
21
+ wikiAttempt = 0;
22
+ for (const node of [...document.querySelectorAll('#editor-blocks .rapier-source-token--wiki-embed')].slice(0, 32)) {
23
+ if (wikiBusy.has(node) || node.hasAttribute('data-rapier-wiki-ready')) continue;
24
+ wikiBusy.add(node);
25
+ try {
26
+ const link = node.querySelector('a'), href = link?.getAttribute('href');
27
+ if (!href) continue;
28
+ const before = node.innerHTML;
29
+ const embedded = await original.embed(href);
30
+ if (!node.isConnected || node.innerHTML !== before || globalThis.RapierOriginalFolderCore?.currentOriginal() !== original) continue;
31
+ const holder = _rapierRenderSemanticRoot(embedded.text, {docKind: 'markdown'}, {wikiEmbedding: true});
32
+ let followed = 0;
33
+ for (const anchor of holder.querySelectorAll('a[href]')) {
34
+ const address = anchor.getAttribute('href');
35
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(address)) continue;
36
+ try {
37
+ if (++followed > 64) throw new Error('link limit');
38
+ anchor.setAttribute('href', await original.addressFrom(embedded.displayPath, address));
39
+ } catch (_) { anchor.replaceWith(...anchor.childNodes); }
40
+ }
41
+ if (!node.isConnected || node.innerHTML !== before || globalThis.RapierOriginalFolderCore?.currentOriginal() !== original) continue;
42
+ for (const image of holder.querySelectorAll('img,[data-rapier-remote-src]'))
43
+ image.replaceWith(document.createTextNode(image.getAttribute('alt') || image.textContent || ''));
44
+ for (const block of [...holder.querySelectorAll('p,h1,h2,h3,h4,h5,h6,div,pre,blockquote,ul,ol,li,table,tr,td,th')].reverse())
45
+ block.replaceWith(...block.childNodes, document.createElement('br'));
46
+ node.replaceChildren(...holder.childNodes);
47
+ node.setAttribute('data-rapier-visible', encodeURIComponent(node.textContent));
48
+ node.setAttribute('data-rapier-wiki-ready', '');
49
+ node.contentEditable = 'false';
50
+ } catch (_) { }
51
+ finally { wikiBusy.delete(node); }
52
+ }
53
+ }, 125);
54
+ }
55
+ window?.addEventListener?.('rapier:original-file-ready', scheduleWikiEmbeds);
56
+ const wikiMode = options => options.wikiEmbedding ? 'links' : options.wikiLinks !== undefined ? options.wikiLinks
57
+ : globalThis.RapierOriginalFolderCore?.currentOriginal?.() ? 'label' : false;
12
58
  function initMarkdownIt() {
13
59
  if (typeof window.markdownit !== 'function') throw new Error('markdownit');
14
60
  if (typeof _rapierMarkdownPreview === 'function') globalThis.rapierRenderPreview = _rapierMarkdownPreview;
@@ -82,6 +128,21 @@ function initMarkdownIt() {
82
128
  const sourceToken = (source, visible, className) =>
83
129
  '<span class="rapier-source-token ' + className + '" data-rapier-source="' +
84
130
  encodeURIComponent(source) + '" data-rapier-visible="' + encodeURIComponent(visible) + '">' + md.utils.escapeHtml(visible) + '</span>';
131
+ installWikiParser(md, (token, env) => {
132
+ const {target, label, embed} = token.meta, href = wikiAddress(target), esc = md.utils.escapeHtml;
133
+ if (env.rapierWikiLinks === 'label') return esc(label);
134
+ if (embed && /\.(?:png|jpe?g|gif|webp|jxl)(?:#.*)?$/i.test(target)) {
135
+ if (!env.rapierWikiEmbedding && globalThis.RapierPlatform?.files.relativeCandidate?.(href) && globalThis.RapierEmbeddedImages?.imageHtml)
136
+ return globalThis.RapierEmbeddedImages.imageHtml(null, label, '', null, '', label, token.content, href, env.rapierImagePresentation !== false);
137
+ return '<span data-rapier-remote-src="' + esc(href) + '" data-rapier-remote-alt="' + esc(label) +
138
+ '" data-rapier-image-source="' + esc(encodeURIComponent(token.content)) + '">' + esc(label) + '</span>';
139
+ }
140
+ const note = embed && !env.rapierWikiEmbedding;
141
+ if (note) scheduleWikiEmbeds();
142
+ return '<span class="rapier-source-token rapier-source-token--wiki' + (note ? '-embed' : '') +
143
+ '" data-rapier-source="' + esc(encodeURIComponent(token.content)) + '" data-rapier-visible="' + esc(encodeURIComponent(label)) +
144
+ '"><a href="' + esc(href) + '">' + esc(label) + '</a></span>';
145
+ });
85
146
  md.renderer.rules.emoji = (tokens, idx) => {
86
147
  const token = tokens[idx];
87
148
  const source = token.markup ? ':' + token.markup + ':' : token.content;
@@ -114,7 +175,9 @@ function initMarkdownIt() {
114
175
 
115
176
  md.renderer.rules.image = (tokens, idx, opts, env, self) => {
116
177
  const token = tokens[idx];
117
- const src = token.attrGet('src') || '';
178
+ const written = token.attrGet('src') || '';
179
+ const hosted = globalThis.RapierEmbedPictureBase?.(written) ?? null;
180
+ const src = hosted || written;
118
181
  const title = token.attrGet('title') || '';
119
182
  const rawAlt = String(token.content ?? token.attrGet('alt') ?? '');
120
183
  const size = _rapierImageAltSize(rawAlt);
@@ -140,7 +203,7 @@ function initMarkdownIt() {
140
203
  + (token.meta?.mdImage?.source ? ' data-rapier-image-source="' + escape(encodeURIComponent(token.meta.mdImage.source)) + '"' : '')
141
204
  + '>' + escape(alt || src) + '</span>';
142
205
  }
143
- if (!env.rapierPage && !_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
206
+ if (!hosted && !env.rapierPage && !_rapierRemoteContent.allowed && _rapierRemoteSubresourceOrigin(src)) {
144
207
  const html = _rapierRemoteImagePlaceholder(src, alt, title, size, layout, rawAlt);
145
208
  return html.replace('<span ', '<span data-rapier-markdown-image="" ' +
146
209
  (token.meta?.mdImage?.source ? 'data-rapier-image-source="' + md.utils.escapeHtml(encodeURIComponent(token.meta.mdImage.source)) + '" ' : ''));
@@ -277,6 +340,7 @@ function initMarkdownIt() {
277
340
  };
278
341
 
279
342
  const _fenceDefault = md.renderer.rules.fence;
343
+ let _mathPromptedOnce = false;
280
344
  {
281
345
  let _diagramPromptedOnce = false;
282
346
  md.renderer.rules.fence = function (tokens, idx, opts, env, self) {
@@ -303,8 +367,16 @@ function initMarkdownIt() {
303
367
  }
304
368
  const provider = env.rapierProviders === false ? null : _rapierProviders.mermaid;
305
369
  const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true;
306
- const ready = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
307
- if (!ready && !provider?.request?.() && env.rapierProviders !== false && !_diagramPromptedOnce) {
370
+ const diagramReady = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
371
+ const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
372
+ const mathNeeded = provider?.requiresMath?.(src) === true;
373
+ const mathReady = !mathNeeded || mathProvider?.status === 'ready';
374
+ if (!mathReady && !mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
375
+ _mathPromptedOnce = true;
376
+ try { _rapierUiMath.request(); } catch (_) {}
377
+ }
378
+ const ready = diagramReady && mathReady;
379
+ if (!diagramReady && !provider?.request?.() && env.rapierProviders !== false && !_diagramPromptedOnce) {
308
380
  _diagramPromptedOnce = true;
309
381
  try { _rapierUiDiagram.request(); } catch (_) {}
310
382
  }
@@ -338,7 +410,6 @@ function initMarkdownIt() {
338
410
  preserveEmphasisMarkup('strong_open', 'data-rapier-strong');
339
411
 
340
412
  {
341
- let _mathPromptedOnce = false;
342
413
  const _renderMath = (src, displayMode, env, authored) => {
343
414
  const marker = displayMode ? '$$' : '$';
344
415
  const source = authored || marker + src + marker, literal = md.utils.escapeHtml(source);
@@ -381,6 +452,10 @@ function initMarkdownIt() {
381
452
  }
382
453
  globalThis.RapierMarkdownLayout.installMarkdownLayout(md);
383
454
  globalThis.RapierImageAssets.configureParser(md, _rapierSplitOpeningFrontmatter);
455
+ const render = md.renderer.render;
456
+ md.renderer.render = function (tokens, options, env) {
457
+ return render.call(this, _rapierCloseOpenDetails(tokens), options, env);
458
+ };
384
459
  }
385
460
 
386
461
  function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
@@ -402,8 +477,21 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
402
477
 
403
478
  const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
404
479
  const env = {rapierImagePresentation: options.imagePresentation === true, ...(options.page ? {rapierPage: true} : {}), ...(options.providers === false ? {rapierProviders: false} : {})};
480
+ if (options.wikiEmbedding) env.rapierWikiEmbedding = true;
481
+ const wikiLinks = wikiMode(options);
482
+ if (wikiLinks) env.rapierWikiLinks = wikiLinks;
405
483
  if (options.mathEquations) env.rapierMathEquations = options.mathEquations;
406
- let rendered = md
484
+ const nesting = md && options.maxNesting > 0 ? options.maxNesting : 0;
485
+ let cutAt = -1, rendered;
486
+ if (nesting || md && options.closeDetails) {
487
+ const saved = md.options.maxNesting;
488
+ if (nesting) md.options.maxNesting = nesting;
489
+ try {
490
+ const tokens = md.parse(markdown, env);
491
+ if (nesting) cutAt = _rapierNestingCutAt(tokens, nesting);
492
+ rendered = md.renderer.render(options.closeDetails ? _rapierCloseOpenDetails(tokens) : tokens, md.options, env);
493
+ } finally { md.options.maxNesting = saved; }
494
+ } else rendered = md
407
495
  ? md.render(markdown, env)
408
496
  : '<pre><code>' + escapeRapierHtmlText(markdown) + '</code></pre>';
409
497
  if (md && env.footnotes && Array.isArray(env.footnotes.list) && env.footnotes.list.length) {
@@ -412,6 +500,7 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
412
500
  rendered = _relabelFootnoteHtml(rendered, labels);
413
501
  }
414
502
  root.innerHTML = sanitizeRapierHtml(rendered, options.page ? 'source' : 'export');
503
+ if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
415
504
  _rapierApplyBlockDirection(root);
416
505
 
417
506
  _rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false);
@@ -425,6 +514,15 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
425
514
  return root;
426
515
  }
427
516
 
517
+ function _rapierAppendNestingCut(root, markdown, cutAt) {
518
+ const note = root.ownerDocument.createElement('p'), pre = root.ownerDocument.createElement('pre'), code = root.ownerDocument.createElement('code');
519
+ note.setAttribute('data-rapier-nesting-cut', 'true');
520
+ note.textContent = 'Nesting is too deep to show here. The rest of the document follows as plain text.';
521
+ code.textContent = String(markdown).split('\n').slice(cutAt).join('\n');
522
+ pre.append(code);
523
+ root.append(note, pre);
524
+ }
525
+ const _rapierNestingCutAt = (tokens, nesting) => { const deepest = tokens.find(token => token.level >= nesting - 1 && token.map); return deepest ? deepest.map[0] + 1 : -1; };
428
526
  async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {}) {
429
527
  const work = options.work || options;
430
528
  check(work);
@@ -435,8 +533,16 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
435
533
  }
436
534
  const markdown = _rapierSplitOpeningFrontmatter(canonical).body;
437
535
  const fallbackEnv = options.page ? {rapierPage: true} : {};
438
- let parsed = options.parsed || (runtime.parseAsync ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv});
536
+ const wikiLinks = markdown.includes('[[') && wikiMode(options);
537
+ if (wikiLinks) fallbackEnv.rapierWikiLinks = wikiLinks;
538
+ const nesting = options.maxNesting > 0 ? options.maxNesting : 0, saved = md.options.maxNesting;
539
+ if (nesting) md.options.maxNesting = nesting;
540
+ let parsed;
541
+ try { parsed = !wikiLinks && options.parsed || (runtime.parseAsync && !wikiLinks && !nesting ? await runtime.parseAsync(markdown, options) : {tokens: md.parse(markdown, fallbackEnv), env: fallbackEnv}); }
542
+ finally { md.options.maxNesting = saved; }
439
543
  let {tokens, env} = parsed, labels = env.footnotes?.list?.map((item, index) => String(item?.label || ('inline-' + (index + 1))));
544
+ const cutAt = nesting ? _rapierNestingCutAt(tokens, nesting) : -1;
545
+ tokens = _rapierCloseOpenDetails(tokens);
440
546
  env = {...env, rapierImagePresentation: options.imagePresentation === true};
441
547
  await pause(work, .3);
442
548
  const inert = document.createElement('template').content.ownerDocument;
@@ -463,6 +569,7 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
463
569
  }
464
570
  }
465
571
  if (html) append();
572
+ if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
466
573
  parsed = tokens = env = labels = null;
467
574
  await finishAsync(_rapierSemanticDecorations(root), work);
468
575
  await pause(work, .6);
@@ -727,6 +834,49 @@ function _rapierImageSizeAttributes(size, marker, geometry, automatic = true) {
727
834
  return attributes + (style ? ' style="' + style + '"' : '');
728
835
  }
729
836
 
837
+ function _rapierCloseOpenDetails(tokens) {
838
+ const opens = /^<details[\t\n\f\r />]/i, closes = /^<\/details[\t\n\f\r ]*>$/i;
839
+ const tags = token => {
840
+ const found = [];
841
+ if (token.type === 'html_inline') found.push(token.content);
842
+ else if (token.type === 'html_block') {
843
+ let raw = '';
844
+ _rapierRawHtmlFragment(token.content, tag => {
845
+ if (raw) { if (new RegExp('^</' + raw + '[\\t\\n\\f\\r ]*>$', 'i').test(tag)) raw = ''; }
846
+ else {
847
+ const literal = /^<(script|style|textarea|title|xmp|iframe|noembed|noframes|plaintext)(?=[\t\n\f\r />])/i.exec(tag);
848
+ if (literal) raw = literal[1];
849
+ else found.push(tag);
850
+ }
851
+ return tag;
852
+ }, md);
853
+ }
854
+ else if (token.type === 'inline') for (const child of token.children || []) if (child.type === 'html_inline') found.push(child.content);
855
+ return found;
856
+ };
857
+ const after = new Map(), add = (index, count) => after.set(index, (after.get(index) || 0) + count);
858
+ const waiting = [];
859
+ for (let index = 0; index < tokens.length; index++) {
860
+ const token = tokens[index];
861
+ let depth = 0;
862
+ for (const tag of tags(token)) {
863
+ if (opens.test(tag)) { if (token.type === 'html_block') waiting.push(index); else depth++; }
864
+ else if (closes.test(tag)) { if (depth) depth--; else waiting.pop(); }
865
+ }
866
+ if (depth) add(index + 1 < tokens.length && tokens[index + 1].nesting === -1 ? index + 1 : index, depth);
867
+ }
868
+ for (const index of waiting) add(index, 1);
869
+ if (!after.size) return tokens;
870
+ const out = [];
871
+ tokens.forEach((token, index) => {
872
+ out.push(token);
873
+ const count = after.get(index);
874
+ if (count) out.push({type: 'html_block', tag: '', attrs: null, map: null, nesting: 0, level: token.level || 0, children: null,
875
+ content: '</details>'.repeat(count) + '\n', markup: '', info: '', meta: null, block: true, hidden: false});
876
+ });
877
+ return out;
878
+ }
879
+
730
880
  function _rapierRawHtmlFragment(source, rewrite, parser) {
731
881
  const state = new parser.inline.State(source, parser, {}, []);
732
882
  const htmlRule = parser.inline.ruler.__rules__.find(rule => rule.name === 'html_inline').fn;
@@ -792,7 +942,9 @@ function* _rapierAssignHeadingSlugsSteps(root, selector = RAPIER_RENDERED_HEADIN
792
942
  if (!headingSet.has(element) && element.id) used[element.id] = 0;
793
943
  }
794
944
  for (const heading of headings) { yield;
795
- heading.id = _rapierNextHeadingSlug(_rapierHeadingSlugBase(heading.textContent), used);
945
+ const base = _rapierHeadingSlugBase(heading.textContent);
946
+ const owned = typeof _rapierChromeOwnsId === 'function' && _rapierChromeOwnsId(base);
947
+ heading.id = _rapierNextHeadingSlug(owned ? 'heading-' + base : base, used);
796
948
  }
797
949
  }
798
950
 
@@ -1622,11 +1774,17 @@ async function _rapierPlainTextFileAsync(context) {
1622
1774
  async function _rapierCloneRootAsync(root, work) { return finishAsync(cloneTree(root), work); }
1623
1775
 
1624
1776
  function _rapierSourceTokenValue(node) {
1625
- if (!node?.hasAttribute('data-rapier-source') || !node.hasAttribute('data-rapier-visible') ||
1626
- Array.from(node.childNodes).some(child => child.nodeType !== Node.TEXT_NODE)) return null;
1777
+ if (!node?.hasAttribute('data-rapier-source') || !node.hasAttribute('data-rapier-visible')) return null;
1627
1778
  try {
1779
+ const source = decodeURIComponent(node.getAttribute('data-rapier-source'));
1780
+ if (node.classList.contains('rapier-source-token--wiki') || node.classList.contains('rapier-source-token--wiki-embed')) {
1781
+ const token = md.parseInline(source, {rapierWikiLinks: 'links'})[0]?.children;
1782
+ if (token?.length !== 1 || token[0].type !== 'rapier_wiki') return null;
1783
+ if (!node.hasAttribute('data-rapier-wiki-ready') && (node.children.length !== 1 || node.firstElementChild.tagName !== 'A' ||
1784
+ node.firstElementChild.getAttribute('href') !== wikiAddress(token[0].meta.target))) return null;
1785
+ } else if (Array.from(node.childNodes).some(child => child.nodeType !== Node.TEXT_NODE)) return null;
1628
1786
  return node.textContent === decodeURIComponent(node.getAttribute('data-rapier-visible'))
1629
- ? decodeURIComponent(node.getAttribute('data-rapier-source')) : null;
1787
+ ? source : null;
1630
1788
  } catch (_) { return null; }
1631
1789
  }
1632
1790
 
@@ -1634,7 +1792,7 @@ function _rapierResolveSoftBreakTokens(root) { return finish(_rapierResolveSoftB
1634
1792
 
1635
1793
  function* _rapierResolveSoftBreakTokensSteps(root) {
1636
1794
  for (const token of root.querySelectorAll('.rapier-source-token--softbreak')) { yield;
1637
- if (_rapierSourceTokenValue(token) === '\n') token.replaceWith(document.createTextNode(' '));
1795
+ if (_rapierSourceTokenValue(token) === '\n') token.replaceWith(document.createTextNode(token.closest('pre') ? '\n' : ' '));
1638
1796
  else token.replaceWith(...token.childNodes);
1639
1797
  }
1640
1798
  return root;
@@ -3,7 +3,7 @@ import {collectPdfVectors} from './pdf-vectors.mjs';
3
3
  import {collectPdfGlyphs} from './pdf-glyphs.mjs';
4
4
  import {pdfFontSources, waitForFonts} from './export-fonts.mjs';
5
5
  import {apply as applyTypography} from '../layout/typography.mjs';
6
- import {documentLanguage, pageSizeOf} from '../spec/document-settings.mjs';
6
+ import {documentLanguage, pageSizeOf, paperForLanguage} from '../spec/document-settings.mjs';
7
7
  import {applyPrintCapitals} from './print-capitals.mjs';
8
8
  import {stripOneTerminator as _rapierWillStripOneTerminator} from '../agent/will.mjs';
9
9
  import {quotedWillSpan} from '../spec/md-marks.mjs';
@@ -220,7 +220,7 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
220
220
  if (current?.() === false) refuse('document_changed');
221
221
  };
222
222
  if (!root?.ownerDocument || typeof capture !== 'function') refuse('pdf_render_unavailable');
223
- const paper = settings?.papersize || (templatePage ? pageSizeOf(settings) : {width: 11906, height: 16838});
223
+ const paper = settings?.papersize || (templatePage ? pageSizeOf(settings) : paperForLanguage(root.ownerDocument.defaultView?.navigator?.language));
224
224
  const width = paper.width / 20, height = paper.height / 20;
225
225
  const margins = Object.fromEntries(['top', 'right', 'bottom', 'left'].map(side =>
226
226
  [side, settings?.geometry ? settings.geometry[side] / 20 : (side === 'top' ? 12 : 14) * 72 / 25.4]));
@@ -841,7 +841,7 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
841
841
  const characters = fallbackCharacters(part, run.carrier); if (!characters.length) continue;
842
842
  const {slots, count} = _rapierPrintSlots(part.text, part.rtl);
843
843
  const advance = part.width * .75 / count;
844
- const baseline = decimal(height - margins.top - (part.y + part.height * .8) * .75);
844
+ const baseline = decimal(height - margins.top - (part.baseline ?? part.y + part.height * .8) * .75);
845
845
  for (let letter = 0; letter < characters.length; letter++) {
846
846
  const size = Math.max(.1, advance * 2 * [...characters[letter].replace(/\n/g, '')].length), scaleY = Math.max(.1, part.height * .75) / size;
847
847
  const value = (codes.get(characters[letter]) || codes.get(characters[letter].codePointAt(0))).toString(16).padStart(4, '0');
@@ -49,6 +49,41 @@ const DIAGRAM_LABEL_CSS = new Set(('color fill stroke background-color font-fami
49
49
  + 'text-decoration-thickness text-underline-offset align-items align-content justify-content justify-items flex-direction flex-wrap '
50
50
  + 'width height min-width min-height max-width max-height margin margin-top margin-right margin-bottom margin-left '
51
51
  + 'padding padding-top padding-right padding-bottom padding-left').split(' '));
52
+ const DOCUMENT_STYLE_PROPERTIES = new Set([...DIAGRAM_LABEL_CSS, ...('background display visibility opacity '
53
+ + 'border border-top border-right border-bottom border-left border-color border-style border-width border-collapse border-spacing '
54
+ + 'border-top-color border-right-color border-bottom-color border-left-color border-top-width border-right-width border-bottom-width border-left-width '
55
+ + 'table-layout caption-side empty-cells list-style-type list-style-position direction unicode-bidi hyphens '
56
+ + 'stroke-width stroke-opacity stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit '
57
+ + 'fill-opacity fill-rule clip-rule clip-path filter stop-color stop-opacity text-anchor dominant-baseline alignment-baseline '
58
+ + 'paint-order vector-effect shape-rendering color-interpolation color-interpolation-filters').split(' ')]);
59
+
60
+ function cleanDocumentStyle(tag, value, context, classes = '') {
61
+ if (context === 'raw' && tag === 'span') return typography.style(typography.parseStyle(value));
62
+ const names = classes.split(/\s+/), diagram = tag === 'svg' && names.includes('pele') &&
63
+ (context === 'diagram' || ['render', 'export', 'source'].includes(context) && names.includes('rapier-diagram'));
64
+ const kept = [];
65
+ let start = 0, quote = '', depth = 0;
66
+ const take = end => {
67
+ const part = value.slice(start, end), colon = part.indexOf(':');
68
+ if (colon < 0) return;
69
+ const property = part.slice(0, colon).trim().toLowerCase(), body = part.slice(colon + 1).trim();
70
+ const diagramProperty = diagram && /^--(?:_[a-z0-9_-]+|pele-[a-z0-9-]+)$/.test(property);
71
+ if (!DOCUMENT_STYLE_PROPERTIES.has(property) && !/^--md-[a-z0-9-]+$/.test(property) && !diagramProperty && !(diagram && property === 'font')) return;
72
+ if (!body || /[\\\u0000-\u0008\u000b\u000e-\u001f{}@]|\/\*|\*\//.test(body)) return;
73
+ const withoutLocal = body.replace(/\burl\(\s*(['"]?)#[a-z0-9_.:-]+\1\s*\)/gi, '');
74
+ if (/(?:url|image|image-set|-webkit-image-set|element|paint|attr|expression)\s*\(/i.test(withoutLocal)) return;
75
+ kept.push(part.trim());
76
+ };
77
+ for (let at = 0; at <= value.length; at++) {
78
+ const ch = value[at];
79
+ if (quote) { if (ch === '\\') at++; else if (ch === quote) quote = ''; }
80
+ else if (ch === '"' || ch === "'") quote = ch;
81
+ else if (ch === '(') depth++;
82
+ else if (ch === ')') { if (--depth < 0) return ''; }
83
+ else if ((ch === ';' || at === value.length) && !depth) { take(at); start = at + 1; }
84
+ }
85
+ return quote || depth ? '' : kept.join(';');
86
+ }
52
87
  const DIAGRAM_LABEL_SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
53
88
  const DIAGRAM_LABEL_HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
54
89
  const DIAGRAM_LABEL_MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
@@ -160,7 +195,8 @@ function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeR
160
195
  if (!_rapierImageDataAttributeAllowed(tag, key, compact, safeRasterDataUrl)) continue;
161
196
  } else if (!uri.test(compact)) continue;
162
197
  }
163
- attribs[name] = selected === 'raw' && tag === 'span' && key === 'style' ? typography.style(typography.parseStyle(value)) : value;
198
+ const cleaned = key === 'style' ? cleanDocumentStyle(tag, value, selected, input.class || '') : value;
199
+ if (key !== 'style' || cleaned) attribs[name] = cleaned;
164
200
  }
165
201
  if ((tag === 'a' || tag === 'area') && attribs.target?.trim().toLowerCase() === '_blank') attribs.rel = 'noopener noreferrer';
166
202
  return {tagName, attribs};
@@ -254,6 +290,23 @@ function diagramLabelContained(node) {
254
290
  }
255
291
 
256
292
  function _rapierInstallSanitizeHooks() {
293
+ _rapierInstallDocumentSanitizeHooks();
294
+ DOMPurify.addHook('uponSanitizeElement', node => {
295
+ if (_rapierSanitizeRuntime.context !== 'paste' || !node || !/^(?:P|LI|H[1-6]|SPAN)$/.test(node.nodeName)) return;
296
+ node.removeAttribute('data-rapier-word-level');
297
+ node.removeAttribute('data-rapier-word-marker');
298
+ const style = node.getAttribute('style');
299
+ if (!style || !/mso-list/i.test(style)) return;
300
+ const spec = globalThis.RapierMarkdownSpec;
301
+ if (!spec) return;
302
+ const {wordListLevel, wordListIsMarker} = spec;
303
+ const level = /^(?:P|LI|H[1-6])$/.test(node.nodeName) ? wordListLevel(style) : 0;
304
+ if (level) node.setAttribute('data-rapier-word-level', String(level));
305
+ else if (node.nodeName === 'SPAN' && wordListIsMarker(style)) node.setAttribute('data-rapier-word-marker', '1');
306
+ });
307
+ }
308
+
309
+ function _rapierInstallDocumentSanitizeHooks() {
257
310
  DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
258
311
  if (!/^(?:src|href|xlink:href)$/i.test(data.attrName || '')) return;
259
312
  const compact = String(data.attrValue || '').replace(/[\u0000-\u0020\u00a0]+/g, '');
@@ -269,7 +322,7 @@ function _rapierInstallSanitizeHooks() {
269
322
  if (_rapierRemoteContent.allowed || !/^(?:paste|render|raw|export)$/.test(_rapierSanitizeRuntime.context)) return;
270
323
  if (!node || node.nodeName !== 'IMG' || node.hasAttribute('data-rapier-remote-src')) return;
271
324
  const src = node.getAttribute('src') || '';
272
- if (!src || !_rapierRemoteSubresourceOrigin(src)) return;
325
+ if (!src || !_rapierRemoteSubresourceOrigin(src) || globalThis.RapierEmbedPictureBase?.vouched?.has(src)) return;
273
326
  node._rapierHeldBack = true;
274
327
  node.setAttribute('data-rapier-remote-src', src);
275
328
  const alt = node.getAttribute('alt');
@@ -286,39 +339,22 @@ function _rapierInstallSanitizeHooks() {
286
339
  node.setAttribute('data-rapier-remote-src', href);
287
340
  }
288
341
  });
289
- DOMPurify.addHook('uponSanitizeElement', node => {
290
- if (_rapierSanitizeRuntime.context !== 'paste' || !node || !/^(?:P|LI|H[1-6]|SPAN)$/.test(node.nodeName)) return;
291
- node.removeAttribute('data-rapier-word-level');
292
- node.removeAttribute('data-rapier-word-marker');
293
- const style = node.getAttribute('style');
294
- if (!style || !/mso-list/i.test(style)) return;
295
- const spec = globalThis.RapierMarkdownSpec;
296
- if (!spec) return;
297
- const {wordListLevel, wordListIsMarker} = spec;
298
- const level = /^(?:P|LI|H[1-6])$/.test(node.nodeName) ? wordListLevel(style) : 0;
299
- if (level) node.setAttribute('data-rapier-word-level', String(level));
300
- else if (node.nodeName === 'SPAN' && wordListIsMarker(style)) node.setAttribute('data-rapier-word-marker', '1');
301
- });
302
342
  DOMPurify.addHook('uponSanitizeAttribute', (node, data) => {
303
343
  const value = String(data.attrValue == null ? '' : data.attrValue);
304
- if (_rapierSanitizeRuntime.context === 'raw' && node?.nodeName === 'SPAN' && data.attrName?.toLowerCase() === 'style') {
305
- data.attrValue = typography.style(typography.parseStyle(value));
344
+ if (data.attrName?.toLowerCase() === 'style') {
345
+ data.attrValue = cleanDocumentStyle(node?.localName?.toLowerCase(), value, _rapierSanitizeRuntime.context, node?.getAttribute('class') || '');
306
346
  data.keepAttr = !!data.attrValue;
307
347
  return;
308
348
  }
309
349
  if (String(data.attrName || '').toLowerCase() === 'id' && _rapierChromeOwnsId(value)) { data.keepAttr = false; return; }
310
350
  if (_rapierRemoteContent.allowed || _rapierSanitizeRuntime.context === 'source') return;
311
- if (String(data.attrName || '').toLowerCase() === 'style') {
312
- const cleaned = _rapierStyleWithoutRemoteUrls(value);
313
- if (cleaned !== value) data.attrValue = cleaned;
314
- return;
315
- }
316
351
  const attribute = String(data.attrName || '').toLowerCase();
317
352
  if (attribute === 'alt' || attribute === 'title') return;
318
353
  if (/^data-rapier-remote-(?:src|alt|title)$/.test(attribute) && node && node._rapierHeldBack === true) { data.forceKeepAttr = true; return; }
319
354
  if (attribute.startsWith('data-')) return;
320
355
  const element = String(node && node.nodeName || '').toUpperCase();
321
356
  if (attribute === 'href' && (element === 'A' || element === 'AREA')) return;
357
+ if (attribute === 'src' && element === 'IMG' && globalThis.RapierEmbedPictureBase?.vouched?.has(value)) return;
322
358
  if (_rapierCssPresentationIsRemote(attribute, value) ||
323
359
  value.split(/[\s,]+/).some(token => _rapierRemoteSubresourceOrigin(token))) {
324
360
  data.keepAttr = false;
@@ -414,6 +450,13 @@ function _rapierLinkDestinationToConfirm(href, text = '') {
414
450
  }
415
451
 
416
452
  function _rapierSafeRasterDataUrl(value) {
453
+ if (/^data:image\/svg\+xml,/i.test(value)) {
454
+ try {
455
+ const assets = globalThis.RapierImageAssets;
456
+ return assets.dataImage(value)?.codec === 'image/svg+xml' &&
457
+ _rapierVerifyRasterBytes(assets.decodeDataImageBytes(value).bytes, 'image/svg+xml');
458
+ } catch (_) { return false; }
459
+ }
417
460
  const match = RAPIER_RASTER_DATA_URL_RE.exec(value);
418
461
  if (!match) return false;
419
462
  try {
@@ -504,7 +547,7 @@ function _rapierVerifyRasterBytes(bytes, mime) {
504
547
  return false;
505
548
  }
506
549
 
507
- return {_rapierVerifyRasterBytes, _rapierInstallSanitizeHooks, sanitizeRapierHtml,
550
+ return {_rapierVerifyRasterBytes, _rapierInstallSanitizeHooks, _rapierInstallDocumentSanitizeHooks, sanitizeRapierHtml,
508
551
  sanitizeRapierFragment: typeof DOMPurify?.sanitize === 'function' ? sanitizeRapierFragment : undefined,
509
552
  diagramLabelStyle, escapeRapierHtmlText, _rapierChromeOwnsId, _rapierLinkDestinationToConfirm, _rapierSafeRasterDataUrl, _rapierCssPresentationIsRemote, _rapierRemoteSubresourceOrigin, _rapierStyleWithoutRemoteUrls, _rapierStylesheetWithoutRemoteUrls, _rapierDropRemoteRules};
510
553
  }
@@ -212,14 +212,14 @@ function _rapierTemplatePage(canonical, settings, root, darkPaper = false) {
212
212
  const image = paragraph.querySelector('img');
213
213
  const layout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(paragraph.getAttribute('data-md-layout'));
214
214
  const source = image?.getAttribute('src') || '';
215
- if (isPageBorderLayout(layout) && /^data:image\/(?:png|jpeg|webp|gif|svg\+xml|jxl);base64,[A-Za-z0-9+/=]+$/i.test(source)) { url = source; paragraph.remove(); break; }
215
+ if (isPageBorderLayout(layout) && globalThis.RapierImageAssets.dataImage(source)) { url = source; paragraph.remove(); break; }
216
216
  }
217
217
  if (!url) return null;
218
218
  const width = paper.width / 15, height = paper.height / 15;
219
219
  const margins = ['top', 'right', 'bottom', 'left'].map(side => (settings?.geometry?.[side] ?? 1440) / 15);
220
220
  const padding = margins.map(value => value + 'px').join(' ');
221
221
  const ground = 'background:var(--rapier-template-border) 0 0/100% 100% no-repeat';
222
- const shown = darkPaper && url.startsWith('data:image/svg+xml;base64,') ? _rapierDarkPaperSvgUrl(url, '#fafafa') : url;
222
+ const shown = darkPaper && globalThis.RapierImageAssets.dataImage(url)?.codec === 'image/svg+xml' ? _rapierDarkPaperSvgUrl(url, '#fafafa') : url;
223
223
  const css = ':root{--rapier-template-border:url("' + url + '")}\n'
224
224
  + '@media screen{' + (shown !== url ? ':root{--rapier-template-border:url("' + shown + '")}' : '') + '.rapier-template-border{display:none}body{background:var(--md-color-inset)}.rapier-page{box-sizing:border-box;width:min(100%,' + width + 'px);min-height:' + height + 'px;margin:0 auto;padding:' + padding
225
225
  + ';background:var(--rapier-template-border) 0 0/100% ' + height + 'px repeat-y,var(--md-color-bg);color:var(--md-color-text)}}\n'
@@ -235,7 +235,7 @@ function _rapierDarkPaperSvgUrl(url, paperInk) {
235
235
  const ink = globalThis.RapierImageInspection?.inkForPaper;
236
236
  if (typeof ink !== 'function') return url;
237
237
  let text;
238
- try { text = new TextDecoder('utf-8', {fatal: true}).decode(Uint8Array.from(atob(url.slice(26)), c => c.charCodeAt(0))); }
238
+ try { text = new TextDecoder('utf-8', {fatal: true}).decode(globalThis.RapierImageAssets.decodeDataImageBytes(url).bytes); }
239
239
  catch (_) { return url; }
240
240
  const inked = ink(text, {dark: true, paperInk});
241
241
  if (inked === text) return url;
@@ -274,7 +274,7 @@ function* _rapierProjectStyledRootSteps(semanticRoot, options) {
274
274
  for (const token of root.querySelectorAll('.rapier-source-token--softbreak')) { yield;
275
275
  if (token.getAttribute('data-rapier-source') !== '%0A') token.replaceWith(...token.childNodes);
276
276
  else if (token.previousSibling?.classList?.contains('callout__label')) token.remove();
277
- else token.replaceWith(root.ownerDocument.createTextNode(' '));
277
+ else token.replaceWith(root.ownerDocument.createTextNode(token.closest('pre') ? '\n' : ' '));
278
278
  }
279
279
  const metadata = opts.metadata || {};
280
280
 
@@ -532,10 +532,10 @@ function _rapierAnnotateExportBoxPolygons(root) {
532
532
  for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
533
533
  const layout = globalThis.RapierMarkdownLayout.parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
534
534
  if (layout?.wrap !== 'box' && layout?.wrap !== 'around') continue;
535
- const match = /^data:image\/svg\+xml;base64,([A-Za-z0-9+/]+=*)$/.exec(image.getAttribute('src') || '');
536
- if (!match) continue;
535
+ const url = image.getAttribute('src') || '';
536
+ if (globalThis.RapierImageAssets.dataImage(url)?.codec !== 'image/svg+xml') continue;
537
537
  let text;
538
- try { text = new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(atob(match[1]), c => c.charCodeAt(0))); }
538
+ try { text = new TextDecoder('utf-8', { fatal: true }).decode(globalThis.RapierImageAssets.decodeDataImageBytes(url).bytes); }
539
539
  catch (_) { continue; }
540
540
  let recipe;
541
541
  try { recipe = _rapierDrawReadSVGRecipe(text); } catch (_) { continue; }
@@ -634,7 +634,7 @@ function _rapierSharedResolve(text, ids, images, definitions) {
634
634
  let broken = false;
635
635
  const src = value => {
636
636
  const found = images.get(value);
637
- if (typeof found !== 'string' || !/^data:image\/(?:png|jpeg|webp|gif|svg\+xml|jxl);base64,[A-Za-z0-9+/]+={0,2}$/i.test(found)) { broken = true; return null; }
637
+ if (!globalThis.RapierImageAssets.dataImage(found)) { broken = true; return null; }
638
638
  return found;
639
639
  };
640
640
  const inline = new RegExp('(\\]\\([ \\t\\r\\n]*(?:&lt;)?)#' + id + '(?=>?[ \\t\\r\\n]*\\)|>?[ \\t\\r\\n]+["\'(])', 'g');
@@ -896,7 +896,7 @@ async function _rapierRequireOfflinePageImages(root) {
896
896
  }
897
897
  if (!missing.length) return;
898
898
  throw Object.assign(new Error('Web page not written; embed these pictures and try again. ' + missing.join('; ')),
899
- {code: 'offline_image_unavailable'});
899
+ {code: 'offline_image_unavailable', missing});
900
900
  }
901
901
 
902
902
  function _rapierCssValueUrls(value) {
@@ -1741,7 +1741,7 @@ function _rapierArtifactLayoutScript(root, nonce = '', {pictures = true} = {}) {
1741
1741
  throw new Error('Offline typography requires the complete retained MIT layout factories');
1742
1742
  const modules = _rapierArtifactFactoryModules(dependencies, 'layout');
1743
1743
  const script = '/* Rapier export layout: SPDX-License-Identifier: MIT */\n' +
1744
- '/* Pretext bac1774c0f8b7a92e44b9da43b6d5443d55d0d9b (Unreleased)\n' + dependencies.license + '\n*/\n' +
1744
+ '/* Pretext 1.0.0 + main 0d102a5cd933611e86a7e60bda7b4baa5995c6b0\n' + dependencies.license + '\n*/\n' +
1745
1745
  '(() => {\nconst modules = Object.create(null);\n' + modules +
1746
1746
  (wrapping ? '\n(' + _rapierProjectArtifactLayout.toString() +
1747
1747
  ')(document.querySelector("main.rapier-page"), modules["spec/md-layout.mjs"], modules["layout/model.mjs"], modules["layout/preparation.mjs"]);\n' : '') +
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: MIT
2
+ export const WIKI_PREFIX = '?rapier-wiki=';
3
+ export function wikiAddress(target) {
4
+ const at = target.indexOf('#'), path = at < 0 ? target : target.slice(0, at);
5
+ return WIKI_PREFIX + encodeURIComponent(path) + (at < 0 ? '' : '#' + encodeURIComponent(target.slice(at + 1)));
6
+ }
7
+ export function wikiTarget(address) {
8
+ if (typeof address !== 'string' || !address.startsWith(WIKI_PREFIX)) return null;
9
+ try { return decodeURIComponent(address.slice(WIKI_PREFIX.length).split('#')[0]); }
10
+ catch { return null; }
11
+ }
12
+ export function installWikiParser(md, render) {
13
+ md.inline.ruler.before('image', 'rapier_wiki', (state, silent) => {
14
+ if (!state.env?.rapierWikiLinks) return false;
15
+ const start = state.pos, embed = state.src[start] === '!';
16
+ const open = start + (embed ? 1 : 0);
17
+ if (state.linkLevel || state.src.slice(open, open + 2) !== '[[') return false;
18
+ const end = state.src.indexOf(']]', open + 2);
19
+ if (end < 0 || end + 2 > state.posMax || end - start > 4096) return false;
20
+ const body = state.src.slice(open + 2, end);
21
+ if (!body || /[\[\]\n\r\0]/.test(body)) return false;
22
+ const pipe = body.indexOf('|'), target = (pipe < 0 ? body : body.slice(0, pipe)).trim();
23
+ if (!target || /^(?:[a-z][a-z0-9+.-]*:|\/|\\)/i.test(target)) return false;
24
+ if (!silent) {
25
+ const token = state.push('rapier_wiki', '', 0);
26
+ token.content = state.src.slice(start, end + 2);
27
+ token.meta = {target, label: pipe < 0 ? target : body.slice(pipe + 1), embed};
28
+ }
29
+ state.pos = end + 2;
30
+ return true;
31
+ });
32
+ md.renderer.rules.rapier_wiki = (tokens, at, options, env) => render(tokens[at], env);
33
+ }
34
+
@@ -40,3 +40,6 @@ export function willMarkerOf(line: string): WillMarker | null;
40
40
  export function terminatorWidth(last: number, prior: number): number;
41
41
  /** The text without one trailing line terminator. */
42
42
  export function stripOneTerminator(text: string): string;
43
+ /** Judges sequential UTF-16 source splices, including the resulting marker identities and protected text. */
44
+ export function evaluateWill(beforeText: string, splices: {pos: number; removed: string; inserted: string}[]):
45
+ {outcome: 'applied'} | {outcome: 'invalid' | 'refused'; reason: string; rule: string; law?: WillLaw; region?: number};
@@ -1,9 +1,11 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import {next, weight, exceptions, maximum} from '../agent/vendor/hyphen-en-us/patterns.js';
3
3
 
4
+ export const hyphenMinimums = {left: 2, right: 3, word: 5};
5
+
4
6
  export function englishCuts(word) {
5
7
  if (!/^[A-Za-z]+$/.test(word)) return null;
6
- if (word.length < 6) return [];
8
+ if (word.length < hyphenMinimums.word) return [];
7
9
  const cuts = [];
8
10
  let offset = 0, before = 0;
9
11
  for (const part of word.match(/[A-Z]+(?![a-z])|[A-Z]?[a-z]+/g)) {
@@ -11,11 +13,11 @@ export function englishCuts(word) {
11
13
  for (const at of partCuts(part)) cuts.push(offset+at);
12
14
  offset += part.length; before = part.length;
13
15
  }
14
- return cuts.filter(at => at >= 3 && at <= word.length-3).sort((a, b) => a-b).filter((at, index, all) => at !== all[index-1]);
16
+ return cuts.filter(at => at >= hyphenMinimums.left && at <= word.length-hyphenMinimums.right).sort((a, b) => a-b).filter((at, index, all) => at !== all[index-1]);
15
17
  }
16
18
 
17
19
  function partCuts(word) {
18
- if (word.length < 6) return [];
20
+ if (word.length < hyphenMinimums.word) return [];
19
21
  const lower = word.toLowerCase(), known = exceptions[lower];
20
22
  if (Array.isArray(known)) return known;
21
23
  const source = '.'+lower+'.', weights = new Uint8Array(source.length+1);
@@ -30,7 +32,7 @@ function partCuts(word) {
30
32
  }
31
33
  }
32
34
  const cuts = [];
33
- for (let at = 3; at <= word.length-3; at++) if (weights[at+1]&1) cuts.push(at);
35
+ for (let at = hyphenMinimums.left; at <= word.length-hyphenMinimums.right; at++) if (weights[at+1]&1) cuts.push(at);
34
36
  return cuts;
35
37
  }
36
38