rapier-markdown-kit 1.2.32 → 1.2.40
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.
- package/README.md +16 -10
- package/dist/agent/vendor/pretext/NOTICE +2 -2
- package/dist/agent/vendor/pretext/SOURCE.json +62 -78
- package/dist/agent/vendor/pretext/analysis.js +24 -6
- package/dist/agent/vendor/pretext/gecko-line-breaks.js +1 -1
- package/dist/agent/vendor/pretext/generated/engine-break-data.js +1 -1
- package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +1 -1
- package/dist/agent/vendor/pretext/graphemes.js +1 -1
- package/dist/agent/vendor/pretext/han-kerning.js +1 -1
- package/dist/agent/vendor/pretext/layout.js +14 -11
- package/dist/agent/vendor/pretext/line-break.js +170 -195
- package/dist/agent/vendor/pretext/line-breaks.js +1 -7
- package/dist/agent/vendor/pretext/line-text.js +2 -2
- package/dist/agent/vendor/pretext/measurement.js +454 -113
- package/dist/agent/vendor/pretext/prepare.js +164 -244
- package/dist/agent/vendor/pretext/rich-inline.js +57 -37
- package/dist/agent/will.mjs +73 -0
- package/dist/kit/ledger/data.mjs +2 -2
- package/dist/kit/ledger/format.mjs +86 -42
- package/dist/kit/ledger/hash.mjs +43 -2
- package/dist/kit/ledger/history.mjs +6 -0
- package/dist/kit/ledger/inverse-source.mjs +6 -2
- package/dist/kit/ledger/journal-records.mjs +4 -2
- package/dist/kit/ledger/merge.mjs +3 -17
- package/dist/kit/ledger/text-merge.mjs +191 -28
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-markdown.mjs +133 -10
- package/dist/kit/render-print.mjs +3 -3
- package/dist/kit/render-sanitize.mjs +66 -23
- package/dist/kit/render.mjs +10 -10
- package/dist/kit/wiki.mjs +34 -0
- package/dist/kit/will.d.mts +3 -0
- package/dist/layout/hyphenation.mjs +6 -4
- package/dist/layout/line-plan.mjs +96 -0
- package/dist/layout/model.mjs +2 -1
- package/dist/layout/paragraph-fit.mjs +4 -4
- package/dist/layout/prepared-paragraph.mjs +19 -2
- package/dist/layout/typography.mjs +57 -63
- package/dist/spec/document-fonts.mjs +1 -0
- package/dist/spec/document-settings.mjs +1 -0
- package/dist/spec/html-blocks.mjs +10 -7
- package/dist/spec/md-assets.mjs +52 -12
- package/package.json +1 -1
- package/style.css +37 -28
- package/dist/agent/vendor/pretext/entry-geometry.js +0 -183
|
@@ -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 '
|
|
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
|
-
|
|
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 (
|
|
305
|
-
data.attrValue =
|
|
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
|
}
|
package/dist/kit/render.mjs
CHANGED
|
@@ -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) &&
|
|
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 &&
|
|
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(
|
|
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
|
|
536
|
-
if (
|
|
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(
|
|
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 (
|
|
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]*(?:<)?)#' + 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
|
|
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
|
+
|
package/dist/kit/will.d.mts
CHANGED
|
@@ -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 <
|
|
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 >=
|
|
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 <
|
|
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 =
|
|
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
|
|
|
@@ -55,9 +55,105 @@ function laterWholeWord(flow, cursor, line, items, bands, slot, next, materializ
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function slotSequence(width, top, obstacles, lineHeight, minWidth, slotsForBand, direction = 'ltr', order = 'columns') {
|
|
59
|
+
const bottom = obstacles.length ? Math.max(...obstacles.map(item => item.y + item.height)) - top : 0, rows = [];
|
|
60
|
+
let y = 0;
|
|
61
|
+
while (y < bottom && rows.length < 768) {
|
|
62
|
+
const bands = slotsForBand(width, obstacles, top + y, lineHeight, Math.min(minWidth, width));
|
|
63
|
+
if (!bands) return null;
|
|
64
|
+
if (bands.length) { rows.push({y, bands}); y += lineHeight; continue; }
|
|
65
|
+
let next = Infinity;
|
|
66
|
+
for (const obstacle of obstacles) if (obstacle.x < width && obstacle.x + obstacle.width > 0 &&
|
|
67
|
+
obstacle.y < top + y + lineHeight && obstacle.y + obstacle.height > top + y) next = Math.min(next, obstacle.y + obstacle.height - top);
|
|
68
|
+
if (!Number.isFinite(next) || next <= y) return null;
|
|
69
|
+
y = next;
|
|
70
|
+
}
|
|
71
|
+
const multiple = rows.some(row => row.bands.length > 1);
|
|
72
|
+
if (!multiple) return {slots: rows.flatMap(row => row.bands.map(band => ({...band, y: row.y}))), end: y, multiple};
|
|
73
|
+
const free = row => row.bands.length === 1 && row.bands[0].x < .5 && row.bands[0].width > width - .5;
|
|
74
|
+
const slots = [];
|
|
75
|
+
let block = 0;
|
|
76
|
+
for (let at = 0; at < rows.length;) {
|
|
77
|
+
if (free(rows[at])) { slots.push({...rows[at].bands[0], y: rows[at].y}); at++; continue; }
|
|
78
|
+
let stop = at;
|
|
79
|
+
while (stop < rows.length && !free(rows[stop])) stop++;
|
|
80
|
+
block++;
|
|
81
|
+
const channels = [];
|
|
82
|
+
for (let index = at; index < stop; index++) for (const band of rows[index].bands) {
|
|
83
|
+
const owner = channels.find(channel => channel.row === index - 1 &&
|
|
84
|
+
Math.min(channel.last.x + channel.last.width, band.x + band.width) - Math.max(channel.last.x, band.x) > 0);
|
|
85
|
+
const slot = {...band, y: rows[index].y};
|
|
86
|
+
if (owner) { owner.slots.push(slot); owner.row = index; owner.last = slot; }
|
|
87
|
+
else channels.push({slots: [slot], row: index, last: slot, x: band.x});
|
|
88
|
+
}
|
|
89
|
+
channels.sort((a, b) => direction === 'rtl' ? b.x - a.x : a.x - b.x);
|
|
90
|
+
const gaps = rows.slice(at, stop).filter(row => row.bands.length > 1).map(row => row.bands[1].x - row.bands[0].x - row.bands[0].width).sort((a, b) => a - b);
|
|
91
|
+
const thin = gaps[gaps.length >> 1] < .15 * width;
|
|
92
|
+
if (!thin) channels.forEach((channel, index) => { for (const slot of channel.slots) { slot.block = block; slot.channel = index; } });
|
|
93
|
+
if (order === 'rows' || thin) slots.push(...channels.flatMap(channel => channel.slots).sort((a, b) => a.y - b.y || (direction === 'rtl' ? b.x - a.x : a.x - b.x)));
|
|
94
|
+
else for (const channel of channels) slots.push(...channel.slots);
|
|
95
|
+
at = stop;
|
|
96
|
+
}
|
|
97
|
+
return {slots, end: y, multiple};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function channelPlan(flow, width, lineHeight, sequence, layoutNextRichInlineLineRange, materializeRichInlineLineRange, items) {
|
|
101
|
+
const lines = [], slots = sequence.slots;
|
|
102
|
+
let cursor, previous = null;
|
|
103
|
+
const place = (band, later) => {
|
|
104
|
+
let range = layoutNextRichInlineLineRange(flow, band.width, cursor);
|
|
105
|
+
if (!range) return 'end';
|
|
106
|
+
if (!Number.isFinite(range.width) || range.width > width + .5) return 'fail';
|
|
107
|
+
if (range.width > band.width + .5) return 'skip';
|
|
108
|
+
let line = materializeRichInlineLineRange(flow, range);
|
|
109
|
+
if (!afterSource(lastSource(line), previous)) return 'fail';
|
|
110
|
+
if (laterWholeWord(flow, cursor, line, items, [band, ...later], 0, layoutNextRichInlineLineRange, materializeRichInlineLineRange)) return 'skip';
|
|
111
|
+
if (later.some(other => other.y === band.y) && insideChip(items, line)) {
|
|
112
|
+
const whole = beforeChip(flow, range, line, cursor, items, layoutNextRichInlineLineRange, materializeRichInlineLineRange);
|
|
113
|
+
if (whole) { range = whole.range; line = whole.line; }
|
|
114
|
+
}
|
|
115
|
+
if (!afterSource(lastSource(line), previous) || lines.length === 4096) return 'fail';
|
|
116
|
+
lines.push({...line, ...band});
|
|
117
|
+
previous = lastSource(line); cursor = range.end;
|
|
118
|
+
return 'placed';
|
|
119
|
+
};
|
|
120
|
+
const done = () => lines.length ? {lines, height: Math.max(lineHeight, ...lines.map(line => line.y + lineHeight))} : null;
|
|
121
|
+
for (let index = 0; index < slots.length; index++) {
|
|
122
|
+
const result = place(slots[index], [...slots.slice(index + 1, index + 9), {x: 0, width, y: sequence.end}]);
|
|
123
|
+
if (result === 'end') return done();
|
|
124
|
+
if (result === 'fail') return null;
|
|
125
|
+
}
|
|
126
|
+
for (let row = 0; row < 768; row++) {
|
|
127
|
+
const result = place({x: 0, width, y: sequence.end + row * lineHeight}, []);
|
|
128
|
+
if (result === 'end') return done();
|
|
129
|
+
if (result !== 'placed') return null;
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function columnsFilled(slots, placed) {
|
|
135
|
+
const total = new Map(), used = new Map(), reached = new Set();
|
|
136
|
+
for (const slot of slots) if (slot.block) total.set(slot.block + ':' + slot.channel, (total.get(slot.block + ':' + slot.channel) || 0) + 1);
|
|
137
|
+
for (const slot of placed) if (slot.block) { used.set(slot.block + ':' + slot.channel, (used.get(slot.block + ':' + slot.channel) || 0) + 1); reached.add(slot.block); }
|
|
138
|
+
for (const count of total.values()) if (count < 4) return false;
|
|
139
|
+
for (const block of reached) {
|
|
140
|
+
let last = -1;
|
|
141
|
+
for (const slot of slots) if (slot.block === block && slot.channel > last) last = slot.channel;
|
|
142
|
+
if ((used.get(block + ':' + last) || 0) < .6 * total.get(block + ':' + last)) return false;
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
58
147
|
export function linePlan(flow, width, top, obstacles, lineHeight, minWidth, direction, balance,
|
|
59
148
|
slotsForBand, layoutNextRichInlineLineRange, materializeRichInlineLineRange, items = null) {
|
|
60
149
|
if (!flow || balance) return null;
|
|
150
|
+
const sequence = obstacles.length ? slotSequence(width, top, obstacles, lineHeight, minWidth, slotsForBand, direction) : null;
|
|
151
|
+
if (sequence?.multiple) {
|
|
152
|
+
const columns = channelPlan(flow, width, lineHeight, sequence, layoutNextRichInlineLineRange, materializeRichInlineLineRange, items);
|
|
153
|
+
if (!columns || columnsFilled(sequence.slots, columns.lines)) return columns;
|
|
154
|
+
return channelPlan(flow, width, lineHeight, slotSequence(width, top, obstacles, lineHeight, minWidth, slotsForBand, direction, 'rows'),
|
|
155
|
+
layoutNextRichInlineLineRange, materializeRichInlineLineRange, items);
|
|
156
|
+
}
|
|
61
157
|
const lines = [];
|
|
62
158
|
let cursor, previous = null, y = 0, bottom = 0, rows = 0;
|
|
63
159
|
for (let step = 0; step < 4096 && rows < 768; step++) {
|
package/dist/layout/model.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {layoutNextRichInlineLineRange, materializeRichInlineLineRange} from '../agent/vendor/pretext/rich-inline.js';
|
|
3
3
|
import {validLayout} from '../spec/md-layout.mjs';
|
|
4
|
-
import {linePlan} from './line-plan.mjs';
|
|
4
|
+
import {linePlan, slotSequence, columnsFilled} from './line-plan.mjs';
|
|
5
5
|
import {captureFontAuthority, createPreparationScope, fontReady, invalidateRichMetrics, paragraphItems,
|
|
6
6
|
prepareRichParagraph, supportsRichStyle} from './preparation.mjs';
|
|
7
7
|
export {captureFontAuthority, createPreparationScope, invalidateRichMetrics, prepareRichParagraph, supportsRichStyle};
|
|
@@ -309,6 +309,7 @@ export function pictureSlices(profile, x, y, width, height, scale = 1) {
|
|
|
309
309
|
return slices;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
export {slotSequence, columnsFilled};
|
|
312
313
|
export function slotsForBand(width, obstacles, top, height, minWidth = 40) {
|
|
313
314
|
if (!finite(width) || width <= 0 || !finite(top) || !finite(height) || height <= 0 ||
|
|
314
315
|
!finite(top + height) || !finite(minWidth) || minWidth <= 0 || !Array.isArray(obstacles)) return null;
|
|
@@ -22,7 +22,7 @@ export function createParagraphFitter() {
|
|
|
22
22
|
return handle;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function* solveSteps(handle, width, {indent = 0, left = 0, right = 0, widths = null, shrink = .
|
|
25
|
+
function* solveSteps(handle, width, {indent = 0, left = 0, right = 0, widths = null, shrink = .5, stretch = 1.2, hyphenCost = 8} = {}, known) {
|
|
26
26
|
const prepared = indexes.get(handle);
|
|
27
27
|
if (!prepared || ![width, indent, left, right].every(Number.isFinite) || width <= 0) return null;
|
|
28
28
|
const measures = Array.isArray(widths) && widths.every(value => Number.isFinite(value) && value > 0) ? widths : [];
|
|
@@ -102,7 +102,7 @@ export function createParagraphFitter() {
|
|
|
102
102
|
if (!Number.isFinite(natural)) continue;
|
|
103
103
|
const count = lastWord - start.word, gapWidth = gaps[lastWord] - gaps[start.word];
|
|
104
104
|
const opening = start.cut ? 0 : Math.min(Math.max(0, left), words[start.word].opening);
|
|
105
|
-
const closing = end.cut ? 0 : Math.min(Math.max(0, right), words[lastWord].closing);
|
|
105
|
+
const closing = end.cut ? Math.min(Math.max(0, right), end.cut.protrude || 0) : Math.min(Math.max(0, right), words[lastWord].closing);
|
|
106
106
|
const priorDiscretionary = start.kind === 'soft' || start.kind === 'auto';
|
|
107
107
|
if (final && priorDiscretionary) continue;
|
|
108
108
|
for (let lineClass = 0; lineClass < classes; lineClass++) {
|
|
@@ -120,7 +120,7 @@ export function createParagraphFitter() {
|
|
|
120
120
|
let local = 1 + 100 * Math.max(0, overflow / target) ** 2;
|
|
121
121
|
if (final && startIndex) local += 100 * Math.max(0, .45 - natural / target) ** 2 + (count === 0 ? 25 : 0);
|
|
122
122
|
else if (!final) local += (ratio < 0 ? 100 : 12.5) * ratio ** 2 + 2400 * (residual / target) ** 2;
|
|
123
|
-
if (discretionary) local +=
|
|
123
|
+
if (discretionary) local += hyphenCost + (priorDiscretionary ? hyphenCost : 0);
|
|
124
124
|
const set = !final && count && residual > 0 ? remaining / count : delta;
|
|
125
125
|
const nextClass = Math.min(lineClass + 1, classes - 1);
|
|
126
126
|
for (let active = available; active; active &= active - 1) {
|
|
@@ -153,7 +153,7 @@ export function createParagraphFitter() {
|
|
|
153
153
|
const start = points[startIndex], end = points[endIndex], lastWord = end.cut ? end.word : end.word - 1;
|
|
154
154
|
chosen.push({start: start.next, end: end.end, next: end.next, kind: end.kind, glyph: end.cut?.glyph || '',
|
|
155
155
|
wordSpacing: spacing[best], opening: start.cut ? 0 : Math.min(Math.max(0, left), words[start.word].opening),
|
|
156
|
-
closing: end.cut ? 0 : Math.min(Math.max(0, right), words[lastWord].closing),
|
|
156
|
+
closing: end.cut ? Math.min(Math.max(0, right), end.cut.protrude || 0) : Math.min(Math.max(0, right), words[lastWord].closing),
|
|
157
157
|
firstWord: start.word, lastWord, segment: segmentIndex});
|
|
158
158
|
best = priorIndex; endIndex = startIndex;
|
|
159
159
|
}
|
|
@@ -2,9 +2,25 @@
|
|
|
2
2
|
import {paragraphHyphens} from './hyphenation.mjs';
|
|
3
3
|
import {prepareRichInlineSegments} from '../agent/vendor/pretext/rich-inline.js';
|
|
4
4
|
import {getEngineProfile, getPreparationLanguage, getFontMeasurement, parseFontSize,
|
|
5
|
-
getTextWidth, getEmojiCorrection, textMayContainEmoji,
|
|
5
|
+
getTextWidth, getEmojiCorrection, textMayContainEmoji, getSegmentMetrics, getContext} from '../agent/vendor/pretext/measurement.js';
|
|
6
6
|
import {KIND_BITS, TEXT, SPACE, SOFT_HYPHEN, ZERO_WIDTH_BREAK, ZERO_WIDTH_GLUE, OBJECT, SPACED, UNBROKEN} from '../agent/vendor/pretext/analysis.js';
|
|
7
7
|
|
|
8
|
+
function measureWithLetterSpacing(text, letterSpacing, emojiCorrection, measurement) {
|
|
9
|
+
if (!measurement.state.takesLetterSpacing) return null;
|
|
10
|
+
const base = getTextWidth(text, measurement, emojiCorrection);
|
|
11
|
+
if (letterSpacing === 0) return base;
|
|
12
|
+
const corrected = getSegmentMetrics(text, measurement).width - base;
|
|
13
|
+
const context = getContext(measurement), previous = context.letterSpacing;
|
|
14
|
+
try {
|
|
15
|
+
context.letterSpacing = `${letterSpacing}px`;
|
|
16
|
+
if (Number.parseFloat(context.letterSpacing) !== letterSpacing) return null;
|
|
17
|
+
const width = context.measureText(text).width - corrected;
|
|
18
|
+
return Number.isFinite(width) ? width : null;
|
|
19
|
+
} finally {
|
|
20
|
+
context.letterSpacing = previous;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
const opening = /^[\p{Ps}\p{Pi}\p{Pf}"']+/u;
|
|
9
25
|
const closing = /[\p{Pe}\p{Pi}\p{Pf}"'.,;:!?]+$/u;
|
|
10
26
|
const empty = values => values == null || !values.some(value => value !== null && value !== 0);
|
|
@@ -141,7 +157,7 @@ export function* prepareParagraphCandidateSteps(items, options) {
|
|
|
141
157
|
if (joined) {
|
|
142
158
|
for (let at = word.first; at <= word.last; at++) {
|
|
143
159
|
const unit = units[at];
|
|
144
|
-
if (unit.kind !== TEXT
|
|
160
|
+
if (unit.kind !== TEXT) continue;
|
|
145
161
|
const delta = unit.width - measure(unit.start, unit.end);
|
|
146
162
|
if (delta !== 0) contexts.push(Object.freeze({start: unit.start, end: unit.end, delta}));
|
|
147
163
|
if (++measuredEdges % 8 === 0) yield;
|
|
@@ -152,6 +168,7 @@ export function* prepareParagraphCandidateSteps(items, options) {
|
|
|
152
168
|
for (const cut of word.breaks) {
|
|
153
169
|
if (cut.kind === 'soft' || cut.kind === 'auto') {
|
|
154
170
|
cut.glyph = '-';
|
|
171
|
+
if (options.hanging && !word.protected) cut.protrude = parseFontSize(items[units[word.first].itemIndex].font) * .25;
|
|
155
172
|
}
|
|
156
173
|
let before = cut.at, after = cut.at + 1;
|
|
157
174
|
if (cut.kind === 'auto') {
|