rapier-markdown-kit 1.1.104 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -2
- package/README.md +34 -3
- package/dist/images/gif.mjs +47 -0
- package/dist/images/playback.mjs +101 -0
- package/dist/kit/conformance/03-wrap-around-silhouette.md +1 -1
- package/dist/kit/conformance/04-wrap-box.md +1 -1
- package/dist/kit/conformance/05-behind.md +1 -1
- package/dist/kit/conformance/06-front.md +1 -1
- package/dist/kit/conformance/07-rotate-raster.md +1 -1
- package/dist/kit/conformance/08-drawing-rotation.md +1 -1
- package/dist/kit/conformance/09-drawing-ring-interior.expected.json +18 -18
- package/dist/kit/conformance/09-drawing-ring-interior.md +1 -1
- package/dist/kit/conformance/10-both-sides.md +1 -1
- package/dist/kit/conformance/11-neighbour-skips-image.md +1 -1
- package/dist/kit/conformance/{12-heading-barrier.expected.json → 12-rule-barrier.expected.json} +1 -1
- package/dist/kit/conformance/{12-heading-barrier.md → 12-rule-barrier.md} +2 -2
- package/dist/kit/conformance/README.md +19 -28
- package/dist/kit/conformance/run.mjs +9 -7
- package/dist/kit/ledger/carried.mjs +10 -5
- package/dist/kit/ledger/format.mjs +12 -3
- package/dist/kit/ledger/hash.mjs +31 -15
- package/dist/kit/ledger/history.mjs +17 -5
- package/dist/kit/ledger/merge.mjs +4 -3
- package/dist/kit/pdf-vectors.mjs +4 -1
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-ink.mjs +1 -1
- package/dist/kit/render-markdown.mjs +146 -31
- package/dist/kit/render-print.mjs +131 -55
- package/dist/kit/render-sanitize.mjs +6 -3
- package/dist/kit/render.mjs +34 -77
- package/dist/layout/model.mjs +10 -8
- package/dist/packages/rapier-diff/index.mjs +75 -32
- package/dist/spec/document-fonts.mjs +1 -1
- package/dist/spec/document-settings.mjs +11 -164
- package/dist/spec/html-blocks.mjs +618 -79
- package/dist/spec/md-assets.mjs +29 -6
- package/dist/spec/md-layout.mjs +8 -0
- package/dist/spec/md-marks.mjs +25 -147
- package/dist/spec/md-source.mjs +54 -19
- package/package.json +1 -1
- package/style.css +7 -51
|
@@ -175,7 +175,7 @@ function _rapierPrintWillPageCss() {
|
|
|
175
175
|
// The capture's own limits (agent/visual.mjs VISUAL_LIMITS): the longest edge and the most pixels one capture returns.
|
|
176
176
|
const CAPTURE_EDGE = 4096, CAPTURE_PIXELS = 4 * 1024 * 1024;
|
|
177
177
|
|
|
178
|
-
async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = false, filename, title, settings}, {capture, signal, current, maxBytes = 8 * 1024 * 1024} = {}) {
|
|
178
|
+
async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = false, filename, title, settings}, {capture, signal, current, maxBytes = 8 * 1024 * 1024, film, ink} = {}) {
|
|
179
179
|
const refuse = code => { throw Object.assign(new Error(code), {code}); };
|
|
180
180
|
const check = () => {
|
|
181
181
|
if (signal?.aborted) refuse('cancelled');
|
|
@@ -193,7 +193,7 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
193
193
|
const bodyHeight = Math.floor((height - margins.top - margins.bottom) * 4 / 3);
|
|
194
194
|
if (bodyWidth < 1 || bodyHeight < 1 || bodyWidth * bodyHeight > 4 * 1024 * 1024) refuse('pdf_page_geometry_invalid');
|
|
195
195
|
const frame = document.createElement('iframe');
|
|
196
|
-
let vectors, nativeGlyphs;
|
|
196
|
+
let vectors, nativeGlyphs, filmSession;
|
|
197
197
|
let rejectAbort;
|
|
198
198
|
const aborted = new Promise((_, reject) => { rejectAbort = reject; });
|
|
199
199
|
aborted.catch(() => {});
|
|
@@ -273,6 +273,8 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
273
273
|
globalThis.RapierMath?.fit(main);
|
|
274
274
|
applyPrintCapitals(main);
|
|
275
275
|
applyTypography(main, {all: true});
|
|
276
|
+
// The document's ink marks, drawn on the words where they now stand on the page.
|
|
277
|
+
ink?.(main);
|
|
276
278
|
const count = Math.max(1, Math.ceil((main.scrollWidth - 1) / bodyWidth));
|
|
277
279
|
if (!Number.isSafeInteger(count)) refuse('pdf_page_geometry_invalid');
|
|
278
280
|
// Captured document font bytes own a same-name family; factory glyphs must
|
|
@@ -296,48 +298,59 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
296
298
|
}
|
|
297
299
|
const font = _rapierPrintWillFont(text);
|
|
298
300
|
strip.style.width = count * bodyWidth + 'px';
|
|
299
|
-
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
strip.style.transform = 'none';
|
|
302
|
+
// One styled film is reused for every page window. Batches stay inside the capture edge
|
|
303
|
+
// and pixel limits. A picture no longer forces its own capture. Pixels are deflated once.
|
|
304
|
+
if (typeof film !== 'function') refuse('pdf_render_unavailable');
|
|
305
|
+
// step() can reject after the film resolves (the document changed, or the attempt was
|
|
306
|
+
// cancelled). The session would otherwise keep its abort listener with no owner.
|
|
307
|
+
let pendingFilm = null, abandonFilm = false;
|
|
308
|
+
const trackedFilm = Promise.resolve(film({root: surface, signal, current, frame: {width: count * bodyWidth, height: bodyHeight}})).then(session => {
|
|
309
|
+
if (abandonFilm) session?.close?.();
|
|
310
|
+
else pendingFilm = session;
|
|
311
|
+
return session;
|
|
312
|
+
});
|
|
313
|
+
try {
|
|
314
|
+
filmSession = await step(trackedFilm);
|
|
315
|
+
pendingFilm = null;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
abandonFilm = true;
|
|
318
|
+
pendingFilm?.close?.();
|
|
319
|
+
pendingFilm = null;
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
let share = Math.max(1, Math.min(Math.floor(CAPTURE_EDGE / bodyWidth), Math.floor(CAPTURE_PIXELS / (bodyWidth * bodyHeight))));
|
|
304
323
|
const pages = [];
|
|
305
324
|
let imageBytes = 0;
|
|
325
|
+
const pack = async rgb => {
|
|
326
|
+
const packed = new Uint8Array(await step(new Response(new Blob([rgb]).stream().pipeThrough(new CompressionStream('deflate'))).arrayBuffer()));
|
|
327
|
+
imageBytes += packed.byteLength;
|
|
328
|
+
if (imageBytes > maxBytes) refuse('export_too_large');
|
|
329
|
+
pages.push({rgb: packed});
|
|
330
|
+
};
|
|
306
331
|
for (let first = 0; first < count;) {
|
|
307
332
|
check();
|
|
308
333
|
const span = Math.min(share, count - first);
|
|
309
|
-
surface.style.width = span * bodyWidth + 'px';
|
|
310
|
-
strip.style.transform = `translateX(${-first * bodyWidth}px)`;
|
|
311
|
-
let captured;
|
|
312
|
-
try { captured = await capture({root: surface, clip: {x: 0, y: 0, width: span * bodyWidth, height: bodyHeight}, signal, current}); }
|
|
313
|
-
catch (error) {
|
|
314
|
-
if (error?.code === 'visual_too_large' && span > 1) { share = Math.ceil(span / 2); continue; }
|
|
315
|
-
throw error;
|
|
316
|
-
}
|
|
317
|
-
check();
|
|
318
|
-
const image = new into.defaultView.Image();
|
|
319
|
-
image.src = 'data:' + captured.mimeType + ';base64,' + captured.data;
|
|
320
334
|
try {
|
|
321
|
-
await step(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
context.fillStyle = '#fff'; context.fillRect(0, 0, bodyWidth, bodyHeight);
|
|
328
|
-
context.drawImage(image, offset * bodyWidth, 0, bodyWidth, bodyHeight, 0, 0, bodyWidth, bodyHeight);
|
|
329
|
-
// Preserve the capture's exact pixels. A JPEG re-encode changes
|
|
330
|
-
// glyph edges, pictures and paint even at its highest quality.
|
|
331
|
-
const rgba = context.getImageData(0, 0, bodyWidth, bodyHeight).data;
|
|
335
|
+
const shot = await step(filmSession.raster({x: first * bodyWidth, y: 0, width: span * bodyWidth, height: bodyHeight}));
|
|
336
|
+
if (shot?.width !== span * bodyWidth || shot?.height !== bodyHeight || !(shot.rgb instanceof Uint8Array)) refuse('pdf_render_unavailable');
|
|
337
|
+
if (span === 1) await pack(shot.rgb);
|
|
338
|
+
else {
|
|
339
|
+
const row = bodyWidth * 3;
|
|
340
|
+
for (let offset = 0; offset < span; offset++) {
|
|
332
341
|
const rgb = new Uint8Array(bodyWidth * bodyHeight * 3);
|
|
333
|
-
for (let
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
342
|
+
for (let y = 0; y < bodyHeight; y++) {
|
|
343
|
+
const from = (y * shot.width + offset * bodyWidth) * 3;
|
|
344
|
+
rgb.set(shot.rgb.subarray(from, from + row), y * row);
|
|
345
|
+
}
|
|
346
|
+
await pack(rgb);
|
|
347
|
+
}
|
|
339
348
|
}
|
|
340
|
-
|
|
349
|
+
shot.rgb = null;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (error?.code === 'visual_too_large' && span > 1) { share = Math.ceil(span / 2); continue; }
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
341
354
|
first += span;
|
|
342
355
|
}
|
|
343
356
|
check();
|
|
@@ -345,7 +358,7 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
345
358
|
pixelWidth: bodyWidth, pixelHeight: bodyHeight, pageNumbers: settings?.pagestyle === 'plain', maxBytes, check});
|
|
346
359
|
check();
|
|
347
360
|
return {bytes, pages: count};
|
|
348
|
-
} finally { nativeGlyphs?.restore(); vectors?.restore(); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
361
|
+
} finally { filmSession?.close(); nativeGlyphs?.restore(); vectors?.restore(); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
349
362
|
}
|
|
350
363
|
|
|
351
364
|
// Ranges locate the actual text fragments after column layout. Splitting a text node at
|
|
@@ -355,7 +368,7 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
355
368
|
const pages = Array.from({length: count}, () => []), walker = into.createTreeWalker(main, 5);
|
|
356
369
|
const markerMetrics = _rapierPrintWillMetrics(Array.from(main.querySelectorAll('.rapier-will-carrier'), node => node.textContent));
|
|
357
370
|
const markerLost = () => { throw _rapierPrintWillLost('a marker has no readable position on the page', 'Try another browser'); };
|
|
358
|
-
const range = into.createRange();
|
|
371
|
+
const range = into.createRange(), segmenter = new Intl.Segmenter(undefined, {granularity: 'grapheme'});
|
|
359
372
|
let nodes = 0, operations = 0;
|
|
360
373
|
for (let node; (node = walker.nextNode());) {
|
|
361
374
|
if (++nodes % 128 === 0) { await new Promise(resolve => setTimeout(resolve, 0)); check(); }
|
|
@@ -374,6 +387,7 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
374
387
|
const preserve = /pre|break-spaces/.test(style.whiteSpace);
|
|
375
388
|
const block = element.closest('p,h1,h2,h3,h4,h5,h6,li,pre,td,th,figcaption,blockquote,.rapier-will-carrier') || element;
|
|
376
389
|
const native = nativeNodes?.get(node);
|
|
390
|
+
let characters = null;
|
|
377
391
|
const pageOf = x => Math.max(0, Math.min(count - 1, Math.floor((x - origin.left + .01) / pageWidth)));
|
|
378
392
|
const visit = (start, end) => {
|
|
379
393
|
if (++operations % 128 === 0) check();
|
|
@@ -385,7 +399,8 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
385
399
|
const first = rects[0], page = pageOf(first.left);
|
|
386
400
|
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);
|
|
387
401
|
let middle = formula ? start : Math.floor((start + end) / 2);
|
|
388
|
-
|
|
402
|
+
// A line never breaks inside a character: halve between whole ones (a letter and its marks, a conjunct, an emoji).
|
|
403
|
+
if (middle > start) middle = (characters ||= segmenter.segment(value)).containing(middle).index;
|
|
389
404
|
const cluster = native?.find(item => item.start < middle && item.end > middle);
|
|
390
405
|
if (cluster) middle = cluster.start > start ? cluster.start : cluster.end;
|
|
391
406
|
if (!oneLine && middle > start && middle < end) { visit(start, middle); visit(middle, end); return; }
|
|
@@ -398,7 +413,7 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
398
413
|
if (carrier && right === x) right += Array.from(text).length * markerMetrics.advance * markerMetrics.size / 1000;
|
|
399
414
|
const bottom = Math.max(...rects.map(rect => rect.bottom)) - origin.top;
|
|
400
415
|
const parts = native ? native.filter(item => item.start >= start && item.end <= end && item.text).map(item => ({...item}))
|
|
401
|
-
: [{text, x, y, width: right - x, height: bottom - y}];
|
|
416
|
+
: [{text, x, y, width: right - x, height: bottom - y, rtl: style.direction === 'rtl'}];
|
|
402
417
|
const last = pages[page].at(-1);
|
|
403
418
|
if (last && !formula && !last.math && last.block === block && last.carrier === carrier && Math.abs(last.y - y) < Math.max(2, Math.min(last.height, bottom - y) * .2)) {
|
|
404
419
|
if (!preserve && last.text.endsWith(' ') && text.startsWith(' ') && parts.length) {
|
|
@@ -416,6 +431,24 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
416
431
|
visit(0, value.length);
|
|
417
432
|
}
|
|
418
433
|
check();
|
|
434
|
+
// Trailing-carrier print CSS pulls a last marker up so it stays on the sheet.
|
|
435
|
+
// Keep that paint; put the searchable object after the preceding words so
|
|
436
|
+
// reading order matches the source (the marker itself has no visible ink).
|
|
437
|
+
const pageHeight = origin.height;
|
|
438
|
+
for (const page of pages) {
|
|
439
|
+
let prev = null;
|
|
440
|
+
for (const run of page) {
|
|
441
|
+
if (run.carrier && prev && run.y + 0.25 < prev.y + prev.height * .8) {
|
|
442
|
+
const next = Math.min(pageHeight - 0.5, prev.y + prev.height * .85);
|
|
443
|
+
if (next > run.y) {
|
|
444
|
+
const dy = next - run.y;
|
|
445
|
+
run.y += dy;
|
|
446
|
+
for (const part of run.parts || []) part.y += dy;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
prev = run;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
419
452
|
return pages.map(page => page.map(({block, carrier, ...run}) => run));
|
|
420
453
|
}
|
|
421
454
|
|
|
@@ -450,6 +483,28 @@ function _rapierPrintLinkPages(main, width, height, count) {
|
|
|
450
483
|
return {_rapierWillSentinelPlan, _rapierPrintWillAdmit, _rapierPrintWillFont, _rapierPrintWillMetrics, _rapierPrintWillPlace, _rapierPrintWillLost, _rapierPrintWillPageCss, _rapierWritePrintPdf};
|
|
451
484
|
}
|
|
452
485
|
|
|
486
|
+
// Where each scalar of an invisible text run stands, in character slots from the run's left edge: a mark shares its letter's
|
|
487
|
+
// 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).
|
|
488
|
+
function _rapierPrintSlots(text, rtl) {
|
|
489
|
+
const pieces = [];
|
|
490
|
+
let scalars = 0, count = 0;
|
|
491
|
+
for (const {segment} of new Intl.Segmenter(undefined, {granularity: 'grapheme'}).segment(text)) {
|
|
492
|
+
const dir = /[\p{Script=Arabic}\p{Script=Hebrew}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}]/u.test(segment) ? 'r' : /\p{L}/u.test(segment) ? 'l' : '';
|
|
493
|
+
const cluster = {from: scalars, length: Array.from(segment).length}, last = pieces.at(-1);
|
|
494
|
+
scalars += cluster.length; count++;
|
|
495
|
+
if (last && (!dir || !last.dir || last.dir === dir)) { last.clusters.push(cluster); last.dir ||= dir; }
|
|
496
|
+
else pieces.push({dir, clusters: [cluster]});
|
|
497
|
+
}
|
|
498
|
+
const slots = new Array(scalars);
|
|
499
|
+
let slot = 0;
|
|
500
|
+
for (const piece of rtl ? pieces.reverse() : pieces) {
|
|
501
|
+
for (const cluster of (piece.dir || (rtl ? 'r' : 'l')) === 'r' ? piece.clusters.reverse() : piece.clusters) {
|
|
502
|
+
slots.fill(slot++, cluster.from, cluster.from + cluster.length);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return {slots, count};
|
|
506
|
+
}
|
|
507
|
+
|
|
453
508
|
// A standard PDF page tree, image streams, Unicode text and an associated source file.
|
|
454
509
|
// It accepts already-rendered pages, so there is no second document grammar or layout engine.
|
|
455
510
|
async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [], canonical, bom = false, filename, title, font, width, height, margins, pixelWidth, pixelHeight, pageNumbers, maxBytes, check}) {
|
|
@@ -515,24 +570,31 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
515
570
|
const type = add('<< /Type /Font /Subtype /Type0 /BaseFont /' + name + ' /Encoding /Identity-H /DescendantFonts [' + descendant + ' 0 R] /ToUnicode ' + mapping + ' 0 R >>');
|
|
516
571
|
nativeResources.push(entry.name + ' ' + type + ' 0 R');
|
|
517
572
|
}
|
|
573
|
+
const sharedImages = new Map();
|
|
518
574
|
for (let index = 0; index < pages.length; index++) {
|
|
519
575
|
check();
|
|
520
|
-
const
|
|
576
|
+
const held = pages[index], flate = !!held?.rgb;
|
|
577
|
+
const picture = stream('/Type /XObject /Subtype /Image /Width ' + pixelWidth + ' /Height ' + pixelHeight + ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /' + (flate ? 'FlateDecode' : 'DCTDecode'), flate ? held.rgb : held);
|
|
578
|
+
pages[index] = null;
|
|
521
579
|
let commands = 'q\n' + [decimal(pixelWidth * .75), 0, 0, decimal(pixelHeight * .75), decimal(margins.left), decimal(height - margins.top - pixelHeight * .75)].join(' ') + ' cm\n/Im0 Do\nQ\n';
|
|
522
|
-
const pictures = ['/Im0 ' + picture + ' 0 R'], states = []
|
|
580
|
+
const pictures = ['/Im0 ' + picture + ' 0 R'], states = [];
|
|
523
581
|
for (const drawing of vectorPages[index] || []) {
|
|
524
582
|
commands += `q\n.75 0 0 -.75 ${decimal(margins.left)} ${decimal(height - margins.top)} cm\n0 0 ${pixelWidth} ${pixelHeight} re W n\n1 0 0 1 ${decimal(drawing.x)} 0 cm\n`;
|
|
525
583
|
commands += [drawing.clip.x, drawing.clip.y, drawing.clip.width, drawing.clip.height].map(decimal).join(' ') + ' re W n\n';
|
|
526
584
|
for (const piece of drawing.sequence) {
|
|
527
585
|
if (piece.image) {
|
|
528
|
-
let
|
|
529
|
-
if (!
|
|
586
|
+
let shared = sharedImages.get(piece.image);
|
|
587
|
+
if (!shared) {
|
|
530
588
|
const image = piece.image;
|
|
531
589
|
const mask = stream('/Type /XObject /Subtype /Image /Width ' + image.width + ' /Height ' + image.height + ' /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /FlateDecode', image.alpha);
|
|
532
590
|
const object = stream('/Type /XObject /Subtype /Image /Width ' + image.width + ' /Height ' + image.height + ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /SMask ' + mask + ' 0 R /Filter /FlateDecode', image.rgb);
|
|
533
|
-
|
|
591
|
+
image.alpha = image.rgb = null;
|
|
592
|
+
const name = '/Im' + (sharedImages.size + 1);
|
|
593
|
+
shared = {name, resource: name + ' ' + object + ' 0 R'};
|
|
594
|
+
sharedImages.set(piece.image, shared);
|
|
534
595
|
}
|
|
535
|
-
|
|
596
|
+
if (!pictures.includes(shared.resource)) pictures.push(shared.resource);
|
|
597
|
+
commands += `q\n${[piece.width, 0, 0, -piece.height, piece.x, piece.y + piece.height].map(decimal).join(' ')} cm\n${shared.name} Do\nQ\n`;
|
|
536
598
|
} else {
|
|
537
599
|
const state = '/Gs' + states.length;
|
|
538
600
|
states.push(state + ' ' + add('<< /Type /ExtGState /ca ' + decimal(piece.fillAlpha) + ' /CA ' + decimal(piece.strokeAlpha) + ' >>') + ' 0 R');
|
|
@@ -556,11 +618,12 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
556
618
|
commands += `${state} gs\n${part.rgba.slice(0, 3).map(decimal).join(' ')} rg\n${entry.name} ${decimal(part.size * .75)} Tf\n0 Tr\n1 0 0 1 ${decimal(margins.left + part.x * .75)} ${decimal(height - margins.top - part.baseline * .75)} Tm\n<${value}> Tj\n`;
|
|
557
619
|
} else {
|
|
558
620
|
const characters = Array.from(part.text); if (!characters.length) continue;
|
|
559
|
-
const
|
|
621
|
+
const {slots, count} = _rapierPrintSlots(part.text, part.rtl);
|
|
622
|
+
const advance = part.width * .75 / count, size = Math.max(.1, advance * 2), scaleY = Math.max(.1, part.height * .75) / size;
|
|
560
623
|
const baseline = decimal(height - margins.top - (part.y + part.height * .8) * .75);
|
|
561
624
|
for (let letter = 0; letter < characters.length; letter++) {
|
|
562
625
|
const value = codes.get(characters[letter].codePointAt(0)).toString(16).padStart(4, '0');
|
|
563
|
-
commands += `/F1 ${decimal(size)} Tf\n3 Tr\n1 0 0 ${decimal(scaleY)} ${decimal(margins.left + part.x * .75 + letter * advance)} ${baseline} Tm\n<${value}> Tj\n`;
|
|
626
|
+
commands += `/F1 ${decimal(size)} Tf\n3 Tr\n1 0 0 ${decimal(scaleY)} ${decimal(margins.left + part.x * .75 + slots[letter] * advance)} ${baseline} Tm\n<${value}> Tj\n`;
|
|
564
627
|
}
|
|
565
628
|
}
|
|
566
629
|
}
|
|
@@ -580,14 +643,27 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
580
643
|
put(2, '<< /Type /Pages /Count ' + kids.length + ' /Kids [' + kids.map(id => id + ' 0 R').join(' ') + '] >>');
|
|
581
644
|
put(1, '<< /Type /Catalog /Pages 2 0 R /Names << /EmbeddedFiles << /Names [<feff' + hex(filename) + '> ' + file + ' 0 R] >> >> /AF [' + file + ' 0 R] >>');
|
|
582
645
|
const info = add('<< /Producer (Rapier) /Title <feff' + hex(title || filename.replace(/\.[^.]*$/, '')) + '> >>');
|
|
583
|
-
const
|
|
584
|
-
let
|
|
646
|
+
const header = encode('%PDF-1.7\n%\u00e2\u00e3\u00cf\u00d3\n'), offsets = new Array(objects.length);
|
|
647
|
+
let cursor = header.length;
|
|
648
|
+
for (let id = 1; id < objects.length; id++) {
|
|
649
|
+
offsets[id] = cursor;
|
|
650
|
+
cursor += (id + ' 0 obj\n').length + objects[id].length + 8;
|
|
651
|
+
}
|
|
652
|
+
const xrefAt = cursor;
|
|
653
|
+
const xref = encode('xref\n0 ' + objects.length + '\n0000000000 65535 f \n' + offsets.slice(1).map(at => String(at).padStart(10, '0') + ' 00000 n \n').join('') + 'trailer\n<< /Size ' + objects.length + ' /Root 1 0 R /Info ' + info + ' 0 R >>\nstartxref\n' + xrefAt + '\n%%EOF\n');
|
|
654
|
+
if (cursor + xref.length > maxBytes) fail('export_too_large');
|
|
655
|
+
const bytes = new Uint8Array(cursor + xref.length);
|
|
656
|
+
bytes.set(header, 0);
|
|
657
|
+
let at = header.length;
|
|
585
658
|
for (let id = 1; id < objects.length; id++) {
|
|
586
|
-
|
|
587
|
-
|
|
659
|
+
const prefix = encode(id + ' 0 obj\n');
|
|
660
|
+
bytes.set(prefix, at); at += prefix.length;
|
|
661
|
+
bytes.set(objects[id], at); at += objects[id].length;
|
|
662
|
+
objects[id] = null;
|
|
663
|
+
const suffix = encode('\nendobj\n');
|
|
664
|
+
bytes.set(suffix, at); at += suffix.length;
|
|
588
665
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const bytes = concat(parts); if (bytes.length > maxBytes) fail('export_too_large'); return bytes;
|
|
666
|
+
bytes.set(xref, at);
|
|
667
|
+
return bytes;
|
|
592
668
|
}
|
|
593
669
|
export {createPrintRenderer, applyPrintCapitals, RAPIER_WILL_SENTINEL_MARK as sentinel};
|
|
@@ -7,7 +7,7 @@ const RAPIER_SANITIZE_PROFILES = Object.freeze({
|
|
|
7
7
|
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"]),
|
|
8
8
|
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"]),
|
|
9
9
|
mathTags: Object.freeze(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),
|
|
10
|
-
htmlAttributes: Object.freeze(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","
|
|
10
|
+
htmlAttributes: Object.freeze(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","exportparts","face","for","headers","height","hidden","high","href","hreflang","id","inert","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","part","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","slot","span","srclang","start","src","srcset","step","style","summary","tabindex","title","translate","type","usemap","valign","value","width","wrap","xmlns"]),
|
|
11
11
|
svgAttributes: Object.freeze(["accent-height","accumulate","additive","alignment-baseline","amplitude","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clippathunits","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dominant-baseline","dur","edgemode","elevation","end","exponent","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","intercept","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","mask-type","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","pointer-events","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","slope","specularconstant","specularexponent","spreadmethod","startoffset","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","systemlanguage","tabindex","tablevalues","targetx","targety","transform","transform-origin","text-anchor","text-decoration","text-orientation","text-rendering","textlength","type","u1","u2","unicode","values","vector-effect","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),
|
|
12
12
|
mathAttributes: Object.freeze(["accent","accentunder","align","bevelled","close","columnalign","columnlines","columnspacing","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lquote","lspace","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),
|
|
13
13
|
xmlAttributes: Object.freeze(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),
|
|
@@ -430,9 +430,11 @@ function escapeRapierHtmlText(value) {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
function _rapierChromeOwnsId(id) {
|
|
433
|
-
// Every id the shell's own markup carries, read once before any document renders (initMarkdownIt primes it)
|
|
433
|
+
// Every id the shell's own markup carries, read once before any document renders (initMarkdownIt primes it),
|
|
434
|
+
// and every rapier- id: the chrome makes its dialogs, overlays, parts and plug-in list later, under that prefix
|
|
435
|
+
// (rapier-anchor- is the renderer's own name for a repeated document anchor, and stays the document's).
|
|
434
436
|
if (!_rapierSanitizeRuntime.chromeIds) _rapierSanitizeRuntime.chromeIds = new Set(Array.from(typeof document?.querySelectorAll === 'function' ? document.querySelectorAll('[id]') : [], element => element.id));
|
|
435
|
-
return _rapierSanitizeRuntime.chromeIds.has(String(id));
|
|
437
|
+
return _rapierSanitizeRuntime.chromeIds.has(String(id)) || /^rapier-(?!anchor-)/.test(id);
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
function _rapierSafeRasterDataUrl(value) {
|
|
@@ -522,6 +524,7 @@ function _rapierVerifyRasterBytes(bytes, mime) {
|
|
|
522
524
|
// declaration, doctype or generator/license comment before <svg itself, of whatever length its
|
|
523
525
|
|
|
524
526
|
if (mime === 'image/svg+xml') return /<svg[\s>]/i.test(new TextDecoder('utf-8', {fatal: false}).decode(bytes));
|
|
527
|
+
if (mime === 'image/gif') return bytes.length >= 13 && /^GIF8[79]a$/.test(String.fromCharCode(...bytes.subarray(0, 6)));
|
|
525
528
|
if (mime === 'image/png') return bytes.length >= 8 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47 && bytes[4] === 0x0d && bytes[5] === 0x0a && bytes[6] === 0x1a && bytes[7] === 0x0a;
|
|
526
529
|
if (mime === 'image/jpeg') return bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;
|
|
527
530
|
if (mime === 'image/webp') return bytes.length >= 12 && String.fromCharCode(...bytes.slice(0, 4)) === 'RIFF' && String.fromCharCode(...bytes.slice(8, 12)) === 'WEBP';
|
package/dist/kit/render.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {htmlImageRecord,
|
|
2
|
+
import {htmlImageRecord, htmlRuntimeSource} from '../spec/html-blocks.mjs';
|
|
3
3
|
import {finish, finishAsync, cloneTree, cleanTree, serializeTree, encodeUtf8Steps, pause} from './render-work.mjs';
|
|
4
4
|
import {fontFamilyCss} from '../spec/md-marks.mjs';
|
|
5
5
|
import {readFontAssets, documentFontsCss} from '../spec/document-fonts.mjs';
|
|
@@ -68,7 +68,7 @@ export function positionNativeDiagram(drawing) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function createRenderer(runtime) {
|
|
71
|
-
const {
|
|
71
|
+
const {RapierLedgerCarried, RapierPageReturnAddress, RapierTextCodec, _rapierArtifactHighlight, _rapierArtifactPreference, _rapierArtifactStyles, _rapierBlobDataUrl, _rapierBuildInterchangeContext, _rapierDocumentNameIsAdmissible, _rapierLedgerParts, _rapierDrawReadSVGRecipe, _rapierDrawShapeProfileFor, _rapierExportFontCss, _rapierFillDiagram, _rapierFinalizeExportCode, _rapierLanguageClass, _rapierLineStartOffsets, _rapierMarkdownEnvironment, _rapierPortableHtml, _rapierPrepareInterchangeContext, _rapierProjectPortableRoot, _rapierProviders, _rapierSourceCharEscaped, _rapierSourceLineSpan, crypto, document, escapeRapierHtmlText, globalThis, sanitizeRapierHtml} = runtime;
|
|
72
72
|
let md = runtime.md;
|
|
73
73
|
async function render(source, options = {}) {
|
|
74
74
|
if (typeof source !== 'string') throw new TypeError('Document source must be a string');
|
|
@@ -157,14 +157,16 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
157
157
|
styledRoot.setAttribute('lang', language);
|
|
158
158
|
if (completePage && typeof _rapierFinalizeExportCode === 'function') await _rapierFinalizeExportCode(styledRoot);
|
|
159
159
|
// _rapierProjectStyledRoot already returns the sanitized projection.
|
|
160
|
-
//
|
|
161
|
-
// picture-shape annotations have no consumer in that artifact.
|
|
160
|
+
// Picture-shape annotations have no consumer in a printed artifact.
|
|
162
161
|
if (!opts.print) _rapierAnnotateExportBoxPolygons(styledRoot);
|
|
162
|
+
// Paper keeps each picture's wrap through the same static float layout a page uses.
|
|
163
|
+
if (completePage || opts.print) {
|
|
164
|
+
if (work) await finishAsync(_rapierSharedPageLayoutSteps(styledRoot), work);
|
|
165
|
+
else _rapierSharedPageLayout(styledRoot);
|
|
166
|
+
}
|
|
163
167
|
if (completePage && !opts.print) {
|
|
164
|
-
if (work)
|
|
165
|
-
|
|
166
|
-
await finishAsync(_rapierShareCapLongCodeBlocksSteps(styledRoot), work);
|
|
167
|
-
} else { _rapierSharedPageLayout(styledRoot); _rapierShareCapLongCodeBlocks(styledRoot); }
|
|
168
|
+
if (work) await finishAsync(_rapierShareCapLongCodeBlocksSteps(styledRoot), work);
|
|
169
|
+
else _rapierShareCapLongCodeBlocks(styledRoot);
|
|
168
170
|
}
|
|
169
171
|
if (typeof opts.afterRoot === 'function') await opts.afterRoot(styledRoot);
|
|
170
172
|
markPrintCapitals(styledRoot);
|
|
@@ -185,8 +187,6 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
185
187
|
// rules of its own -- Share, for the no-script float it alone writes -- appends them.
|
|
186
188
|
// Print retains its existing factory resources. HTML adds only the optional small Sans subset below.
|
|
187
189
|
const includeFonts = !!opts.print;
|
|
188
|
-
const zenFontCss = opts.print && styledRoot.querySelector('svg[aria-roledescription="zenuml"]')
|
|
189
|
-
? (_rapierProviders?.mermaid?.exportFontCss?.() || '') : '';
|
|
190
190
|
const willFont = opts.print && opts.willFont ? opts.willFont : null;
|
|
191
191
|
const settingsCss = _rapierDocumentSettingsCss(settings);
|
|
192
192
|
const refined = styledRoot.querySelector('p[data-md-hyphens="auto"],p[data-md-lines],p[data-md-caps="small"],p[data-md-hanging="on"],img[data-rapier-print-lines]');
|
|
@@ -227,7 +227,7 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
227
227
|
// nonce out of it and be admitted.
|
|
228
228
|
const layoutScript = opts.scripts !== false ? _rapierArtifactLayoutScript(styledRoot, nonce, {pictures:!opts.print}) : '';
|
|
229
229
|
const inkScript = !opts.print && opts.scripts !== false ? _rapierArtifactInkScript(styledRoot, nonce) : '';
|
|
230
|
-
const htmlScript = opts.scripts !== false && styledRoot.querySelector('[data-rapier-html]') ? '<script nonce="' + nonce + '">
|
|
230
|
+
const htmlScript = !opts.print && opts.scripts !== false && styledRoot.querySelector('[data-rapier-html],[data-rapier-animation]') ? '<script nonce="' + nonce + '">' + htmlRuntimeSource().replace(/<\/script/gi, '<\\/script') + '</script>' : '';
|
|
231
231
|
const page = '<!DOCTYPE html>\n'
|
|
232
232
|
+ '<html lang="' + escapeRapierHtmlText(language) + '" data-rapier-theme="' + theme + '" data-highlights="' + highlights + '" data-invert-image-alpha="' + (invertImageAlpha ? 'on' : 'off') + '">\n'
|
|
233
233
|
+ '<head>\n<meta charset="UTF-8">\n' + (carrier ? RapierPageReturnAddress.PAGE_SEED + '\n' : '')
|
|
@@ -243,7 +243,7 @@ async function _rapierBuildArtifact(options, providedContext) {
|
|
|
243
243
|
+ '<meta name="generator" content="Rapier ' + escapeRapierHtmlText(version) + '">\n'
|
|
244
244
|
+ '<title>' + escapeRapierHtmlText(pageTitle) + '</title>\n'
|
|
245
245
|
+ '<style>' + css + '</style>\n</head>\n'
|
|
246
|
-
+ '<body class="' + bodyClass + '"><main class="' + docClass + '" data-md-theme="' + theme + '"
|
|
246
|
+
+ '<body class="' + bodyClass + '"><main class="' + docClass + '" data-md-theme="' + theme + '">'
|
|
247
247
|
+ bodyHtml + '</main>' + carrier + layoutScript + inkScript + htmlScript + '</body>\n</html>';
|
|
248
248
|
|
|
249
249
|
return {
|
|
@@ -301,49 +301,6 @@ function* _rapierProjectStyledRootSteps(semanticRoot, options) {
|
|
|
301
301
|
if (lang) code.classList.add('language-' + String(lang).replace(/[^a-z0-9_-]/gi, ''));
|
|
302
302
|
}
|
|
303
303
|
} else {
|
|
304
|
-
if (root.hasAttribute('data-md-space')) {
|
|
305
|
-
// One raw HTML token may have several siblings. Its authored heights stay inside one line-sized box.
|
|
306
|
-
for (let element = root.firstElementChild; element;) {
|
|
307
|
-
const group = element.getAttribute('data-md-html-block');
|
|
308
|
-
if (group === null) { element = element.nextElementSibling; continue; }
|
|
309
|
-
let first = element;
|
|
310
|
-
// Sanitizing a container or repairing a table can leave its text before the first surviving tag.
|
|
311
|
-
while (first.previousSibling && first.previousSibling.nodeType !== 1) { first = first.previousSibling; yield; }
|
|
312
|
-
const wrap = document.createElement('div');
|
|
313
|
-
wrap.className = 'md-space-block';
|
|
314
|
-
root.insertBefore(wrap, first);
|
|
315
|
-
let node = first;
|
|
316
|
-
while (node && (node.nodeType !== 1 || node.getAttribute('data-md-html-block') === group)) {
|
|
317
|
-
const next = node.nextSibling;
|
|
318
|
-
wrap.appendChild(node);
|
|
319
|
-
node = next;
|
|
320
|
-
yield;
|
|
321
|
-
}
|
|
322
|
-
element = wrap.nextElementSibling;
|
|
323
|
-
}
|
|
324
|
-
// A removed raw container can leave only text. Reserve that surviving content in this projection.
|
|
325
|
-
for (let first = root.firstChild; first;) {
|
|
326
|
-
if (first.nodeType === 1) { first = first.nextSibling; continue; }
|
|
327
|
-
let end = first, hasText = false;
|
|
328
|
-
while (end && end.nodeType !== 1) {
|
|
329
|
-
if (end.nodeType === 3 && end.nodeValue.trim()) hasText = true;
|
|
330
|
-
end = end.nextSibling;
|
|
331
|
-
yield;
|
|
332
|
-
}
|
|
333
|
-
if (hasText) {
|
|
334
|
-
const wrap = document.createElement('div');
|
|
335
|
-
wrap.className = 'md-space-block';
|
|
336
|
-
root.insertBefore(wrap, first);
|
|
337
|
-
for (let node = first; node !== end;) {
|
|
338
|
-
const next = node.nextSibling;
|
|
339
|
-
wrap.appendChild(node);
|
|
340
|
-
node = next;
|
|
341
|
-
yield;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
first = end;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
304
|
for (const code of root.querySelectorAll('pre > code')) { yield;
|
|
348
305
|
if (code.parentElement?.classList.contains('diagram-source') &&
|
|
349
306
|
code.closest('.diagram-block')?.getAttribute('data-diagram-state') === 'ready') continue;
|
|
@@ -465,11 +422,15 @@ function _rapierPaintCode(el, html) {
|
|
|
465
422
|
|
|
466
423
|
function _rapierPrefixPortableAnchors(root, baseName) { return finish(_rapierPrefixPortableAnchorsSteps(root, baseName)); }
|
|
467
424
|
|
|
468
|
-
|
|
425
|
+
// The id an anchor carries on a portable page: the document's name, then the anchor with what an id cannot hold made a hyphen.
|
|
426
|
+
function _rapierPortableAnchorId(baseName, id) {
|
|
469
427
|
const slug = String(baseName || 'document').toLowerCase()
|
|
470
428
|
.replace(/[^a-z0-9]+/g, '-')
|
|
471
429
|
.replace(/^-+|-+$/g, '') || 'document';
|
|
472
|
-
|
|
430
|
+
return 'rapier-' + slug + '-' + (String(id).replace(/[^a-z0-9_-]+/gi, '-').replace(/^-+|-+$/g, '') || 'anchor');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function* _rapierPrefixPortableAnchorsSteps(root, baseName) {
|
|
473
434
|
const ids = new Map(), svgIds = new Map();
|
|
474
435
|
const used = new Set();
|
|
475
436
|
const scopeOf = element => {
|
|
@@ -486,11 +447,10 @@ function* _rapierPrefixPortableAnchorsSteps(root, baseName) {
|
|
|
486
447
|
for (const element of root.querySelectorAll('[id]')) { yield;
|
|
487
448
|
const oldId = element.id;
|
|
488
449
|
if (!oldId) continue;
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
let nextId = prefix + base;
|
|
450
|
+
const first = _rapierPortableAnchorId(baseName, oldId);
|
|
451
|
+
let nextId = first;
|
|
492
452
|
let suffix = 2;
|
|
493
|
-
while (used.has(nextId)) nextId =
|
|
453
|
+
while (used.has(nextId)) nextId = first + '-' + suffix++;
|
|
494
454
|
used.add(nextId);
|
|
495
455
|
// A document link resolves the first matching anchor, as it did before renaming.
|
|
496
456
|
// SVG references resolve within their own drawing, never the last repeated drawing.
|
|
@@ -693,13 +653,14 @@ function _rapierSharedResolve(text, ids, images, definitions) {
|
|
|
693
653
|
let broken = false;
|
|
694
654
|
const src = value => {
|
|
695
655
|
const found = images.get(value);
|
|
696
|
-
if (typeof found !== 'string' || !/^data:image\/(?:png|jpeg|webp|svg\+xml|jxl);base64,[A-Za-z0-9+/]+={0,2}$/i.test(found)) { broken = true; return null; }
|
|
656
|
+
if (typeof found !== 'string' || !/^data:image\/(?:png|jpeg|webp|gif|svg\+xml|jxl);base64,[A-Za-z0-9+/]+={0,2}$/i.test(found)) { broken = true; return null; }
|
|
697
657
|
return found;
|
|
698
658
|
};
|
|
699
659
|
// Authored # characters were encoded. A raw id after ]( can only be a structural
|
|
700
660
|
// substitution; matching its destination lets nested alt brackets survive unchanged.
|
|
701
661
|
const inline = new RegExp('(\\]\\([ \\t\\r\\n]*(?:<)?)#' + id + '(?=>?[ \\t\\r\\n]*\\)|>?[ \\t\\r\\n]+["\'(])', 'g');
|
|
702
|
-
|
|
662
|
+
// A definition may sit in a quote or a list item: its line starts with that container's markers.
|
|
663
|
+
const definition = new RegExp('(^[ \\t>]*\\[((?:\\\\.|[^\\]\\\\])+)\\]:[ \\t]*(?:(?:\\r\\n|\\r|\\n)[ \\t>]*)?(?:<)?)#' + id + '(?=>?[ \\t]*$|>?[ \\t]+["\'(])', 'gm');
|
|
703
664
|
const swapInline = (match, lead, value) => { const found = src(value); return found == null ? match : lead + found; };
|
|
704
665
|
const swapDefinition = (match, lead, label, value) => {
|
|
705
666
|
if (!definitions.has(md.utils.normalizeReference(_rapierSharedDecode(label)))) return match;
|
|
@@ -877,7 +838,7 @@ function* _rapierSharedPageLayoutSteps(root) {
|
|
|
877
838
|
// `layout.y` already owns `shape-outside` for its own inset reservation (below); the two are not
|
|
878
839
|
// combinable (a float has exactly one `shape-outside` value), so a `y`-offset `around` picture
|
|
879
840
|
// keeps that inset instead of silhouette wrapping -- a documented, narrow degradation.
|
|
880
|
-
if (layout.wrap === 'around' && !layout.rotate && !layout.y) {
|
|
841
|
+
if (layout.wrap === 'around' && !layout.rotate && !layout.y && !image.dataset.rapierContour?.endsWith('-box')) {
|
|
881
842
|
const src = image.getAttribute('src');
|
|
882
843
|
if (src) {
|
|
883
844
|
paragraph.style.setProperty('shape-outside', 'url("' + src + '")');
|
|
@@ -905,16 +866,12 @@ function* _rapierSharedPageLayoutSteps(root) {
|
|
|
905
866
|
for (const {image, paragraph, layout} of positionedAssignments) {
|
|
906
867
|
yield;
|
|
907
868
|
paragraph.style.cssText = 'position:relative;height:0;margin:0;padding:0;line-height:0';
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
image.style.left = 'calc(' + x + '% - ' + (naturalWidth / 2) + 'px)';
|
|
915
|
-
} else {
|
|
916
|
-
image.style.left = x + '%';
|
|
917
|
-
}
|
|
869
|
+
// Without x the picture starts at the left edge, as everywhere else (layout/model.mjs imageBox).
|
|
870
|
+
const x = layout.x, naturalWidth = Number(image.getAttribute('width')) || 0;
|
|
871
|
+
if (layout.width != null) image.style.width = layout.width + '%';
|
|
872
|
+
else if (naturalWidth > 0) image.style.width = naturalWidth + 'px';
|
|
873
|
+
image.style.left = x == null ? '0' : layout.width != null ? (x - layout.width / 2) + '%'
|
|
874
|
+
: naturalWidth > 0 ? 'calc(' + x + '% - ' + (naturalWidth / 2) + 'px)' : x + '%';
|
|
918
875
|
image.style.position = 'absolute';
|
|
919
876
|
image.style.top = (layout.y || 0) + 'em';
|
|
920
877
|
image.style.margin = '0';
|
|
@@ -1567,6 +1524,7 @@ function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
|
1567
1524
|
// wrap=around reads data-rapier-occupancy, else alpha. Cache key: src, dimensions, layout.wrap, rotate angle.
|
|
1568
1525
|
// A turned raster's profile is recomputed here as pictureProfile does, never shipped.
|
|
1569
1526
|
function profile(image, layout) {
|
|
1527
|
+
if (layout.wrap === 'around' && image.dataset?.rapierContour?.endsWith('-box')) return null;
|
|
1570
1528
|
const src = image.currentSrc || image.getAttribute('src') || '', rotateRad = (layout.rotate || 0) * Math.PI / 180;
|
|
1571
1529
|
const cached = profiles.get(image);
|
|
1572
1530
|
if (cached?.src === src && cached.width === image.naturalWidth && cached.height === image.naturalHeight &&
|
|
@@ -1780,8 +1738,7 @@ function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
|
1780
1738
|
if (image.hasAttribute('data-rapier-html')) {
|
|
1781
1739
|
const attrs = JSON.parse(decodeURIComponent(image.getAttribute('data-rapier-html'))).attrs;
|
|
1782
1740
|
const line = parseFloat(computed.lineHeight) || (parseFloat(computed.fontSize) || 16) * 1.6;
|
|
1783
|
-
unrotated.height =
|
|
1784
|
-
? Number(image.dataset.rapierNaturalHeight) || box(image).height : Number(attrs.height) * line;
|
|
1741
|
+
unrotated.height = metadata.objectHeight(unrotated.width, Number(image.dataset.rapierNaturalHeight) || box(image).height, attrs, line);
|
|
1785
1742
|
}
|
|
1786
1743
|
// rasterReserved (layout/model.mjs).
|
|
1787
1744
|
const rasterRad = (layout.rotate || 0) * Math.PI / 180;
|
|
@@ -1936,6 +1893,6 @@ function _rapierArtifactTypographyScript(root) {
|
|
|
1936
1893
|
'})();';
|
|
1937
1894
|
}
|
|
1938
1895
|
|
|
1939
|
-
return {_rapierDataImageDestinationsAsync, _rapierSharedSourceFormsAsync, _rapierPrefixPortableAnchorsSteps, _rapierProjectStyledRootAsync, _rapierBuildArtifact, _rapierBuildPublishedPage, _rapierProjectStyledRoot, _rapierDocxRenderStyles, _rapierPaintCode, _rapierPrefixPortableAnchors, _rapierDocumentSettingsOf, _rapierDocumentSettingsCss, _rapierExportedPageCsp, _rapierArtifactNonce, _rapierAnnotateExportBoxPolygons, _rapierSharedSourceHash, _rapierSharedSourceForms, _rapierSharedSourceCarrier, _rapierSharedResolve, _rapierReadSharedDocument, _rapierSharedPageFallbackCss, _rapierShareWrapKind, _rapierSharePictureParagraph, _rapierSharedPageLayout, _rapierShareCapLongCodeBlocks, _rapierRequireOfflinePageImages, _rapierCssValueUrls, _rapierBuildSharedPage, _rapierImageSources, _rapierImageDestinations, _rapierScanMarkdownImages, _rapierParseMarkdownImageDestination, _rapierImageOpenRunsList, _rapierArtifactFactoryModules, _rapierProjectArtifactLayout, _rapierArtifactLayoutScript, _rapierArtifactInkScript , render};
|
|
1896
|
+
return {_rapierDataImageDestinationsAsync, _rapierSharedSourceFormsAsync, _rapierPrefixPortableAnchorsSteps, _rapierPortableAnchorId, _rapierProjectStyledRootAsync, _rapierBuildArtifact, _rapierBuildPublishedPage, _rapierProjectStyledRoot, _rapierDocxRenderStyles, _rapierPaintCode, _rapierPrefixPortableAnchors, _rapierDocumentSettingsOf, _rapierDocumentSettingsCss, _rapierExportedPageCsp, _rapierArtifactNonce, _rapierAnnotateExportBoxPolygons, _rapierSharedSourceHash, _rapierSharedSourceForms, _rapierSharedSourceCarrier, _rapierSharedResolve, _rapierReadSharedDocument, _rapierSharedPageFallbackCss, _rapierShareWrapKind, _rapierSharePictureParagraph, _rapierSharedPageLayout, _rapierShareCapLongCodeBlocks, _rapierRequireOfflinePageImages, _rapierCssValueUrls, _rapierBuildSharedPage, _rapierImageSources, _rapierImageDestinations, _rapierScanMarkdownImages, _rapierParseMarkdownImageDestination, _rapierImageOpenRunsList, _rapierArtifactFactoryModules, _rapierProjectArtifactLayout, _rapierArtifactLayoutScript, _rapierArtifactInkScript , render};
|
|
1940
1897
|
}
|
|
1941
1898
|
export {createRenderer};
|