rapier-markdown-kit 1.1.93 → 1.1.95
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/dist/kit/render-markdown.mjs +18 -5
- package/dist/kit/render-print.mjs +23 -4
- package/dist/kit/render-sanitize.mjs +18 -2
- package/dist/kit/render-styles.mjs +1 -1
- package/dist/kit/render.mjs +1 -1
- package/dist/layout/model.mjs +3 -3
- package/package.json +1 -1
- package/style.css +2 -0
|
@@ -673,9 +673,9 @@ function _rapierAssignHeadingSlugs(root, selector, reserveExisting) { return fin
|
|
|
673
673
|
function* _rapierAssignHeadingSlugsSteps(root, selector = RAPIER_RENDERED_HEADING_SELECTOR, reserveExisting = true) {
|
|
674
674
|
if (!root || !root.querySelectorAll) return;
|
|
675
675
|
const used = Object.create(null);
|
|
676
|
-
const headings =
|
|
676
|
+
const headings = _rapierDormantHeadings(root, selector, true);
|
|
677
677
|
const headingSet = new Set(headings);
|
|
678
|
-
if (reserveExisting) for (const element of (root.isConnected ? document : root
|
|
678
|
+
if (reserveExisting) for (const element of _rapierDormantHeadings(root.isConnected ? document : root, '[id]', true)) { yield;
|
|
679
679
|
if (!headingSet.has(element) && element.id) used[element.id] = 0;
|
|
680
680
|
}
|
|
681
681
|
for (const heading of headings) { yield;
|
|
@@ -687,8 +687,8 @@ function _rapierDisambiguateRenderedAnchors(root) { return finish(_rapierDisambi
|
|
|
687
687
|
|
|
688
688
|
function* _rapierDisambiguateRenderedAnchorsSteps(root) {
|
|
689
689
|
if (!root || !root.querySelectorAll) return;
|
|
690
|
-
const headings = new Set(root
|
|
691
|
-
const candidates =
|
|
690
|
+
const headings = new Set(_rapierDormantHeadings(root, RAPIER_RENDERED_HEADING_SELECTOR, true));
|
|
691
|
+
const candidates = _rapierDormantHeadings(root, '[id]', true)
|
|
692
692
|
.filter(element => !headings.has(element));
|
|
693
693
|
for (const element of candidates) { yield;
|
|
694
694
|
const original = _rapierRenderedAnchorOriginalIds.get(element);
|
|
@@ -718,7 +718,7 @@ function* _rapierDisambiguateRenderedAnchorsSteps(root) {
|
|
|
718
718
|
const add = (map, key, link) => { if (!map.has(key)) map.set(key, new Set()); map.get(key).add(link); };
|
|
719
719
|
for (const selector of ['.footnote-ref a[href^="#"]', '.footnote-backref[href^="#"]']) {
|
|
720
720
|
const index = {hash: new Map(), label: new Map(), order: new Map()};
|
|
721
|
-
for (const link of root
|
|
721
|
+
for (const link of _rapierDormantHeadings(root, selector, true)) {
|
|
722
722
|
index.order.set(link, index.order.size);
|
|
723
723
|
add(index.hash, _rapierDecodeHash(link.getAttribute('href')), link);
|
|
724
724
|
add(index.label, link.getAttribute('data-footnote-label'), link);
|
|
@@ -1022,6 +1022,11 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1022
1022
|
const root = yield* cloneTree(semanticRoot), nativeElements = new Set();
|
|
1023
1023
|
// A source soft break is a reading space, never the editor's invisible token.
|
|
1024
1024
|
yield* _rapierResolveSoftBreakTokensSteps(root);
|
|
1025
|
+
// Word consumes these source-bearing islands as pictures with TeX alternatives. Keep
|
|
1026
|
+
// their SVG through this projection; the normal export sanitizer still owns admission.
|
|
1027
|
+
if (opts.keepMath) for (const math of root.querySelectorAll('.math-rendered[data-math-src],.math-placeholder,[data-rapier-math-source]')) {
|
|
1028
|
+
for (const element of [math, ...math.querySelectorAll('*')]) nativeElements.add(element);
|
|
1029
|
+
}
|
|
1025
1030
|
if (opts.keepDiagrams) for (const figure of root.querySelectorAll('.diagram-block[data-diagram-native]')) {
|
|
1026
1031
|
const svg = figure.querySelector('.diagram-cache > svg.rapier-native-flowchart');
|
|
1027
1032
|
if (figure.getAttribute('data-diagram-state') === 'ready' && svg) {
|
|
@@ -1049,6 +1054,13 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1049
1054
|
}
|
|
1050
1055
|
|
|
1051
1056
|
for (const wrapper of root.querySelectorAll('.math-display-wrap')) { yield;
|
|
1057
|
+
if (opts.keepMath && wrapper.querySelector('.math-rendered,.math-placeholder,[data-rapier-math-source]')) {
|
|
1058
|
+
const paragraph = document.createElement('p');
|
|
1059
|
+
paragraph.setAttribute('data-md-align', 'center');
|
|
1060
|
+
while (wrapper.firstChild) paragraph.appendChild(wrapper.firstChild);
|
|
1061
|
+
wrapper.replaceWith(paragraph);
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1052
1064
|
const source = _rapierMathSource(wrapper);
|
|
1053
1065
|
const p = document.createElement('p');
|
|
1054
1066
|
const code = document.createElement('code');
|
|
@@ -1057,6 +1069,7 @@ function* _rapierProjectPortableRootSteps(semanticRoot, options) {
|
|
|
1057
1069
|
wrapper.replaceWith(p);
|
|
1058
1070
|
}
|
|
1059
1071
|
for (const math of root.querySelectorAll('.math-rendered,.math-placeholder')) { yield;
|
|
1072
|
+
if (nativeElements.has(math)) continue;
|
|
1060
1073
|
const code = document.createElement('code');
|
|
1061
1074
|
code.textContent = _rapierMathSource(math) || '[equation]';
|
|
1062
1075
|
math.replaceWith(code);
|
|
@@ -247,6 +247,7 @@ async function _rapierWritePrintPdf({root, css, canonical, filename, title, sett
|
|
|
247
247
|
'max-width': 'none', 'min-height': '0', padding: '0', margin: '0', overflow: 'visible',
|
|
248
248
|
'column-width': bodyWidth + 'px', 'column-gap': '0', 'column-count': 'auto', 'column-fill': 'auto'}))
|
|
249
249
|
main.style.setProperty(name, value, 'important');
|
|
250
|
+
globalThis.RapierMath?.fit(main);
|
|
250
251
|
const count = Math.max(1, Math.ceil((main.scrollWidth - 1) / bodyWidth));
|
|
251
252
|
if (!Number.isSafeInteger(count)) refuse('pdf_page_geometry_invalid');
|
|
252
253
|
const textPages = await _rapierPrintTextPages(main, bodyWidth, count, check);
|
|
@@ -310,18 +311,34 @@ async function _rapierWritePrintPdf({root, css, canonical, filename, title, sett
|
|
|
310
311
|
// its line and page boundaries keeps a PDF search on the page that carries those words.
|
|
311
312
|
async function _rapierPrintTextPages(main, pageWidth, count, check) {
|
|
312
313
|
const into = main.ownerDocument, origin = main.getBoundingClientRect();
|
|
313
|
-
const pages = Array.from({length: count}, () => [])
|
|
314
|
+
const pages = Array.from({length: count}, () => []);
|
|
315
|
+
// The renderer's outlines have no text ranges. Read each equation's exact TeX
|
|
316
|
+
// alternative at its SVG box, and reject its subtree so titles cannot duplicate it.
|
|
317
|
+
const walker = into.createTreeWalker(main, 5, {acceptNode(node) {
|
|
318
|
+
if (node.parentElement?.closest('.math-rendered svg[aria-label]')) return 2;
|
|
319
|
+
return node.nodeType === 3 || node.matches('.math-rendered svg[aria-label]') ? 1 : 3;
|
|
320
|
+
}});
|
|
314
321
|
const range = into.createRange();
|
|
322
|
+
const pageOf = x => Math.max(0, Math.min(count - 1, Math.floor((x - origin.left + .01) / pageWidth)));
|
|
315
323
|
let nodes = 0, operations = 0;
|
|
316
324
|
for (let node; (node = walker.nextNode());) {
|
|
317
325
|
if (++nodes % 128 === 0) { await new Promise(resolve => setTimeout(resolve, 0)); check(); }
|
|
318
|
-
const
|
|
326
|
+
const math = node.nodeType === 1;
|
|
327
|
+
const style = into.defaultView.getComputedStyle(math ? node : node.parentElement);
|
|
319
328
|
if (style.visibility === 'hidden' || style.visibility === 'collapse') continue;
|
|
329
|
+
if (math) {
|
|
330
|
+
const rect = node.getBoundingClientRect(), text = node.getAttribute('aria-label');
|
|
331
|
+
if (rect.width > 0 && rect.height > 0 && text) {
|
|
332
|
+
const page = pageOf(rect.left);
|
|
333
|
+
pages[page].push({text, x: rect.left - origin.left - page * pageWidth, y: rect.top - origin.top,
|
|
334
|
+
width: rect.width, height: rect.height, math: true});
|
|
335
|
+
}
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
320
338
|
const preserve = /pre|break-spaces/.test(style.whiteSpace);
|
|
321
339
|
const block = node.parentElement.closest('p,h1,h2,h3,h4,h5,h6,li,pre,td,th,figcaption,blockquote,.rapier-will-carrier') || node.parentElement;
|
|
322
340
|
const carrier = node.parentElement.closest('.rapier-will-carrier');
|
|
323
341
|
const value = node.data;
|
|
324
|
-
const pageOf = x => Math.max(0, Math.min(count - 1, Math.floor((x - origin.left + .01) / pageWidth)));
|
|
325
342
|
const visit = (start, end) => {
|
|
326
343
|
if (++operations % 128 === 0) check();
|
|
327
344
|
range.setStart(node, start); range.setEnd(node, end);
|
|
@@ -340,7 +357,7 @@ async function _rapierPrintTextPages(main, pageWidth, count, check) {
|
|
|
340
357
|
const right = Math.max(...rects.map(rect => rect.right)) - origin.left - page * pageWidth;
|
|
341
358
|
const bottom = Math.max(...rects.map(rect => rect.bottom)) - origin.top;
|
|
342
359
|
const last = pages[page].at(-1);
|
|
343
|
-
if (last && last.block === block && last.carrier === carrier && Math.abs(last.y - y) < Math.max(2, Math.min(last.height, bottom - y) * .2)) {
|
|
360
|
+
if (last && !last.math && last.block === block && last.carrier === carrier && Math.abs(last.y - y) < Math.max(2, Math.min(last.height, bottom - y) * .2)) {
|
|
344
361
|
last.text = preserve ? last.text + text : (last.text + text).replace(/[ \t\r\n\f]+/g, ' ');
|
|
345
362
|
last.width = Math.max(last.x + last.width, right) - Math.min(last.x, x);
|
|
346
363
|
last.height = Math.max(last.y + last.height, bottom) - Math.min(last.y, y);
|
|
@@ -404,7 +421,9 @@ async function writeRasterPdf({pages, textPages, canonical, filename, title, fon
|
|
|
404
421
|
const value = characters.map(character => codes.get(character.codePointAt(0)).toString(16).padStart(4, '0')).join('');
|
|
405
422
|
const size = Math.max(.1, run.height * .75);
|
|
406
423
|
const scale = run.width * .75 / (characters.length * size * .5) * 100;
|
|
424
|
+
if (run.math) commands += '/Span << /ActualText <feff' + hex(run.text) + '> >> BDC\n';
|
|
407
425
|
commands += `BT\n/F1 ${decimal(size)} Tf\n${decimal(scale)} Tz\n3 Tr\n1 0 0 1 ${decimal(margins.left + run.x * .75)} ${decimal(height - margins.top - (run.y + run.height * .8) * .75)} Tm\n<${value}> Tj\nET\n`;
|
|
426
|
+
if (run.math) commands += 'EMC\n';
|
|
408
427
|
}
|
|
409
428
|
if (numberFont) commands += `BT\n/F2 9 Tf\n0 Tr\n1 0 0 1 ${decimal(width / 2 - String(index + 1).length * 2.5)} ${decimal(margins.bottom / 2)} Tm\n(${index + 1}) Tj\nET\n`;
|
|
410
429
|
const content = stream('/Filter /FlateDecode', await compressed(encode(commands)));
|
|
@@ -423,16 +423,32 @@ function _rapierCssPresentationIsRemote(property, value) {
|
|
|
423
423
|
return declaration.length ? _rapierDropRemoteDeclarations(declaration) : false;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
// A reference with no scheme and no host of its own ("photo.png", "#top", each word of a class) resolves on the base's scheme and
|
|
427
|
+
// host whatever its path, so its answer is the base's, found once for each base and page: a long document's render asked
|
|
428
|
+
// this of every token of every attribute, a URL built each time.
|
|
429
|
+
const relativeReference = {base: null, here: null, origin: ''};
|
|
426
430
|
function _rapierRemoteSubresourceOrigin(value) {
|
|
427
431
|
const raw = String(value == null ? '' : value)
|
|
428
432
|
.replace(/[\u0009\u000a\u000d]+/g, '').replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, '');
|
|
429
433
|
if (!raw) return '';
|
|
434
|
+
if (!/^[a-z][a-z0-9+.\-]*:/i.test(raw) && !/^[\\/]{2}/.test(raw)) {
|
|
435
|
+
const base = document.baseURI, here = location.protocol.toLowerCase() + '//' + location.host;
|
|
436
|
+
if (relativeReference.base !== base || relativeReference.here !== here) {
|
|
437
|
+
relativeReference.origin = _rapierRemoteSubresourceOriginOf('a', base, here);
|
|
438
|
+
relativeReference.base = base; relativeReference.here = here;
|
|
439
|
+
}
|
|
440
|
+
return relativeReference.origin;
|
|
441
|
+
}
|
|
442
|
+
return _rapierRemoteSubresourceOriginOf(raw, document.baseURI, location.protocol.toLowerCase() + '//' + location.host);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function _rapierRemoteSubresourceOriginOf(raw, base, here) {
|
|
430
446
|
let url;
|
|
431
|
-
try { url = new URL(raw,
|
|
447
|
+
try { url = new URL(raw, base); } catch (_) { return ''; }
|
|
432
448
|
const host = String(url.host || '');
|
|
433
449
|
if (!host) return '';
|
|
434
450
|
const place = String(url.protocol || '').toLowerCase() + '//' + host;
|
|
435
|
-
return place ===
|
|
451
|
+
return place === here ? '' : place;
|
|
436
452
|
}
|
|
437
453
|
|
|
438
454
|
function _rapierStyleWithoutRemoteUrls(value) {
|
|
@@ -56,7 +56,7 @@ function _rapierDocumentPrintCss(hostSelector) {
|
|
|
56
56
|
${list('.md-render pre','.artifact-flat')}{white-space:pre-wrap!important;overflow-wrap:anywhere!important;word-break:break-word!important;overflow:visible!important;max-height:none!important}
|
|
57
57
|
${p('.artifact-flat')}{margin:0;font:400 12pt/1.55 var(--md-font-sans);tab-size:2}
|
|
58
58
|
${p('.artifact-flat--code')}{font:400 10pt/1.5 var(--md-font-mono)}
|
|
59
|
-
${
|
|
59
|
+
${list('.table-scroll-wrap','.math-display-wrap')}{overflow:visible!important;padding:0!important}
|
|
60
60
|
${list('.table-scroll-wrap table','.md-render table')}{width:100%!important;min-width:0!important;max-width:100%!important;table-layout:auto}
|
|
61
61
|
${list('.md-render th','.md-render td')}{overflow-wrap:anywhere;word-break:normal}
|
|
62
62
|
${p('.md-render thead')}{display:table-header-group}
|
package/dist/kit/render.mjs
CHANGED
|
@@ -48,7 +48,7 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
48
48
|
if (opts.kind === 'standalone') {
|
|
49
49
|
if (context.semanticRoot.querySelector('.math-placeholder'))
|
|
50
50
|
throw new Error('Web page not written; the math plug-in is required to include its equations');
|
|
51
|
-
//
|
|
51
|
+
// Invalid TeX renders as a static error SVG. A source-only span means
|
|
52
52
|
// rendering itself failed, so publishing it would silently omit the equation.
|
|
53
53
|
if (context.semanticRoot.querySelector('span[data-rapier-math-source]'))
|
|
54
54
|
throw new Error('Web page not written; a required equation could not be rendered');
|
package/dist/layout/model.mjs
CHANGED
|
@@ -375,13 +375,13 @@ export function slotsForBand(width, obstacles, top, height, minWidth = 40) {
|
|
|
375
375
|
|
|
376
376
|
// A picture moves a block's words only where its outline (with its standoff) meets them. The block is laid as it lies with no
|
|
377
377
|
// picture beside it, balanced and aligned in the whole column; when no line of words there meets an obstacle, that is its
|
|
378
|
-
// layout. Otherwise its lines flow around the outline. A
|
|
379
|
-
//
|
|
378
|
+
// layout. Otherwise its lines flow around the outline. A short heading can fit beside the outline in a slot narrower
|
|
379
|
+
// than minWidth, so start-aligned, unbalanced words need the same contact check.
|
|
380
380
|
export function flowLines(flow, width, top, obstacles, lineHeight, minWidth, direction = 'ltr', balance = 0, align = 'start') {
|
|
381
381
|
const plan = linePlan(flow, width, top, obstacles, lineHeight, minWidth, direction, balance, slotsForBand, layoutNextRichInlineLineRange, materializeRichInlineLineRange);
|
|
382
382
|
const side = align === 'center' ? 'center' : align === 'left' || align === 'right' ? align
|
|
383
383
|
: (align === 'end') === (direction === 'rtl') ? 'left' : 'right';
|
|
384
|
-
if (!plan || !obstacles?.length
|
|
384
|
+
if (!plan || !obstacles?.length) return plan;
|
|
385
385
|
const free = linePlan(flow, width, top, [], lineHeight, minWidth, direction, balance, slotsForBand, layoutNextRichInlineLineRange, materializeRichInlineLineRange);
|
|
386
386
|
if (!free) return plan;
|
|
387
387
|
const meets = free.lines.some(line => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.95",
|
|
4
4
|
"description": "Read, render and write Self-contained Markdown: one .md file that carries its pictures, drawings and layout, and that any Markdown app can read. HTML renderer, stylesheet, text layout, Will/1 agent permissions, and an edit history with authorship and offline merge. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
package/style.css
CHANGED
|
@@ -467,6 +467,8 @@
|
|
|
467
467
|
-webkit-overflow-scrolling:touch;
|
|
468
468
|
text-align:center;
|
|
469
469
|
}
|
|
470
|
+
.md-render .math-display-wrap:has(svg[data-rapier-math]){overflow:visible}
|
|
471
|
+
.md-render svg[data-rapier-math]{max-width:100%;height:auto}
|
|
470
472
|
.md-render .math-display-wrap>math{
|
|
471
473
|
display:inline-block;
|
|
472
474
|
width:auto;
|