rapier-markdown-kit 1.2.8 → 1.2.9
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/agent/will.mjs +0 -6
- package/dist/images/gif.mjs +0 -3
- package/dist/images/playback.mjs +0 -10
- package/dist/kit/assets.mjs +0 -1
- package/dist/kit/conformance/measurer.mjs +0 -0
- package/dist/kit/conformance/run.mjs +1 -8
- package/dist/kit/export-fonts.mjs +0 -14
- package/dist/kit/ledger/authored-origin.mjs +0 -5
- package/dist/kit/ledger/authorship.mjs +0 -2
- package/dist/kit/ledger/carried.mjs +4 -7
- package/dist/kit/ledger/data.mjs +0 -8
- package/dist/kit/ledger/format.mjs +0 -25
- package/dist/kit/ledger/hash.mjs +11 -7
- package/dist/kit/ledger/history.mjs +10 -29
- package/dist/kit/ledger/inverse-source.mjs +0 -46
- package/dist/kit/ledger/journal-records.mjs +24 -56
- package/dist/kit/ledger/merge.mjs +0 -27
- package/dist/kit/ledger/text-merge.mjs +0 -20
- package/dist/kit/ledger/transport.mjs +0 -47
- package/dist/kit/ledger.mjs +0 -4
- package/dist/kit/markdown-it.mjs +0 -3
- package/dist/kit/pdf-glyphs.mjs +1 -10
- package/dist/kit/pdf-source.mjs +5 -5
- package/dist/kit/pdf-vectors.mjs +0 -3
- package/dist/kit/print-capitals.mjs +0 -4
- package/dist/kit/render-entry.mjs +0 -1
- package/dist/kit/render-ink.mjs +0 -5
- package/dist/kit/render-markdown.mjs +12 -117
- package/dist/kit/render-print.mjs +1 -47
- package/dist/kit/render-sanitize.mjs +7 -59
- package/dist/kit/render-styles.mjs +0 -7
- package/dist/kit/render-work.mjs +0 -12
- package/dist/kit/render.mjs +18 -173
- package/dist/layout/hyphenation.mjs +0 -23
- package/dist/layout/line-plan.mjs +0 -20
- package/dist/layout/model.mjs +0 -55
- package/dist/layout/paragraph-fit.mjs +50 -60
- package/dist/layout/preparation.mjs +23 -56
- package/dist/layout/prepared-paragraph.mjs +0 -25
- package/dist/layout/typography.mjs +103 -41
- package/dist/packages/rapier-diff/concerns.mjs +0 -16
- package/dist/packages/rapier-diff/diff.mjs +0 -7
- package/dist/packages/rapier-diff/index.mjs +0 -1
- package/dist/packages/rapier-diff/matching.mjs +0 -19
- package/dist/packages/rapier-diff/merge.mjs +0 -9
- package/dist/packages/rapier-diff/moves.mjs +0 -7
- package/dist/packages/rapier-diff/structure.mjs +0 -2
- package/dist/packages/rapier-diff/tokens.mjs +0 -6
- package/dist/packages/rapier-diff/walk.mjs +0 -12
- package/dist/spec/document-fonts.mjs +0 -9
- package/dist/spec/document-settings.mjs +4 -23
- package/dist/spec/frontmatter.mjs +0 -1
- package/dist/spec/html-blocks.mjs +0 -36
- package/dist/spec/md-assets.mjs +2 -61
- package/dist/spec/md-layout.mjs +0 -34
- package/dist/spec/md-marks.mjs +0 -51
- package/dist/spec/md-source.mjs +0 -16
- package/dist/spec/word-sidecar.mjs +0 -2
- package/package.json +1 -1
- package/style.css +32 -16
|
@@ -9,7 +9,6 @@ import {stripOneTerminator as _rapierWillStripOneTerminator} from '../agent/will
|
|
|
9
9
|
import {quotedWillSpan} from '../spec/md-marks.mjs';
|
|
10
10
|
const RAPIER_WILL_SENTINEL_MARK = '';
|
|
11
11
|
// SPDX-License-Identifier: MIT
|
|
12
|
-
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
13
12
|
function createPrintRenderer(runtime) {
|
|
14
13
|
const {Node, _rapierWillParse, crypto, document, globalThis} = runtime;
|
|
15
14
|
function _rapierWillSentinelPlan(canonical, kind) {
|
|
@@ -46,8 +45,6 @@ function _rapierPrintWillAdmit(canonical, plan) {
|
|
|
46
45
|
function _rapierPrintWillFont(markers, advanceOverride) {
|
|
47
46
|
const metrics = _rapierPrintWillMetrics(markers);
|
|
48
47
|
const size = metrics.size;
|
|
49
|
-
// The page's carrier face keeps the short marker advance. Searchable text is
|
|
50
|
-
// positioned at 500 units, which is also /DW, so this copy can be asked to match it.
|
|
51
48
|
const advance = advanceOverride ?? metrics.advance;
|
|
52
49
|
const scalars = Array.from(new Set(markers.flatMap(marker => Array.from(marker, ch => ch.codePointAt(0)))));
|
|
53
50
|
const order = scalars.filter(code => code < 0x10000).sort((a, b) => a - b)
|
|
@@ -75,8 +72,6 @@ function _rapierPrintWillFont(markers, advanceOverride) {
|
|
|
75
72
|
const cmap = narrow.length <= 0xffff
|
|
76
73
|
? cat(u16(0), u16(2), u16(3), u16(1), u32(20), u16(3), u16(10), u32(20 + narrow.length), narrow, full)
|
|
77
74
|
: cat(u16(0), u16(1), u16(3), u16(10), u32(12), full);
|
|
78
|
-
// The collinear contour has zero area and enough bounds for native print at 2px.
|
|
79
|
-
// Keep it below the baseline so a carrier at a page break does not straddle both page clips.
|
|
80
75
|
const bounds = [0, -400, 10, 0];
|
|
81
76
|
const outline = cat(u16(1), bounds.map(u16), u16(2), u16(0), [1, 1, 1], u16(0), u16(10), u16(-5), u16(bounds[1]), u16(bounds[3] - bounds[1]), u16((bounds[1] - bounds[3]) / 2), [0]);
|
|
82
77
|
const utf16 = text => Array.from(text, ch => u16(ch.charCodeAt(0)));
|
|
@@ -120,7 +115,6 @@ function _rapierPrintWillMetrics(markers) {
|
|
|
120
115
|
}
|
|
121
116
|
|
|
122
117
|
function _rapierPrintWillPlace(root, plan) {
|
|
123
|
-
// A marker the page holds inside other content (a code block, say) is quoted text, which a reader of the PDF's text could not tell from a carried marker.
|
|
124
118
|
const lost = () => { throw _rapierPrintWillLost('a marker stands inside other content, where a reader of the PDF\'s text could not tell it from a carried marker', 'Take the quoted marker out'); };
|
|
125
119
|
const measure = _rapierPrintWillMetrics(plan.markers);
|
|
126
120
|
const tokens = new Map(plan.markers.map((marker, index) => [plan.sentinel + index + RAPIER_WILL_SENTINEL_MARK, index]));
|
|
@@ -153,8 +147,6 @@ function _rapierPrintWillPlace(root, plan) {
|
|
|
153
147
|
carrier.textContent = plan.markers[row.index];
|
|
154
148
|
return carrier;
|
|
155
149
|
});
|
|
156
|
-
// A rule's own top margin is a line of the sheet's (none at the head of the page, where the sheet takes it off the first child): it
|
|
157
|
-
// moves to the run, so the gap the rule makes with the block above is the one it made.
|
|
158
150
|
if (next && next.tagName === 'HR') {
|
|
159
151
|
if (first.previousElementSibling || first.parentNode !== root) carriers[0].style.marginTop = 'var(--md-line)';
|
|
160
152
|
next.style.marginTop = '0';
|
|
@@ -177,7 +169,6 @@ function _rapierPrintWillPageCss() {
|
|
|
177
169
|
+ '}';
|
|
178
170
|
}
|
|
179
171
|
|
|
180
|
-
// The capture's own limits (agent/visual.mjs VISUAL_LIMITS): the longest edge and the most pixels one capture returns.
|
|
181
172
|
const CAPTURE_EDGE = 4096, CAPTURE_PIXELS = 4 * 1024 * 1024;
|
|
182
173
|
|
|
183
174
|
async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = false, filename, title, settings}, {capture, signal, current, maxBytes = 8 * 1024 * 1024, film, ink} = {}) {
|
|
@@ -187,8 +178,6 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
187
178
|
if (current?.() === false) refuse('document_changed');
|
|
188
179
|
};
|
|
189
180
|
if (!root?.ownerDocument || typeof capture !== 'function') refuse('pdf_render_unavailable');
|
|
190
|
-
// The native printer chooses its default paper. A file made without a printer uses A4,
|
|
191
|
-
// with the same explicit document settings and default margins as the print artifact.
|
|
192
181
|
const paper = settings?.papersize || {width: 11906, height: 16838};
|
|
193
182
|
const width = paper.width / 20, height = paper.height / 20;
|
|
194
183
|
const margin = settings?.geometry ? settings.geometry.twips / 20 : null;
|
|
@@ -221,8 +210,6 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
221
210
|
check();
|
|
222
211
|
const into = frame.contentDocument;
|
|
223
212
|
if (!into?.body) refuse('pdf_render_unavailable');
|
|
224
|
-
// The capture and native printer use standards layout. An initial blank
|
|
225
|
-
// iframe otherwise measures preformatted inline struts in quirks mode.
|
|
226
213
|
into.open(); into.write('<!doctype html><html><head></head><body></body></html>'); into.close();
|
|
227
214
|
into.documentElement.setAttribute('data-rapier-theme', 'light');
|
|
228
215
|
into.documentElement.setAttribute('lang', documentLanguage(settings, document.defaultView?.navigator?.language));
|
|
@@ -246,7 +233,6 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
246
233
|
strip.append(root); surface.append(strip); into.body.append(surface);
|
|
247
234
|
const main = root.querySelector('main');
|
|
248
235
|
if (!main) refuse('pdf_render_unavailable');
|
|
249
|
-
// A file for paper is light whatever the person's own scheme: the page's text takes its colour scheme from this attribute.
|
|
250
236
|
main.setAttribute('data-md-theme', 'light');
|
|
251
237
|
for (const details of main.querySelectorAll('details')) details.open = true;
|
|
252
238
|
main.getBoundingClientRect();
|
|
@@ -254,12 +240,8 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
254
240
|
await step(into.fonts.ready);
|
|
255
241
|
for (const image of main.querySelectorAll('img')) { image.loading = 'eager'; await step(image.decode()); }
|
|
256
242
|
check();
|
|
257
|
-
// The print sheet owns the words and styles. CSS fragmentation supplies the file's
|
|
258
|
-
// pages without a print dialog; its break constraints become column constraints here.
|
|
259
243
|
for (const node of [main, ...main.querySelectorAll('*')]) {
|
|
260
244
|
const style = into.defaultView.getComputedStyle(node);
|
|
261
|
-
// Clipped, out-of-flow source has no page ink. Leaving it in a multicolumn
|
|
262
|
-
// container can still create blank columns and expose its text to PDF search.
|
|
263
245
|
if (!node.closest('.rapier-will-carrier') && ['absolute', 'fixed'].includes(style.position) && /^rect\(/.test(style.clip)) {
|
|
264
246
|
const box = style.clip.slice(5, -1).trim().split(/[,\s]+/).map(Number.parseFloat);
|
|
265
247
|
if (box.length === 4 && box.every(Number.isFinite) && (box[1] <= box[3] || box[2] <= box[0])) {
|
|
@@ -278,12 +260,9 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
278
260
|
globalThis.RapierMath?.fit(main);
|
|
279
261
|
applyPrintCapitals(main);
|
|
280
262
|
applyTypography(main, {all: true});
|
|
281
|
-
// The document's ink marks, drawn on the words where they now stand on the page.
|
|
282
263
|
ink?.(main);
|
|
283
264
|
const count = Math.max(1, Math.ceil((main.scrollWidth - 1) / bodyWidth));
|
|
284
265
|
if (!Number.isSafeInteger(count)) refuse('pdf_page_geometry_invalid');
|
|
285
|
-
// Captured document font bytes own a same-name family; factory glyphs must
|
|
286
|
-
// never replace their page paint. This uses the artifact, not a live catalog.
|
|
287
266
|
const customFamilies = new Set(fontCss ? fontRequests(main, {fontCss, css, settings}).map(face => face.family.toLowerCase()) : []);
|
|
288
267
|
nativeGlyphs = await collectPdfGlyphs(main, {width: bodyWidth, check,
|
|
289
268
|
faces: globalThis.RapierDocxFonts?.DOCX_FONTS?.filter(face => !customFamilies.has(face.family.toLowerCase()))});
|
|
@@ -304,11 +283,7 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
304
283
|
const font = _rapierPrintWillFont(text, 500);
|
|
305
284
|
strip.style.width = count * bodyWidth + 'px';
|
|
306
285
|
strip.style.transform = 'none';
|
|
307
|
-
// One styled film is reused for every page window. Batches stay inside the capture edge
|
|
308
|
-
// and pixel limits. A picture no longer forces its own capture. Pixels are deflated once.
|
|
309
286
|
if (typeof film !== 'function') refuse('pdf_render_unavailable');
|
|
310
|
-
// step() can reject after the film resolves (the document changed, or the attempt was
|
|
311
|
-
// cancelled). The session would otherwise keep its abort listener with no owner.
|
|
312
287
|
let pendingFilm = null, abandonFilm = false;
|
|
313
288
|
const trackedFilm = Promise.resolve(film({root: surface, signal, current, frame: {width: count * bodyWidth, height: bodyHeight}})).then(session => {
|
|
314
289
|
if (abandonFilm) session?.close?.();
|
|
@@ -366,8 +341,6 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
366
341
|
} finally { filmSession?.close(); nativeGlyphs?.restore(); vectors?.restore(); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
367
342
|
}
|
|
368
343
|
|
|
369
|
-
// Ranges locate the actual text fragments after column layout. Splitting a text node at
|
|
370
|
-
// its line and page boundaries keeps a PDF search on the page that carries those words.
|
|
371
344
|
async function _rapierPrintTextPages(main, pageWidth, count, check, origin = main.getBoundingClientRect(), nativeNodes = null) {
|
|
372
345
|
const into = main.ownerDocument;
|
|
373
346
|
const pages = Array.from({length: count}, () => []), walker = into.createTreeWalker(main, 5);
|
|
@@ -382,8 +355,6 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
382
355
|
if (node.nodeType === 1 && !formula || node.nodeType === 3 && element.closest('.math-rendered[data-math-src]')) continue;
|
|
383
356
|
let value;
|
|
384
357
|
if (formula) {
|
|
385
|
-
// The math owner paints SVG paths and retains the equation's exact TeX alternative.
|
|
386
|
-
// Carry that semantic text at the SVG's measured position for search and copy.
|
|
387
358
|
value = formula.getAttribute('aria-label') || '';
|
|
388
359
|
} else value = node.data;
|
|
389
360
|
const style = into.defaultView.getComputedStyle(element);
|
|
@@ -397,14 +368,11 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
397
368
|
const visit = (start, end) => {
|
|
398
369
|
if (++operations % 128 === 0) check();
|
|
399
370
|
if (!formula) { range.setStart(node, start); range.setEnd(node, end); }
|
|
400
|
-
// A tiny carrier font can have its advance rounded to zero by a browser. Its
|
|
401
|
-
// measured baseline still locates the marker; the font's authored metrics keep its text width.
|
|
402
371
|
const rects = Array.from(formula ? [formula.getBoundingClientRect()] : range.getClientRects()).filter(rect => rect.height > 0 && (rect.width > 0 || carrier));
|
|
403
372
|
if (!rects.length) { if (carrier) markerLost(); return; }
|
|
404
373
|
const first = rects[0], page = pageOf(first.left);
|
|
405
374
|
const oneLine = rects.every(rect => pageOf(rect.left) === page && pageOf(rect.width ? rect.right - .02 : rect.left) === page && Math.abs(rect.top - first.top) < .5);
|
|
406
375
|
let middle = formula ? start : Math.floor((start + end) / 2);
|
|
407
|
-
// A line never breaks inside a character: halve between whole ones (a letter and its marks, a conjunct, an emoji).
|
|
408
376
|
if (middle > start) middle = (characters ||= segmenter.segment(value)).containing(middle).index;
|
|
409
377
|
const cluster = native?.find(item => item.start < middle && item.end > middle);
|
|
410
378
|
if (cluster) middle = cluster.start > start ? cluster.start : cluster.end;
|
|
@@ -436,9 +404,6 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
436
404
|
visit(0, value.length);
|
|
437
405
|
}
|
|
438
406
|
check();
|
|
439
|
-
// Trailing-carrier print CSS pulls a last marker up so it stays on the sheet.
|
|
440
|
-
// Keep that paint; put the searchable object after the preceding words so
|
|
441
|
-
// reading order matches the source (the marker itself has no visible ink).
|
|
442
407
|
const pageHeight = origin.height;
|
|
443
408
|
for (const page of pages) {
|
|
444
409
|
let prev = null;
|
|
@@ -454,8 +419,6 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
454
419
|
prev = run;
|
|
455
420
|
}
|
|
456
421
|
}
|
|
457
|
-
// Each run carries the tags of its block's structure (outermost first): a PDF reader finds the document's headings, lists and
|
|
458
|
-
// tables from them rather than from positions.
|
|
459
422
|
const ROLES = {TABLE: 'Table', TR: 'TR', TD: 'TD', TH: 'TH', UL: 'L', OL: 'L', LI: 'LI', BLOCKQUOTE: 'BlockQuote', P: 'P', PRE: 'Code', FIGCAPTION: 'Caption',
|
|
460
423
|
H1: 'H1', H2: 'H2', H3: 'H3', H4: 'H4', H5: 'H5', H6: 'H6'};
|
|
461
424
|
const tags = block => {
|
|
@@ -498,8 +461,6 @@ function _rapierPrintLinkPages(main, width, height, count) {
|
|
|
498
461
|
return {_rapierWillSentinelPlan, _rapierPrintWillAdmit, _rapierPrintWillFont, _rapierPrintWillMetrics, _rapierPrintWillPlace, _rapierPrintWillLost, _rapierPrintWillPageCss, _rapierWritePrintPdf};
|
|
499
462
|
}
|
|
500
463
|
|
|
501
|
-
// Where each scalar of an invisible text run stands, in character slots from the run's left edge: a mark shares its letter's
|
|
502
|
-
// slot, a right-to-left word runs from the right, and so do the words of a right-to-left line (strong letters only; one line).
|
|
503
464
|
function _rapierPrintSlots(text, rtl) {
|
|
504
465
|
const pieces = [];
|
|
505
466
|
let scalars = 0, count = 0;
|
|
@@ -520,8 +481,6 @@ function _rapierPrintSlots(text, rtl) {
|
|
|
520
481
|
return {slots, count};
|
|
521
482
|
}
|
|
522
483
|
|
|
523
|
-
// A standard PDF page tree, image streams, Unicode text and an associated source file.
|
|
524
|
-
// It accepts already-rendered pages, so there is no second document grammar or layout engine.
|
|
525
484
|
async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [], canonical, bom = false, filename, title, font, width, height, margins, pixelWidth, pixelHeight, pageNumbers, maxBytes, check}) {
|
|
526
485
|
const encoder = new TextEncoder(), encode = value => encoder.encode(value);
|
|
527
486
|
const fail = code => { throw Object.assign(new Error(code), {code}); };
|
|
@@ -589,7 +548,6 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
589
548
|
nativeResources.push(entry.name + ' ' + type + ' 0 R');
|
|
590
549
|
}
|
|
591
550
|
const sharedImages = new Map();
|
|
592
|
-
// The structure tree (PDF 1.7, 14.7): one element per tagged DOM element, each run marked with its page's next MCID.
|
|
593
551
|
const structs = new Map(), documentElement = {role: 'Document', kids: []}, parentTree = [];
|
|
594
552
|
const structOf = tags => {
|
|
595
553
|
let parent = documentElement;
|
|
@@ -607,7 +565,6 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
607
565
|
const held = pages[index], flate = !!held?.rgb;
|
|
608
566
|
const picture = stream('/Type /XObject /Subtype /Image /Width ' + pixelWidth + ' /Height ' + pixelHeight + ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /' + (flate ? 'FlateDecode' : 'DCTDecode'), flate ? held.rgb : held);
|
|
609
567
|
pages[index] = null;
|
|
610
|
-
// The page's picture is how its tagged text looks, not a figure of its own: an artifact.
|
|
611
568
|
let commands = '/Artifact BMC\nq\n' + [decimal(pixelWidth * .75), 0, 0, decimal(pixelHeight * .75), decimal(margins.left), decimal(height - margins.top - pixelHeight * .75)].join(' ') + ' cm\n/Im0 Do\nQ\nEMC\n';
|
|
612
569
|
const pictures = ['/Im0 ' + picture + ' 0 R'], states = [];
|
|
613
570
|
for (const drawing of vectorPages[index] || []) {
|
|
@@ -638,9 +595,6 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
638
595
|
const textAlphas = new Map();
|
|
639
596
|
for (const run of textPages[index]) {
|
|
640
597
|
if (!run.text) continue;
|
|
641
|
-
// Keep one logical Unicode text object, resetting its font before each scalar's direction
|
|
642
|
-
// can change. Readers that ignore ActualText otherwise discard earlier words in mixed RTL/LTR.
|
|
643
|
-
// Express width in the font size: some readers ignore horizontal text scaling when joining.
|
|
644
598
|
const element = structOf(run.tags || []);
|
|
645
599
|
if (element) {element.kids.push({page: index, mcid: marked.length}); commands += `/${element.role} << /MCID ${marked.length} >> BDC\n`; marked.push(element);}
|
|
646
600
|
commands += `q\n/Span << /ActualText <feff${hex(run.text)}> >> BDC\nBT\n100 Tz\n`;
|
|
@@ -683,7 +637,7 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
683
637
|
const parents = add('<< /Nums [' + parentTree.map((marked, index) => index + ' [' + marked.map(element => ids.get(element) + ' 0 R').join(' ') + ']').join(' ') + '] >>');
|
|
684
638
|
put(tree, '<< /Type /StructTreeRoot /K [' + ids.get(documentElement) + ' 0 R] /ParentTree ' + parents + ' 0 R /ParentTreeNextKey ' + parentTree.length + ' >>');
|
|
685
639
|
put(1, '<< /Type /Catalog /Pages 2 0 R /MarkInfo << /Marked true >> /StructTreeRoot ' + tree + ' 0 R /Names << /EmbeddedFiles << /Names [<feff' + hex(filename) + '> ' + file + ' 0 R] >> >> /AF [' + file + ' 0 R] >>');
|
|
686
|
-
const info = add('<< /Producer (Rapier) /Title <feff' + hex(title || filename.replace(/\.[^.]*$/, '')) + '> >>');
|
|
640
|
+
const info = add('<< /Producer (Rapier) /Creator (Rapier) /Title <feff' + hex(title || filename.replace(/\.[^.]*$/, '')) + '> >>');
|
|
687
641
|
const header = encode('%PDF-1.7\n%\u00e2\u00e3\u00cf\u00d3\n'), offsets = new Array(objects.length);
|
|
688
642
|
let cursor = header.length;
|
|
689
643
|
for (let id = 1; id < objects.length; id++) {
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {typography} from '../spec/md-marks.mjs';
|
|
3
3
|
const RAPIER_SANITIZE_FORBID_TAGS = Object.freeze(['form', 'style', 'dialog', 'template', 'iframe']);
|
|
4
|
-
// Invokers (DOMPurify 3.4.16 allows them) act on another element by id, and a document shares the page with the editor's own
|
|
5
|
-
// controls: a document's button could open or close them.
|
|
6
4
|
const RAPIER_SANITIZE_FORBID_ATTR = Object.freeze(['action', 'formaction', 'form', 'for', 'command', 'commandfor', 'popover', 'popovertarget', 'popovertargetaction']);
|
|
7
|
-
// The document profiles use one allowlist in a browser and in an inert server parser.
|
|
8
5
|
const RAPIER_SANITIZE_PROFILES = Object.freeze({
|
|
9
6
|
htmlTags: Object.freeze(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","search","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),
|
|
10
7
|
svgTags: Object.freeze(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","enterkeyhint","exportparts","filter","font","g","glyph","glyphref","hkern","image","inputmode","line","lineargradient","marker","mask","metadata","mpath","part","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),
|
|
@@ -56,7 +53,6 @@ const DIAGRAM_LABEL_SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
|
56
53
|
const DIAGRAM_LABEL_HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
57
54
|
const DIAGRAM_LABEL_MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
|
|
58
55
|
|
|
59
|
-
// The profile each DOMPurify instance holds (setConfig), shared by every renderer made over it.
|
|
60
56
|
const purifyConfigured = new WeakMap();
|
|
61
57
|
|
|
62
58
|
function _rapierImageDataAttributeAllowed(tag, attribute, value, verify) {
|
|
@@ -64,9 +60,6 @@ function _rapierImageDataAttributeAllowed(tag, attribute, value, verify) {
|
|
|
64
60
|
&& /^data:image\/(?:png|jpeg|webp|jxl);base64,/i.test(value)) && verify(value);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
// Snapshot current math text before a Markdown writer collapses whitespace. Rendered SVG
|
|
68
|
-
// glyphs have no textContent: put their retained source in this inert copy so the writer's
|
|
69
|
-
// blank-node rule cannot discard an equation or its containing paragraph/list item.
|
|
70
63
|
function prepareMathSource(input, DOMParser) {
|
|
71
64
|
const html = typeof input === 'string';
|
|
72
65
|
const MARK = 'span[data-rapier-math-source],span.math-rendered[data-math-src]';
|
|
@@ -82,18 +75,17 @@ function prepareMathSource(input, DOMParser) {
|
|
|
82
75
|
try { node.textContent = decodeURIComponent(node.getAttribute('data-math-src')); } catch (_) {}
|
|
83
76
|
continue;
|
|
84
77
|
}
|
|
78
|
+
try {
|
|
79
|
+
const held = node.getAttribute('data-rapier-math-source'), source = decodeURIComponent(held);
|
|
80
|
+
if (held && (source === node.textContent || source.replace(/\r\n?/g, '\n').replace(/\0/g, '') === node.textContent)) continue;
|
|
81
|
+
} catch (_) {}
|
|
85
82
|
try { node.setAttribute('data-rapier-math-source', encodeURIComponent(node.textContent || '')); }
|
|
86
83
|
catch (_) { node.removeAttribute('data-rapier-math-source'); }
|
|
87
84
|
}
|
|
88
85
|
return sourceRoot ? root.outerHTML : root;
|
|
89
86
|
}
|
|
90
87
|
|
|
91
|
-
// An inert parser and a string sanitizer use the same profile as DOMPurify. Neither port may
|
|
92
|
-
// execute document scripts or fetch resources. HTML5 parsing precedes filtering so foreign
|
|
93
|
-
// content, entities and malformed table markup cross the same boundary as a browser's parser.
|
|
94
88
|
function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeRasterDataUrl, chromeOwnsId = id => /^rapier-(?!anchor-)/.test(id)}) {
|
|
95
|
-
// chromeOwnsId defaults to _rapierChromeOwnsId's prefix rule: every page Rapier writes carries rapier- elements
|
|
96
|
-
// (rapier-document, rapier-ledger). The factories are serialized into written pages, so neither reads a module constant.
|
|
97
89
|
if (![parseFragment, serialize, sanitizeHtml, safeRasterDataUrl, chromeOwnsId].every(port => typeof port === 'function'))
|
|
98
90
|
throw new TypeError('The inert HTML sanitizer requires every parser and filtering port');
|
|
99
91
|
const uri = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
|
|
@@ -103,8 +95,6 @@ function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeR
|
|
|
103
95
|
const svgTags = new Set(RAPIER_SANITIZE_PROFILES.svgTags), mathTags = new Set(RAPIER_SANITIZE_PROFILES.mathTags);
|
|
104
96
|
const mathIntegration = new Set(['mi', 'mo', 'mn', 'ms', 'mtext']), htmlIntegration = new Set(['annotation-xml']);
|
|
105
97
|
const sharedHtmlSvg = new Set(['title', 'style', 'font', 'a', 'script']);
|
|
106
|
-
// DOMPurify's default namespace admission applies in addition to its tag profile. In
|
|
107
|
-
// particular a MathML name parsed as HTML, or HTML inside SVG desc, is not admitted.
|
|
108
98
|
const validNamespace = (node, parent) => {
|
|
109
99
|
const tag = node.tagName.toLowerCase(), parentTag = (parent.tagName || 'template').toLowerCase();
|
|
110
100
|
const namespace = node.namespaceURI, parentNamespace = parent.namespaceURI || htmlNamespace;
|
|
@@ -158,6 +148,7 @@ function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeR
|
|
|
158
148
|
const key = name.toLowerCase();
|
|
159
149
|
const value = name === 'value' ? inputValue : inputValue.trim();
|
|
160
150
|
if (forbiddenAttributes.has(key) || key.startsWith('on')) continue;
|
|
151
|
+
if (key === 'id' && chromeOwnsId(value)) continue;
|
|
161
152
|
if (/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i.test(value)
|
|
162
153
|
|| key === 'attributename' && value.includes('href')) continue;
|
|
163
154
|
const data = /^data-[\-\w.\u00b7-\uffff]+$/.test(key), aria = /^aria-[\-\w]+$/.test(key);
|
|
@@ -179,13 +170,9 @@ function createInertHtmlSanitizer({parseFragment, serialize, sanitizeHtml, safeR
|
|
|
179
170
|
};
|
|
180
171
|
}
|
|
181
172
|
|
|
182
|
-
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
183
173
|
function createRenderSanitizer(runtime) {
|
|
184
174
|
const {CSSStyleSheet, DOMPurify, RAPIER_RASTER_DATA_URL_RE, URL, _rapierCssDeclaration, _rapierDropRemoteDeclarations, _rapierRemoteContent, _rapierRuleDescriptorIsRemote, _rapierSanitizeRuntime, document, globalThis, location} = runtime;
|
|
185
175
|
|
|
186
|
-
// Only the diagram reader and its styled export admit static foreignObject labels. The
|
|
187
|
-
// ordinary SVG reader still rejects HTML subdocuments. The export class selects scope;
|
|
188
|
-
// every descendant is checked again, so a copied class never confers authority.
|
|
189
176
|
function diagramLabelRoot(node) {
|
|
190
177
|
for (let current = node; current?.nodeType === 1; current = current.parentElement) {
|
|
191
178
|
if (current.localName?.toLowerCase() === 'foreignobject') return current;
|
|
@@ -205,7 +192,6 @@ function diagramLabelStyle(property, value) {
|
|
|
205
192
|
declaration.setProperty(property, value);
|
|
206
193
|
const parsed = declaration.getPropertyValue(property);
|
|
207
194
|
if (!parsed || /(?:url|image-set|element|paint|attr)\s*\(/i.test(parsed)) return false;
|
|
208
|
-
// SVG paint accepts resource references; a custom property must not smuggle one in.
|
|
209
195
|
return !/^(?:fill|stroke)$/.test(property) || !/var\s*\(/i.test(parsed);
|
|
210
196
|
}
|
|
211
197
|
|
|
@@ -279,14 +265,6 @@ function _rapierInstallSanitizeHooks() {
|
|
|
279
265
|
&& _rapierImageDataAttributeAllowed(tag, data.attrName.toLowerCase(), compact, _rapierSafeRasterDataUrl);
|
|
280
266
|
if (!rasterAllowed) data.keepAttr = false;
|
|
281
267
|
});
|
|
282
|
-
// Paste, render and export sanitise: a remote <img src> would fetch the instant its markup lands in the page,
|
|
283
|
-
// so the src the hook below is about to strip never survives. Move it to a data-* attribute first
|
|
284
|
-
// (DOMPurify already leaves data-* alone, and the sanitizer's own inert document never fetches it
|
|
285
|
-
// either), so the picture and its alt text still reach Turndown's rapierImage rule as a live
|
|
286
|
-
// pasted-page picture and write out `` -- not vanish -- and an <img> in a document's own
|
|
287
|
-
// HTML (a table kept as HTML) is written again with its URL: the raw profile admits no data-*, so
|
|
288
|
-
// only the three this hook sets on the node it holds back stay. A picture inside a link is the same
|
|
289
|
-
// node with the same fix: the outer <a> rule reads this img's own converted markdown as its content.
|
|
290
268
|
DOMPurify.addHook('uponSanitizeElement', node => {
|
|
291
269
|
if (_rapierRemoteContent.allowed || !/^(?:paste|render|raw|export)$/.test(_rapierSanitizeRuntime.context)) return;
|
|
292
270
|
if (!node || node.nodeName !== 'IMG' || node.hasAttribute('data-rapier-remote-src')) return;
|
|
@@ -308,18 +286,12 @@ function _rapierInstallSanitizeHooks() {
|
|
|
308
286
|
node.setAttribute('data-rapier-remote-src', href);
|
|
309
287
|
}
|
|
310
288
|
});
|
|
311
|
-
// A list from Word is named by the style of its paragraph and of the span that holds its marker (mso-list,
|
|
312
|
-
// spec/html-reading.mjs), and the CSS pass below empties a style of every property the browser does not
|
|
313
|
-
// know: a marker's style is only `mso-list:Ignore`. The paste carries both as data-* first, where they stay,
|
|
314
|
-
// for the conversion that reads them (_rapierPasteWordLists).
|
|
315
289
|
DOMPurify.addHook('uponSanitizeElement', node => {
|
|
316
290
|
if (_rapierSanitizeRuntime.context !== 'paste' || !node || !/^(?:P|LI|H[1-6]|SPAN)$/.test(node.nodeName)) return;
|
|
317
|
-
// Only the style names them: a page cannot bring these attributes of its own.
|
|
318
291
|
node.removeAttribute('data-rapier-word-level');
|
|
319
292
|
node.removeAttribute('data-rapier-word-marker');
|
|
320
293
|
const style = node.getAttribute('style');
|
|
321
294
|
if (!style || !/mso-list/i.test(style)) return;
|
|
322
|
-
// Word's list reading is the editor's (AGPL) grammar; a host without it keeps the paste's paragraphs as they are.
|
|
323
295
|
const spec = globalThis.RapierMarkdownSpec;
|
|
324
296
|
if (!spec) return;
|
|
325
297
|
const {wordListLevel, wordListIsMarker} = spec;
|
|
@@ -334,9 +306,7 @@ function _rapierInstallSanitizeHooks() {
|
|
|
334
306
|
data.keepAttr = !!data.attrValue;
|
|
335
307
|
return;
|
|
336
308
|
}
|
|
337
|
-
|
|
338
|
-
// allow is about what the page fetches, and this rule is about what the page's chrome finds.
|
|
339
|
-
if (String(data.attrName || '').toLowerCase() === 'id' && _rapierSanitizeRuntime.context === 'raw' && _rapierChromeOwnsId(value)) { data.keepAttr = false; return; }
|
|
309
|
+
if (String(data.attrName || '').toLowerCase() === 'id' && _rapierChromeOwnsId(value)) { data.keepAttr = false; return; }
|
|
340
310
|
if (_rapierRemoteContent.allowed || _rapierSanitizeRuntime.context === 'source') return;
|
|
341
311
|
if (String(data.attrName || '').toLowerCase() === 'style') {
|
|
342
312
|
const cleaned = _rapierStyleWithoutRemoteUrls(value);
|
|
@@ -344,7 +314,6 @@ function _rapierInstallSanitizeHooks() {
|
|
|
344
314
|
return;
|
|
345
315
|
}
|
|
346
316
|
const attribute = String(data.attrName || '').toLowerCase();
|
|
347
|
-
// These attributes carry authored text, never resource requests.
|
|
348
317
|
if (attribute === 'alt' || attribute === 'title') return;
|
|
349
318
|
if (/^data-rapier-remote-(?:src|alt|title)$/.test(attribute) && node && node._rapierHeldBack === true) { data.forceKeepAttr = true; return; }
|
|
350
319
|
if (attribute.startsWith('data-')) return;
|
|
@@ -363,9 +332,6 @@ function _rapierInstallSanitizeHooks() {
|
|
|
363
332
|
data.forceKeepAttr = true;
|
|
364
333
|
}
|
|
365
334
|
});
|
|
366
|
-
// A link that opens a new tab never hands that tab a window.opener or a Referer: the editor's own
|
|
367
|
-
// click router already opens with noopener,noreferrer; the sanitized markup that Share and the
|
|
368
|
-
// standalone export carry gets the same rule in its bytes.
|
|
369
335
|
DOMPurify.addHook('afterSanitizeAttributes', node => {
|
|
370
336
|
if (!node || !/^(?:A|AREA)$/i.test(node.nodeName) || !node.hasAttribute('target')) return;
|
|
371
337
|
if (String(node.getAttribute('target') || '').trim().toLowerCase() !== '_blank') return;
|
|
@@ -387,25 +353,17 @@ function sanitizeRapierHtml(value, context = 'render') {
|
|
|
387
353
|
return sanitize(value, context, false);
|
|
388
354
|
}
|
|
389
355
|
|
|
390
|
-
// The same parser, profile and hooks admit both results. A private renderer can
|
|
391
|
-
// adopt the sanitized nodes without serializing and parsing them again.
|
|
392
356
|
function sanitizeRapierFragment(value, context = 'render') {
|
|
393
357
|
return sanitize(value, context, true);
|
|
394
358
|
}
|
|
395
359
|
|
|
396
360
|
function sanitize(value, context, fragment) {
|
|
397
|
-
// 'export' is the render profile under the export hooks; 'raw' is a document's own HTML (initMarkdownIt, html_block):
|
|
398
|
-
// the render profile without data-* attributes, which Rapier's delegated handlers read as their own controls, and
|
|
399
|
-
// (the hook) without an id the chrome owns. 'source' is Markdown source HTML, written and never shown: the render
|
|
400
|
-
// profile's admission, and the block on remote content stays the reader's (a remote picture's URL is the person's words).
|
|
401
361
|
const selected = context === 'raw' || context === 'export' || context === 'source' || RAPIER_SANITIZE_OPTIONS[context] ? context : 'render';
|
|
402
362
|
const labels = selected === 'diagram' || selected === 'export', key = selected + (fragment ? ' fragment' : '');
|
|
403
363
|
let options = purifyOptions.get(key);
|
|
404
364
|
if (!options) {
|
|
405
365
|
options = selected === 'raw' ? { ...RAPIER_SANITIZE_OPTIONS.render, ALLOW_DATA_ATTR: false } : RAPIER_SANITIZE_OPTIONS[selected] || RAPIER_SANITIZE_OPTIONS.render;
|
|
406
366
|
if (selected === 'source') options = {...options, ADD_DATA_URI_TAGS: ['image']};
|
|
407
|
-
// DOMPurify's USE_PROFILES replaces explicit allowlists. Add MathML to the
|
|
408
|
-
// selected profile without discarding its admitted HTML and SVG vocabulary.
|
|
409
367
|
if (labels) options = {...options, ALLOWED_TAGS: [...new Set([...options.ALLOWED_TAGS, ...RAPIER_SANITIZE_PROFILES.mathTags])],
|
|
410
368
|
ALLOWED_ATTR: [...new Set([...options.ALLOWED_ATTR, ...RAPIER_SANITIZE_PROFILES.mathAttributes])], ADD_TAGS: ['foreignobject'], ADD_DATA_URI_TAGS: ['image'],
|
|
411
369
|
HTML_INTEGRATION_POINTS: {foreignobject: true}, FORBID_TAGS: options.FORBID_TAGS.filter(tag => tag !== 'foreignobject')};
|
|
@@ -417,8 +375,6 @@ function sanitize(value, context, fragment) {
|
|
|
417
375
|
DOMPurify.addHook('uponSanitizeAttribute', diagramLabelAttribute);
|
|
418
376
|
DOMPurify.addHook('afterSanitizeAttributes', diagramLabelContained);
|
|
419
377
|
}
|
|
420
|
-
// DOMPurify reparses a per-call configuration on every call (its cache compares its own copy), about 0.45 ms
|
|
421
|
-
// each in Chrome 155. A profile is set once and kept until another profile is asked for; every caller comes here.
|
|
422
378
|
if (purifyConfigured.get(DOMPurify) !== options) { DOMPurify.setConfig(options); purifyConfigured.set(DOMPurify, options); }
|
|
423
379
|
const previous = _rapierSanitizeRuntime.context;
|
|
424
380
|
_rapierSanitizeRuntime.context = selected;
|
|
@@ -444,15 +400,10 @@ function escapeRapierHtmlText(value) {
|
|
|
444
400
|
}
|
|
445
401
|
|
|
446
402
|
function _rapierChromeOwnsId(id) {
|
|
447
|
-
// Every id the shell's own markup carries, read once before any document renders (initMarkdownIt primes it),
|
|
448
|
-
// and every rapier- id: the chrome makes its dialogs, overlays, parts and plug-in list later, under that prefix
|
|
449
|
-
// (rapier-anchor- is the renderer's own name for a repeated document anchor, and stays the document's).
|
|
450
403
|
if (!_rapierSanitizeRuntime.chromeIds) _rapierSanitizeRuntime.chromeIds = new Set(Array.from(typeof document?.querySelectorAll === 'function' ? document.querySelectorAll('[id]') : [], element => element.id));
|
|
451
404
|
return _rapierSanitizeRuntime.chromeIds.has(String(id)) || /^rapier-(?!anchor-)/.test(id);
|
|
452
405
|
}
|
|
453
406
|
|
|
454
|
-
// A link whose words or address carry a bidi control, or whose host is spelled in punycode, can show a destination it does
|
|
455
|
-
// not have. Opening one names its real address first; the answer is that address, or '' when the link opens at once.
|
|
456
407
|
const RAPIER_BIDI_CONTROL = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/;
|
|
457
408
|
function _rapierLinkDestinationToConfirm(href, text = '') {
|
|
458
409
|
let url, address = String(href || '');
|
|
@@ -484,9 +435,6 @@ function _rapierCssPresentationIsRemote(property, value) {
|
|
|
484
435
|
return declaration.length ? _rapierDropRemoteDeclarations(declaration) : false;
|
|
485
436
|
}
|
|
486
437
|
|
|
487
|
-
// 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
|
|
488
|
-
// host whatever its path, so its answer is the base's, found once for each base and page: a long document's render asked
|
|
489
|
-
// this of every token of every attribute, a URL built each time.
|
|
490
438
|
const relativeReference = {base: null, here: null, origin: ''};
|
|
491
439
|
function _rapierRemoteSubresourceOrigin(value) {
|
|
492
440
|
const raw = String(value == null ? '' : value)
|
|
@@ -544,7 +492,7 @@ function _rapierDropRemoteRules(rules, owner) {
|
|
|
544
492
|
}
|
|
545
493
|
|
|
546
494
|
function _rapierVerifyRasterBytes(bytes, mime) {
|
|
547
|
-
if (mime === 'image/jxl') return globalThis.RapierImageAssets?.isJxl(bytes) === true;
|
|
495
|
+
if (mime === 'image/jxl') return globalThis.RapierImageAssets?.isJxl(bytes) === true;
|
|
548
496
|
|
|
549
497
|
// declaration, doctype or generator/license comment before <svg itself, of whatever length its
|
|
550
498
|
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
3
2
|
function createRenderStyles(runtime) {
|
|
4
3
|
const {_rapierArtifactAccent, _rapierStyleText} = runtime;
|
|
5
4
|
function _rapierArtifactThemeCss(houseDefaults = false) {
|
|
6
|
-
// Geometry and the selected accent belong to this page; palette and type belong to the
|
|
7
5
|
// MIT reference sheet. Static code highlighting reads the app token names.
|
|
8
|
-
// The live editor resolves System to Light before applying the reference heading tokens;
|
|
9
|
-
// a carried System page keeps following the reader's device, including those light tokens.
|
|
10
6
|
const accent = houseDefaults ? '' : ':root,.md-render{--md-color-accent:' + _rapierArtifactAccent() + '}';
|
|
11
7
|
return `
|
|
12
8
|
${accent}
|
|
@@ -82,7 +78,6 @@ function _rapierArtifactStyles(theme, includeFonts, printMode, houseDefaults = f
|
|
|
82
78
|
else if (includeFonts) parts.push(_rapierStyleText('rapier-font-style'));
|
|
83
79
|
parts.push(_rapierStyleText('rapier-content-style'));
|
|
84
80
|
parts.push(_rapierArtifactThemeCss(houseDefaults));
|
|
85
|
-
// Local faces keep an offline fallback. The HTML writer may append a small factory Sans subset.
|
|
86
81
|
if (!includeFonts && fontCss === null) parts.push(`@font-face{font-family:'Geist/Roboto';src:local('Roboto');font-weight:400 500;size-adjust:105%;ascent-override:95.7%;descent-override:28.1%;line-gap-override:0%}
|
|
87
82
|
@font-face{font-family:'Geist/Roboto';src:local('Roboto Bold');font-weight:600 700;size-adjust:105%;ascent-override:95.7%;descent-override:28.1%;line-gap-override:0%}
|
|
88
83
|
@font-face{font-family:'Geist/Helvetica Neue';src:local('Helvetica Neue');font-weight:400 500;size-adjust:103.8%;ascent-override:96.8%;descent-override:28.4%;line-gap-override:0%}
|
|
@@ -102,8 +97,6 @@ function _rapierArtifactStyles(theme, includeFonts, printMode, houseDefaults = f
|
|
|
102
97
|
:root,.md-render{--md-font-sans:'Geist','Geist/Roboto','Geist/Helvetica Neue','Geist/Segoe UI','Geist/Arial','Geist/Noto Sans','Geist/Ubuntu','Geist/Liberation Sans','Geist/DejaVu Sans',system-ui,sans-serif}`);
|
|
103
98
|
const highlightCss = _rapierStyleText('rapier-highlight-style');
|
|
104
99
|
parts.push(highlightCss);
|
|
105
|
-
// A page written for the system theme has no body.light to switch: under a light system
|
|
106
|
-
// setting each palette takes its light values from the sheet's own body.light rule.
|
|
107
100
|
if (theme === 'system') for (const [, palette, light] of highlightCss.matchAll(/html\[data-highlights="(standard|accent)"\] body\.light\s*\{([^}]*)\}/g))
|
|
108
101
|
parts.push('@media (prefers-color-scheme:light){html[data-rapier-theme="system"][data-highlights="' + palette + '"] body{' + light.trim() + '}}');
|
|
109
102
|
if (printMode) parts.push(_rapierArtifactPrintCss());
|
package/dist/kit/render-work.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// The synchronous and cooperative writers consume the same document transformations.
|
|
3
2
|
export function finish(iterator) {
|
|
4
3
|
let next;
|
|
5
4
|
do { next = iterator.next(); } while (!next.done);
|
|
@@ -19,8 +18,6 @@ export async function pause(work, fraction, label) {
|
|
|
19
18
|
check(work);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
// This locates complete fragments; the existing HTML parser and sanitizer still admit
|
|
23
|
-
// every byte. Uncertain nesting stays together instead of guessing a new boundary.
|
|
24
21
|
export function htmlBoundary() {
|
|
25
22
|
const stack = [], voids = new Set('area base br col embed hr img input link meta param source track wbr'.split(' '));
|
|
26
23
|
const rawTags = new Set('script style textarea title xmp iframe noembed noframes'.split(' '));
|
|
@@ -101,9 +98,6 @@ export function* cloneTree(root) {
|
|
|
101
98
|
return copy;
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
// Null entries are traversal checkpoints, never fragment boundaries. A large
|
|
105
|
-
// nested unit is serialized cooperatively but is still admitted as one complete
|
|
106
|
-
// HTML/namespace unit by the synchronous parser and sanitizer.
|
|
107
101
|
export function* htmlParts(root, limit = 16384, consume = false) {
|
|
108
102
|
limit = Math.max(2, limit);
|
|
109
103
|
const text = root.ownerDocument.createElement('div');
|
|
@@ -135,11 +129,7 @@ export function* htmlParts(root, limit = 16384, consume = false) {
|
|
|
135
129
|
if (at.nodeType === 1) {
|
|
136
130
|
if (small(at)) pieces.push(at.outerHTML);
|
|
137
131
|
else {
|
|
138
|
-
// Native shallow serialization owns tag/attribute spelling and
|
|
139
|
-
// escaping. Native text serialization below keeps its parent context.
|
|
140
132
|
const empty = at.cloneNode(false);
|
|
141
|
-
// A foreign element with children must retain its native closing tag
|
|
142
|
-
// even in an inert DOM that self-closes an empty SVG element.
|
|
143
133
|
if (at.firstChild) empty.appendChild(root.ownerDocument.createTextNode(''));
|
|
144
134
|
const shell = empty.outerHTML, close = /<\/[^<>]+>$/.exec(shell)?.[0] || '';
|
|
145
135
|
if (close && at.firstChild) {
|
|
@@ -182,7 +172,6 @@ export function* htmlParts(root, limit = 16384, consume = false) {
|
|
|
182
172
|
export function admitHtml(root, html, sanitize, fragment = null, context = 'export') {
|
|
183
173
|
if (fragment) root.appendChild(fragment(html, context));
|
|
184
174
|
else {
|
|
185
|
-
// Inert string-only hosts retain their explicitly supported admission path.
|
|
186
175
|
const part = root.ownerDocument.createElement('div');
|
|
187
176
|
part.innerHTML = sanitize(html, context);
|
|
188
177
|
while (part.firstChild) root.appendChild(part.firstChild);
|
|
@@ -208,7 +197,6 @@ export function* serializeTree(root, sanitize = null, trim = false) {
|
|
|
208
197
|
return trim ? result.trim() : result;
|
|
209
198
|
}
|
|
210
199
|
|
|
211
|
-
// Chunk boundaries never split a UTF-16 pair; TextEncoder remains the byte authority.
|
|
212
200
|
export function* encodeUtf8Steps(source, size = 65536) {
|
|
213
201
|
const encoder = new TextEncoder(), parts = [];
|
|
214
202
|
let length = 0;
|