rapier-markdown-kit 1.2.3 → 1.2.6
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 +6 -5
- package/README.md +28 -16
- package/dist/images/gif.mjs +3 -1
- package/dist/images/playback.mjs +6 -4
- package/dist/kit/conformance/run.mjs +1 -1
- package/dist/kit/ledger/authored-origin.mjs +49 -0
- package/dist/kit/ledger/authorship.mjs +18 -9
- package/dist/kit/ledger/data.mjs +7 -4
- package/dist/kit/ledger/format.mjs +25 -8
- package/dist/kit/ledger/history.mjs +31 -5
- package/dist/kit/ledger/inverse-source.mjs +111 -49
- package/dist/kit/ledger/journal-records.mjs +24 -29
- package/dist/kit/ledger/merge.mjs +48 -13
- package/dist/kit/ledger/text-merge.mjs +75 -29
- package/dist/kit/ledger/transport.mjs +30 -45
- package/dist/kit/pdf-vectors.mjs +2 -1
- package/dist/kit/print-capitals.mjs +0 -1
- package/dist/kit/rapier-ledger.mjs +2 -0
- package/dist/kit/render-markdown.mjs +12 -3
- package/dist/kit/render-print.mjs +43 -7
- package/dist/kit/render-sanitize.mjs +19 -12
- package/dist/kit/render-styles.mjs +3 -3
- package/dist/kit/render-work.mjs +1 -1
- package/dist/kit/render.mjs +6 -41
- package/dist/layout/paragraph-fit.mjs +1 -15
- package/dist/layout/typography.mjs +2 -1
- package/dist/packages/rapier-diff/concerns.mjs +163 -0
- package/dist/packages/rapier-diff/diff.mjs +68 -0
- package/dist/packages/rapier-diff/index.mjs +4 -744
- package/dist/packages/rapier-diff/matching.mjs +157 -0
- package/dist/packages/rapier-diff/merge.mjs +226 -0
- package/dist/packages/rapier-diff/moves.mjs +130 -0
- package/dist/packages/rapier-diff/structure.mjs +50 -0
- package/dist/packages/rapier-diff/tokens.mjs +72 -0
- package/dist/packages/rapier-diff/walk.mjs +219 -0
- package/dist/spec/html-blocks.mjs +3 -3
- package/dist/spec/md-layout.mjs +1 -1
- package/dist/spec/md-marks.mjs +5 -5
- package/dist/spec/md-source.mjs +15 -7
- package/package.json +1 -1
- package/style.css +11 -11
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Paired source placement, shared by live editing and canonical-history proof.
|
|
3
3
|
import {_rapierTransformSplices, _rapierValidAuthored, _rapierHistoryEffects} from './journal-records.mjs';
|
|
4
4
|
import {projectInverseSource} from './inverse-source.mjs';
|
|
5
|
+
import {authoredBasis, authoredPlacement, authoredPlacements} from './authored-origin.mjs';
|
|
5
6
|
import {sha256} from './hash.mjs';
|
|
6
7
|
import {canonicalJSON} from './data.mjs';
|
|
7
8
|
|
|
@@ -74,6 +75,18 @@ export function transposeRecord(base, splices, id, records) {
|
|
|
74
75
|
if (next === null) throw new RangeError('canonical_transport_invalid');
|
|
75
76
|
current = next;
|
|
76
77
|
}
|
|
78
|
+
// A concurrent act that already removed these same authored units and wrote the
|
|
79
|
+
// same replacement is one physical edit. This act stays in history with no second
|
|
80
|
+
// deletion. Pure insertions are never collapsed: each one is its own unit. An
|
|
81
|
+
// inverse of that act means the replacement is not still in force.
|
|
82
|
+
const sameRows = (left, right) => left.length === right.length && left.every((row, index) =>
|
|
83
|
+
row.pos === right[index].pos && row.removed === right[index].removed && row.inserted === right[index].inserted);
|
|
84
|
+
const matched = crossed.filter(record => sameRows(record.splices, splices));
|
|
85
|
+
const targetsOf = record => record.transaction.sourceTransactionIds || (record.transaction.sourceTransactionId ? [record.transaction.sourceTransactionId] : []);
|
|
86
|
+
const withdrawn = matched.some(record => crossed.some(other => targetsOf(other).includes(record.transaction.id) ||
|
|
87
|
+
other.transaction.reverts === record.transaction.id || other.transaction.reapplies === record.transaction.id));
|
|
88
|
+
if (splices.some(row => row.removed.length) && matched.length && !withdrawn)
|
|
89
|
+
return {splices: [], sourceIndices: [], against: crossed, text: current};
|
|
77
90
|
const moved = [], sourceIndices = [];
|
|
78
91
|
for (const [sourceIndex, row] of splices.entries()) {
|
|
79
92
|
const submitted = _rapierTransformSplices(source, [row]);
|
|
@@ -120,49 +133,7 @@ export function transposeSource(base, splices, client, log) {
|
|
|
120
133
|
return transposeRecord(base, splices, client, records);
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
|
|
124
|
-
// and concurrent predecessors remain separate until an act observes both.
|
|
125
|
-
function authoredFrontier(records, visit = null) {
|
|
126
|
-
const dependencies = new Map(), frontier = new Set();
|
|
127
|
-
for (const record of records) {
|
|
128
|
-
const id = record.transaction.id, basis = record.authored?.basis;
|
|
129
|
-
if (visit) visit(record, basis ?? [...frontier].sort());
|
|
130
|
-
if (!basis) { dependencies.set(id, [...frontier]); frontier.clear(); }
|
|
131
|
-
else {
|
|
132
|
-
const pending = [...basis], seen = new Set();
|
|
133
|
-
while (pending.length) {
|
|
134
|
-
const prior = pending.pop();
|
|
135
|
-
if (seen.has(prior)) continue;
|
|
136
|
-
if (!dependencies.has(prior)) throw new RangeError('canonical_origin_invalid');
|
|
137
|
-
seen.add(prior); frontier.delete(prior); pending.push(...dependencies.get(prior));
|
|
138
|
-
}
|
|
139
|
-
dependencies.set(id, basis);
|
|
140
|
-
}
|
|
141
|
-
frontier.add(id);
|
|
142
|
-
}
|
|
143
|
-
return [...frontier].sort();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function authoredBasis(records) { return authoredFrontier(records); }
|
|
147
|
-
|
|
148
|
-
export function authoredPlacements(base, records) {
|
|
149
|
-
const origins = new Map(); let source = base;
|
|
150
|
-
authoredFrontier(records, (record, basis) => {
|
|
151
|
-
origins.set(record.transaction.id, record.authored ?? authoredPlacement(source, record.splices, basis));
|
|
152
|
-
source = _rapierTransformSplices(source, record.splices);
|
|
153
|
-
if (source === null) throw new RangeError('canonical_origin_invalid');
|
|
154
|
-
});
|
|
155
|
-
return origins;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Immutable authored placement stays on its own act. Basis IDs refer only to this
|
|
159
|
-
// canonical history; no source snapshot or duplicate history is stored.
|
|
160
|
-
export function authoredPlacement(source, splices, basis) {
|
|
161
|
-
const origin = {basis: basis.slice().sort(), source: sha256(source), splices: splices.map(row => ({...row}))};
|
|
162
|
-
if (!_rapierValidAuthored(origin) || _rapierTransformSplices(source, splices) === null)
|
|
163
|
-
throw new RangeError('canonical_origin_invalid');
|
|
164
|
-
return origin;
|
|
165
|
-
}
|
|
136
|
+
export {authoredBasis, authoredPlacement, authoredPlacements};
|
|
166
137
|
|
|
167
138
|
// Reconstruct an authored view from the same records, then prove its exact physical
|
|
168
139
|
// placement. Equality of resulting text alone cannot prove a repeated-text anchor.
|
|
@@ -233,6 +204,19 @@ function authoredReplay(base, records, append = null) {
|
|
|
233
204
|
if (source === null) throw new RangeError('canonical_origin_invalid');
|
|
234
205
|
}
|
|
235
206
|
}
|
|
207
|
+
const coverKey = origin => origin?.splices.some(row => row.removed.length)
|
|
208
|
+
? canonicalJSON({basis: origin.basis, splices: origin.splices}) : null;
|
|
209
|
+
const coverCounts = new Map();
|
|
210
|
+
for (const [id, origin] of origins) {
|
|
211
|
+
if (effects.references.get(id)?.length) continue;
|
|
212
|
+
const key = coverKey(origin);
|
|
213
|
+
if (key) coverCounts.set(key, (coverCounts.get(key) || 0) + 1);
|
|
214
|
+
}
|
|
215
|
+
// Two inverses can name different acts and still withdraw one physical replacement.
|
|
216
|
+
const sharesCover = id => (effects.references.get(id) || []).some(ref => {
|
|
217
|
+
const key = coverKey(origins.get(ref));
|
|
218
|
+
return !!key && coverCounts.get(key) > 1;
|
|
219
|
+
});
|
|
236
220
|
const ordered = (ids, selected = null) => {
|
|
237
221
|
const order = [], known = new Set(ids), waiting = new Map(), dependents = new Map(), ready = [];
|
|
238
222
|
const rank = id => selected ? Number(selected.has(id)) : Number(origins.get(id).splices.some(row => row.removed.length));
|
|
@@ -351,10 +335,11 @@ function authoredReplay(base, records, append = null) {
|
|
|
351
335
|
if (aligned.text !== text) throw new RangeError('canonical_origin_invalid');
|
|
352
336
|
const crossed = aligned.records.slice(basisOrder.length);
|
|
353
337
|
let moved;
|
|
354
|
-
if (
|
|
338
|
+
if (effects.references.get(id)?.length && (sharedInverses || sharesCover(id))) {
|
|
355
339
|
// Each inverse retains its own targets. First prove the exact intention
|
|
356
340
|
// in its causal source, then place that choice in the accepted effect
|
|
357
|
-
// graph. Another inverse may already suppress the same original bytes
|
|
341
|
+
// graph. Another inverse may already suppress the same original bytes,
|
|
342
|
+
// including when the two inverses name different authors of one replacement.
|
|
358
343
|
const authored = projectInverse(basis.text, basis.records, id);
|
|
359
344
|
if (!same(origin.splices, authored.splices) ||
|
|
360
345
|
_rapierTransformSplices(basis.text, origin.splices) !== authored.after)
|
package/dist/kit/pdf-vectors.mjs
CHANGED
|
@@ -132,7 +132,8 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
|
|
|
132
132
|
root = document.importNode(parsed.documentElement, true);
|
|
133
133
|
root.style.setProperty('width', originalBox.width + 'px', 'important'); root.style.setProperty('height', originalBox.height + 'px', 'important');
|
|
134
134
|
mount = document.createElement('div'); mount.style.cssText = `position:fixed;left:${originalBox.left}px;top:${originalBox.top}px;width:${originalBox.width}px;height:${originalBox.height}px;pointer-events:none`;
|
|
135
|
-
|
|
135
|
+
// A shadow root keeps the picture's own <style> on the picture, as an <img> does.
|
|
136
|
+
mount.attachShadow({mode: 'open'}).append(root); document.body.append(mount); temporary.push(mount); await document.fonts.ready;
|
|
136
137
|
textRoots.push(root);
|
|
137
138
|
}
|
|
138
139
|
const box = root.getBoundingClientRect(), nodes = [root, ...root.querySelectorAll('*')], sequence = [];
|
|
@@ -82,4 +82,3 @@ export function createPrintCapitals(lineMetrics, linesHeight, followingProse) {
|
|
|
82
82
|
|
|
83
83
|
const capitals = createPrintCapitals(lineMetrics, linesHeight, followingPrintProse);
|
|
84
84
|
export const applyPrintCapitals = capitals.apply;
|
|
85
|
-
export const clearPrintCapitals = capitals.clear;
|
|
@@ -10,6 +10,8 @@ export {_rapierTransformSplices, _rapierRecordSplices, _rapierValidLedgerRecord,
|
|
|
10
10
|
import {replayHistory, sourceBefore, historyProjection, historyProjections, historyPlaces, selectiveUndo, groupHistoryActs, pictureHistorySplices} from './ledger/history.mjs';
|
|
11
11
|
export {replayHistory, sourceBefore, historyProjection, historyProjections, historyPlaces, selectiveUndo, groupHistoryActs, pictureHistorySplices};
|
|
12
12
|
|
|
13
|
+
import {sha256State} from './ledger/hash.mjs';
|
|
14
|
+
export {sha256State};
|
|
13
15
|
import {transposeSource} from './ledger/transport.mjs';
|
|
14
16
|
export {transposeSource};
|
|
15
17
|
|
|
@@ -325,7 +325,7 @@ function initMarkdownIt() {
|
|
|
325
325
|
const provider = env.rapierProviders === false ? null : _rapierProviders.mermaid;
|
|
326
326
|
const native = globalThis.RapierFlowchart?.parseFlowchart(src).ok === true;
|
|
327
327
|
const ready = native || !!(provider && provider.status === 'ready' && typeof provider.renderToString === 'function');
|
|
328
|
-
if (!ready && env.rapierProviders !== false && !_diagramPromptedOnce) {
|
|
328
|
+
if (!ready && !provider?.request?.() && env.rapierProviders !== false && !_diagramPromptedOnce) {
|
|
329
329
|
_diagramPromptedOnce = true;
|
|
330
330
|
try { _rapierUiDiagram.request(); } catch (_) {}
|
|
331
331
|
}
|
|
@@ -366,7 +366,7 @@ function initMarkdownIt() {
|
|
|
366
366
|
if (env.rapierPage) return '<span data-rapier-math-source="">' + literal + '</span>';
|
|
367
367
|
const mathProvider = env.rapierProviders === false ? null : _rapierProviders.math;
|
|
368
368
|
if (!mathProvider || mathProvider.status !== 'ready' || typeof mathProvider.renderToString !== 'function') {
|
|
369
|
-
if (env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
369
|
+
if (!mathProvider?.request?.() && env.rapierProviders !== false && !_mathPromptedOnce) {
|
|
370
370
|
_mathPromptedOnce = true;
|
|
371
371
|
try { _rapierUiMath.request(); } catch (_) {}
|
|
372
372
|
}
|
|
@@ -1716,6 +1716,15 @@ function* _rapierResolveSoftBreakTokensSteps(root) {
|
|
|
1716
1716
|
return root;
|
|
1717
1717
|
}
|
|
1718
1718
|
|
|
1719
|
+
// Markup parsed in a document of its own, which runs no script and loads nothing (Copy's clipboard tree): one element per
|
|
1720
|
+
// markup, or no boxes where the joined parse did not keep them apart.
|
|
1721
|
+
function _rapierInertParse(markups) {
|
|
1722
|
+
const doc = new DOMParser().parseFromString(markups.map((html, at) => '<div data-rapier-batch="' + at + '">' + html + '</div>').join(''), 'text/html');
|
|
1723
|
+
const boxes = [...doc.body.childNodes];
|
|
1724
|
+
const intact = boxes.length === markups.length && boxes.every((box, at) => box.getAttribute?.('data-rapier-batch') === String(at));
|
|
1725
|
+
return {doc, boxes: intact ? boxes : null};
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1719
1728
|
function _rapierUnwrapElement(element) {
|
|
1720
1729
|
while (element.firstChild) element.parentNode.insertBefore(element.firstChild, element);
|
|
1721
1730
|
element.remove();
|
|
@@ -1742,7 +1751,7 @@ function renderWithMathML(source, metadata = {}) {
|
|
|
1742
1751
|
return {html, equations};
|
|
1743
1752
|
}
|
|
1744
1753
|
|
|
1745
|
-
return {renderWithMathML, _rapierClipboardPayloadAsync, _rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierUnwrapElement, _rapierBidiStrong, render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierApplyImageGeometry, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
|
|
1754
|
+
return {renderWithMathML, _rapierClipboardPayloadAsync, _rapierPlainTextFileAsync, _rapierCloneRootAsync, _rapierPortablePlainTextAsync, _rapierHeldPicturesAsWordsAsync, _rapierRenderSemanticRootAsync, _rapierProjectPortableRootAsync, _rapierPortableHtmlAsync, _rapierCalloutInfo, _rapierRemoveCalloutMarker, _rapierMathSource, _rapierFlattenPortableDetails, _rapierProjectPortableRoot, _rapierPortableHtml, _rapierNormalizeInlineText, _rapierPortableInlineText, _rapierPortableListText, _rapierPortableTableText, _rapierPortablePlainText, _rapierPlainTextFile, _rapierHeldPicturesAsWords, _rapierSourceTokenValue, _rapierResolveSoftBreakTokens, _rapierInertParse, _rapierUnwrapElement, _rapierBidiStrong, render: (source, metadata = {}) => { if (!md) initMarkdownIt(); return _rapierRenderSemanticRoot(source, metadata).innerHTML; }, initMarkdownIt, _rapierRenderSemanticRoot, _rapierInstallSemanticProbe, _rapierApplyImageGeometry, _rapierImageAltSize, _rapierImageAltSourceParts, _rapierImageAltText, _rapierImageSizeAttributes, _rapierRawHtmlFragment, _rapierApplyBlockDirection, _rapierFirstStrongDir, _rapierAssignHeadingSlugs, _rapierDisambiguateRenderedAnchors, _rapierDecodeHash, _rapierDecorateJumpLists, _rapierMarkFigureCaptions, _rapierMarkListContinuations, _rapierMarkTableCaptions, _rapierNormalizeCodeElement, _rapierLanguageClass, _relabelFootnoteHtml, _footnoteIdFromLabel, wrapDisplayMath, parser: () => md};
|
|
1746
1755
|
}
|
|
1747
1756
|
export {RAPIER_TABLE_CAPTION_PREFIX_RE as tableCaptionPrefix, RAPIER_PORTABLE_HIGHLIGHT_HEX as portableHighlightHex, _RAPIER_RTL_SCRIPT as rtlScripts, createMarkdownRenderer};
|
|
1748
1757
|
|
|
@@ -75,7 +75,7 @@ function _rapierPrintWillFont(markers) {
|
|
|
75
75
|
const bounds = [0, -400, 10, 0];
|
|
76
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]);
|
|
77
77
|
const utf16 = text => Array.from(text, ch => u16(ch.charCodeAt(0)));
|
|
78
|
-
const names = [[1, '
|
|
78
|
+
const names = [[1, 'RapierWillCarrier'], [2, 'Regular'], [4, 'RapierWillCarrier Regular'], [6, 'RapierWillCarrier-Regular']];
|
|
79
79
|
let offset = 0;
|
|
80
80
|
const tables = {
|
|
81
81
|
'OS/2': cat(u16(4), u16(advance), u16(400), u16(5), u16(0), u16(650), u16(600), u16(0), u16(75), u16(650), u16(600), u16(0), u16(350),
|
|
@@ -449,7 +449,17 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
449
449
|
prev = run;
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
|
-
|
|
452
|
+
// Each run carries the tags of its block's structure (outermost first): a PDF reader finds the document's headings, lists and
|
|
453
|
+
// tables from them rather than from positions.
|
|
454
|
+
const ROLES = {TABLE: 'Table', TR: 'TR', TD: 'TD', TH: 'TH', UL: 'L', OL: 'L', LI: 'LI', BLOCKQUOTE: 'BlockQuote', P: 'P', PRE: 'Code', FIGCAPTION: 'Caption',
|
|
455
|
+
H1: 'H1', H2: 'H2', H3: 'H3', H4: 'H4', H5: 'H5', H6: 'H6'};
|
|
456
|
+
const tags = block => {
|
|
457
|
+
const chain = [];
|
|
458
|
+
for (let node = block; node && node !== main; node = node.parentElement)
|
|
459
|
+
if (ROLES[node.tagName] || node === block && node.matches('.rapier-will-carrier')) chain.unshift({node, role: ROLES[node.tagName] || 'P'});
|
|
460
|
+
return chain;
|
|
461
|
+
};
|
|
462
|
+
return pages.map(page => page.map(({block, ...run}) => ({...run, tags: tags(block)})));
|
|
453
463
|
}
|
|
454
464
|
|
|
455
465
|
function _rapierPrintLinkPages(main, width, height, count) {
|
|
@@ -542,6 +552,9 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
542
552
|
const descriptor = add('<< /Type /FontDescriptor /FontName /RapierText /Flags 4 /FontBBox [' + font.bounds.join(' ') + '] /ItalicAngle 0 /Ascent 500 /Descent -500 /CapHeight 500 /StemV 80 /FontFile2 ' + fontFile + ' 0 R >>');
|
|
543
553
|
const descendant = add('<< /Type /Font /Subtype /CIDFontType2 /BaseFont /RapierText /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor ' + descriptor + ' 0 R /DW 500 /CIDToGIDMap /Identity >>');
|
|
544
554
|
const type = add('<< /Type /Font /Subtype /Type0 /BaseFont /RapierText /Encoding /Identity-H /DescendantFonts [' + descendant + ' 0 R] /ToUnicode ' + mapping + ' 0 R >>');
|
|
555
|
+
const carrierDescriptor = add('<< /Type /FontDescriptor /FontName /RapierWillCarrier /Flags 4 /FontBBox [' + font.bounds.join(' ') + '] /ItalicAngle 0 /Ascent 500 /Descent -500 /CapHeight 500 /StemV 80 /FontFile2 ' + fontFile + ' 0 R >>');
|
|
556
|
+
const carrierDescendant = add('<< /Type /Font /Subtype /CIDFontType2 /BaseFont /RapierWillCarrier /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor ' + carrierDescriptor + ' 0 R /DW 500 /CIDToGIDMap /Identity >>');
|
|
557
|
+
const carrierType = add('<< /Type /Font /Subtype /Type0 /BaseFont /RapierWillCarrier /Encoding /Identity-H /DescendantFonts [' + carrierDescendant + ' 0 R] /ToUnicode ' + mapping + ' 0 R >>');
|
|
545
558
|
const numberFont = pageNumbers ? add('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>') : null;
|
|
546
559
|
const kids = pages.map(() => { objects.push(null); return objects.length - 1; }), decimal = value => Number(value.toFixed(4));
|
|
547
560
|
const nativeFonts = new Map();
|
|
@@ -571,12 +584,26 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
571
584
|
nativeResources.push(entry.name + ' ' + type + ' 0 R');
|
|
572
585
|
}
|
|
573
586
|
const sharedImages = new Map();
|
|
587
|
+
// The structure tree (PDF 1.7, 14.7): one element per tagged DOM element, each run marked with its page's next MCID.
|
|
588
|
+
const structs = new Map(), documentElement = {role: 'Document', kids: []}, parentTree = [];
|
|
589
|
+
const structOf = tags => {
|
|
590
|
+
let parent = documentElement;
|
|
591
|
+
for (const {node, role} of tags) {
|
|
592
|
+
let element = structs.get(node);
|
|
593
|
+
if (!element) {element = {role, kids: [], parent}; structs.set(node, element); parent.kids.push(element);}
|
|
594
|
+
parent = element;
|
|
595
|
+
}
|
|
596
|
+
return parent === documentElement ? null : parent;
|
|
597
|
+
};
|
|
574
598
|
for (let index = 0; index < pages.length; index++) {
|
|
575
599
|
check();
|
|
600
|
+
const marked = [];
|
|
601
|
+
parentTree.push(marked);
|
|
576
602
|
const held = pages[index], flate = !!held?.rgb;
|
|
577
603
|
const picture = stream('/Type /XObject /Subtype /Image /Width ' + pixelWidth + ' /Height ' + pixelHeight + ' /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /' + (flate ? 'FlateDecode' : 'DCTDecode'), flate ? held.rgb : held);
|
|
578
604
|
pages[index] = null;
|
|
579
|
-
|
|
605
|
+
// The page's picture is how its tagged text looks, not a figure of its own: an artifact.
|
|
606
|
+
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';
|
|
580
607
|
const pictures = ['/Im0 ' + picture + ' 0 R'], states = [];
|
|
581
608
|
for (const drawing of vectorPages[index] || []) {
|
|
582
609
|
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`;
|
|
@@ -609,6 +636,8 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
609
636
|
// Keep one logical Unicode text object, resetting its font before each scalar's direction
|
|
610
637
|
// can change. Readers that ignore ActualText otherwise discard earlier words in mixed RTL/LTR.
|
|
611
638
|
// Express width in the font size: some readers ignore horizontal text scaling when joining.
|
|
639
|
+
const element = structOf(run.tags || []);
|
|
640
|
+
if (element) {element.kids.push({page: index, mcid: marked.length}); commands += `/${element.role} << /MCID ${marked.length} >> BDC\n`; marked.push(element);}
|
|
612
641
|
commands += `q\n/Span << /ActualText <feff${hex(run.text)}> >> BDC\nBT\n100 Tz\n`;
|
|
613
642
|
for (const part of run.parts || [run]) {
|
|
614
643
|
if (part.font) {
|
|
@@ -623,11 +652,11 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
623
652
|
const baseline = decimal(height - margins.top - (part.y + part.height * .8) * .75);
|
|
624
653
|
for (let letter = 0; letter < characters.length; letter++) {
|
|
625
654
|
const value = codes.get(characters[letter].codePointAt(0)).toString(16).padStart(4, '0');
|
|
626
|
-
commands +=
|
|
655
|
+
commands += `${run.carrier ? '/Fc' : '/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`;
|
|
627
656
|
}
|
|
628
657
|
}
|
|
629
658
|
}
|
|
630
|
-
commands += 'ET\nEMC\nQ\n';
|
|
659
|
+
commands += 'ET\nEMC\nQ\n' + (element ? 'EMC\n' : '');
|
|
631
660
|
}
|
|
632
661
|
if (numberFont) commands += `BT\n/F2 9 Tf\n0 Tr\n1 0 0 1 ${decimal(width / 2 - String(index + 1).length * 2.5)} ${decimal(margins.bottom / 2)} Tm\n(${index + 1}) Tj\nET\n`;
|
|
633
662
|
const content = stream('/Filter /FlateDecode', await compressed(encode(commands)));
|
|
@@ -638,10 +667,17 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
638
667
|
: '/Dest [' + kids[link.target.page] + ' 0 R /XYZ ' + decimal(margins.left + link.target.x * .75) + ' ' + decimal(height - margins.top - link.target.y * .75) + ' null]';
|
|
639
668
|
return add('<< /Type /Annot /Subtype /Link /Border [0 0 0] /Rect [' + rect.join(' ') + '] ' + target + ' >>');
|
|
640
669
|
});
|
|
641
|
-
put(kids[index], '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ' + decimal(width) + ' ' + decimal(height) + '] /Resources << /XObject << ' + pictures.join(' ') + ' >> /ExtGState << ' + states.join(' ') + ' >> /Font << /F1 ' + type + ' 0 R' + (numberFont ? ' /F2 ' + numberFont + ' 0 R' : '') + ' ' + nativeResources.join(' ') + ' >> >> /Contents ' + content + ' 0 R' + (annotations.length ? ' /Annots [' + annotations.map(id => id + ' 0 R').join(' ') + ']' : '') + ' >>');
|
|
670
|
+
put(kids[index], '<< /Type /Page /Parent 2 0 R /StructParents ' + index + ' /MediaBox [0 0 ' + decimal(width) + ' ' + decimal(height) + '] /Resources << /XObject << ' + pictures.join(' ') + ' >> /ExtGState << ' + states.join(' ') + ' >> /Font << /F1 ' + type + ' 0 R /Fc ' + carrierType + ' 0 R' + (numberFont ? ' /F2 ' + numberFont + ' 0 R' : '') + ' ' + nativeResources.join(' ') + ' >> >> /Contents ' + content + ' 0 R' + (annotations.length ? ' /Annots [' + annotations.map(id => id + ' 0 R').join(' ') + ']' : '') + ' >>');
|
|
642
671
|
}
|
|
643
672
|
put(2, '<< /Type /Pages /Count ' + kids.length + ' /Kids [' + kids.map(id => id + ' 0 R').join(' ') + '] >>');
|
|
644
|
-
|
|
673
|
+
const tree = add(''), ids = new Map([[documentElement, add('')]]);
|
|
674
|
+
const reserve = element => { for (const kid of element.kids) if (kid.kids) { ids.set(kid, add('')); reserve(kid); } };
|
|
675
|
+
reserve(documentElement);
|
|
676
|
+
for (const [element, id] of ids) put(id, '<< /Type /StructElem /S /' + element.role + ' /P ' + (element.parent ? ids.get(element.parent) : tree) + ' 0 R /K [' +
|
|
677
|
+
element.kids.map(kid => kid.kids ? ids.get(kid) + ' 0 R' : '<< /Type /MCR /Pg ' + kids[kid.page] + ' 0 R /MCID ' + kid.mcid + ' >>').join(' ') + '] >>');
|
|
678
|
+
const parents = add('<< /Nums [' + parentTree.map((marked, index) => index + ' [' + marked.map(element => ids.get(element) + ' 0 R').join(' ') + ']').join(' ') + '] >>');
|
|
679
|
+
put(tree, '<< /Type /StructTreeRoot /K [' + ids.get(documentElement) + ' 0 R] /ParentTree ' + parents + ' 0 R /ParentTreeNextKey ' + parentTree.length + ' >>');
|
|
680
|
+
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] >>');
|
|
645
681
|
const info = add('<< /Producer (Rapier) /Title <feff' + hex(title || filename.replace(/\.[^.]*$/, '')) + '> >>');
|
|
646
682
|
const header = encode('%PDF-1.7\n%\u00e2\u00e3\u00cf\u00d3\n'), offsets = new Array(objects.length);
|
|
647
683
|
let cursor = header.length;
|
|
@@ -29,11 +29,6 @@ const RAPIER_SANITIZE_OPTIONS = Object.freeze({
|
|
|
29
29
|
FORBID_TAGS: RAPIER_SANITIZE_FORBID_TAGS,
|
|
30
30
|
FORBID_ATTR: RAPIER_SANITIZE_FORBID_ATTR,
|
|
31
31
|
}),
|
|
32
|
-
snippet: Object.freeze({
|
|
33
|
-
..._rapierSanitizeProfile('html'),
|
|
34
|
-
FORBID_TAGS: RAPIER_SANITIZE_FORBID_TAGS,
|
|
35
|
-
FORBID_ATTR: Object.freeze([...'style']),
|
|
36
|
-
}),
|
|
37
32
|
diagram: Object.freeze({
|
|
38
33
|
..._rapierSanitizeProfile('html', 'svg'),
|
|
39
34
|
FORBID_TAGS: Object.freeze(['form', 'dialog', 'template', 'iframe', 'script', 'foreignobject']),
|
|
@@ -59,6 +54,9 @@ const DIAGRAM_LABEL_SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
|
|
|
59
54
|
const DIAGRAM_LABEL_HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
60
55
|
const DIAGRAM_LABEL_MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
|
|
61
56
|
|
|
57
|
+
// The profile each DOMPurify instance holds (setConfig), shared by every renderer made over it.
|
|
58
|
+
const purifyConfigured = new WeakMap();
|
|
59
|
+
|
|
62
60
|
function _rapierImageDataAttributeAllowed(tag, attribute, value, verify) {
|
|
63
61
|
return (tag === 'img' && attribute === 'src' || tag === 'image' && /^(?:href|xlink:href)$/.test(attribute)
|
|
64
62
|
&& /^data:image\/(?:png|jpeg|webp|jxl);base64,/i.test(value)) && verify(value);
|
|
@@ -376,6 +374,8 @@ function _rapierInstallSanitizeHooks() {
|
|
|
376
374
|
});
|
|
377
375
|
}
|
|
378
376
|
|
|
377
|
+
const purifyOptions = new Map();
|
|
378
|
+
|
|
379
379
|
function sanitizeRapierHtml(value, context = 'render') {
|
|
380
380
|
return sanitize(value, context, false);
|
|
381
381
|
}
|
|
@@ -392,24 +392,31 @@ function sanitize(value, context, fragment) {
|
|
|
392
392
|
// (the hook) without an id the chrome owns. 'source' is Markdown source HTML, written and never shown: the render
|
|
393
393
|
// profile's admission, and the block on remote content stays the reader's (a remote picture's URL is the person's words).
|
|
394
394
|
const selected = context === 'raw' || context === 'export' || context === 'source' || RAPIER_SANITIZE_OPTIONS[context] ? context : 'render';
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
395
|
+
const labels = selected === 'diagram' || selected === 'export', key = selected + (fragment ? ' fragment' : '');
|
|
396
|
+
let options = purifyOptions.get(key);
|
|
397
|
+
if (!options) {
|
|
398
|
+
options = selected === 'raw' ? { ...RAPIER_SANITIZE_OPTIONS.render, ALLOW_DATA_ATTR: false } : RAPIER_SANITIZE_OPTIONS[selected] || RAPIER_SANITIZE_OPTIONS.render;
|
|
399
|
+
if (selected === 'source') options = {...options, ADD_DATA_URI_TAGS: ['image']};
|
|
399
400
|
// DOMPurify's USE_PROFILES replaces explicit allowlists. Add MathML to the
|
|
400
401
|
// selected profile without discarding its admitted HTML and SVG vocabulary.
|
|
401
|
-
options = {...options, ALLOWED_TAGS: [...new Set([...options.ALLOWED_TAGS, ...RAPIER_SANITIZE_PROFILES.mathTags])],
|
|
402
|
+
if (labels) options = {...options, ALLOWED_TAGS: [...new Set([...options.ALLOWED_TAGS, ...RAPIER_SANITIZE_PROFILES.mathTags])],
|
|
402
403
|
ALLOWED_ATTR: [...new Set([...options.ALLOWED_ATTR, ...RAPIER_SANITIZE_PROFILES.mathAttributes])], ADD_TAGS: ['foreignobject'], ADD_DATA_URI_TAGS: ['image'],
|
|
403
404
|
HTML_INTEGRATION_POINTS: {foreignobject: true}, FORBID_TAGS: options.FORBID_TAGS.filter(tag => tag !== 'foreignobject')};
|
|
405
|
+
if (fragment) options = {...options, RETURN_DOM_FRAGMENT: true};
|
|
406
|
+
purifyOptions.set(key, options);
|
|
407
|
+
}
|
|
408
|
+
if (labels) {
|
|
404
409
|
DOMPurify.addHook('uponSanitizeElement', diagramLabelElement);
|
|
405
410
|
DOMPurify.addHook('uponSanitizeAttribute', diagramLabelAttribute);
|
|
406
411
|
DOMPurify.addHook('afterSanitizeAttributes', diagramLabelContained);
|
|
407
412
|
}
|
|
408
|
-
|
|
413
|
+
// DOMPurify reparses a per-call configuration on every call (its cache compares its own copy), about 0.45 ms
|
|
414
|
+
// each in Chrome 155. A profile is set once and kept until another profile is asked for; every caller comes here.
|
|
415
|
+
if (purifyConfigured.get(DOMPurify) !== options) { DOMPurify.setConfig(options); purifyConfigured.set(DOMPurify, options); }
|
|
409
416
|
const previous = _rapierSanitizeRuntime.context;
|
|
410
417
|
_rapierSanitizeRuntime.context = selected;
|
|
411
418
|
try {
|
|
412
|
-
return DOMPurify.sanitize(value == null ? '' : String(value)
|
|
419
|
+
return DOMPurify.sanitize(value == null ? '' : String(value));
|
|
413
420
|
} finally {
|
|
414
421
|
if (labels) {
|
|
415
422
|
DOMPurify.removeHook('uponSanitizeElement', diagramLabelElement);
|
|
@@ -103,9 +103,9 @@ function _rapierArtifactStyles(theme, includeFonts, printMode, houseDefaults = f
|
|
|
103
103
|
const highlightCss = _rapierStyleText('rapier-highlight-style');
|
|
104
104
|
parts.push(highlightCss);
|
|
105
105
|
// A page written for the system theme has no body.light to switch: under a light system
|
|
106
|
-
// setting
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
// setting each palette takes its light values from the sheet's own body.light rule.
|
|
107
|
+
if (theme === 'system') for (const [, palette, light] of highlightCss.matchAll(/html\[data-highlights="(standard|accent)"\] body\.light\s*\{([^}]*)\}/g))
|
|
108
|
+
parts.push('@media (prefers-color-scheme:light){html[data-rapier-theme="system"][data-highlights="' + palette + '"] body{' + light.trim() + '}}');
|
|
109
109
|
if (printMode) parts.push(_rapierArtifactPrintCss());
|
|
110
110
|
return parts.filter(Boolean).join('\n\n');
|
|
111
111
|
}
|
package/dist/kit/render-work.mjs
CHANGED
|
@@ -8,7 +8,7 @@ export function finish(iterator) {
|
|
|
8
8
|
|
|
9
9
|
export function check(work) {
|
|
10
10
|
work?.check?.();
|
|
11
|
-
if (work?.signal?.aborted) throw Object.assign(new Error('The operation was
|
|
11
|
+
if (work?.signal?.aborted) throw Object.assign(new Error('The operation was canceled.'), {name: 'AbortError', code: 'cancelled'});
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export async function pause(work, fraction, label) {
|
package/dist/kit/render.mjs
CHANGED
|
@@ -8,43 +8,6 @@ import {documentLanguage, documentSettingsFonts} from '../spec/document-settings
|
|
|
8
8
|
import {createLineMeasurer, linesHeight} from '../layout/model.mjs';
|
|
9
9
|
import {markPrintCapitals, createPrintCapitals, followingPrintProse} from './print-capitals.mjs';
|
|
10
10
|
import {freezePublishedInk} from './render-ink.mjs';
|
|
11
|
-
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
12
|
-
// Rapier generates attribute selectors for dark paper. Project those owned rules onto their
|
|
13
|
-
// SVG elements before export removes stylesheets; the inert host and the editor share this pass.
|
|
14
|
-
export function presentNativeDiagram(drawing) {
|
|
15
|
-
const winners=new Map();
|
|
16
|
-
for(const style of drawing.querySelectorAll('style')){
|
|
17
|
-
const media=/^\s*@media\s*\(\s*prefers-color-scheme\s*:\s*dark\s*\)\s*\{([\s\S]*)\}\s*$/.exec(style.textContent);
|
|
18
|
-
if(media)for(const rule of media[1].matchAll(/([^{}]+)\{([^{}]*)\}/g)){
|
|
19
|
-
// The browser's CSS sanitizer serializes owned hex colours as rgb()/rgba().
|
|
20
|
-
const properties=[...rule[2].matchAll(/(?:^|;)\s*(fill|stroke|color|stop-color)\s*:\s*(#[\da-f]{3,8}|rgba?\([\d.,%/\s]+\)|none)\s*(?:!important)?(?=;|$)/gi)];
|
|
21
|
-
for(const selector of rule[1].split(',')){
|
|
22
|
-
// Draw's selectors contain only attributes, descendants and immediate children.
|
|
23
|
-
if(!/^\s*\[[^\[\]{};]+\](?:\s*(?:>\s*)?\[[^\[\]{};]+\])*\s*$/.test(selector))continue;
|
|
24
|
-
const rank=(selector.match(/\[/g)||[]).length;
|
|
25
|
-
for(const element of [...(drawing.matches(selector)?[drawing]:[]),...drawing.querySelectorAll(selector)]){
|
|
26
|
-
let paints=winners.get(element);if(!paints)winners.set(element,paints=new Map());
|
|
27
|
-
for(const [,property,dark] of properties){const light=element.getAttribute(property);
|
|
28
|
-
if(light&&(paints.get(property)?.rank??-1)<=rank)paints.set(property,{light,dark,rank});}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
style.remove();
|
|
33
|
-
}
|
|
34
|
-
for(const [element,paints] of winners)for(const [property,{light,dark}] of paints)
|
|
35
|
-
element.style.setProperty(property,'light-dark('+light+', '+dark+')','important');
|
|
36
|
-
drawing.classList.add('rapier-native-flowchart');drawing.setAttribute('role','img');
|
|
37
|
-
drawing.setAttribute('aria-roledescription',drawing.getAttribute('data-rapier-diagram-kind')||'flowchart');
|
|
38
|
-
const width=Number(drawing.getAttribute('width')),
|
|
39
|
-
sizes=[...drawing.querySelectorAll('text[font-size]')].map(text=>Number(text.getAttribute('font-size'))).filter(size=>Number.isFinite(size)&&size>0),
|
|
40
|
-
minimum=sizes.reduce((smallest,size)=>Math.min(smallest,size),Infinity);
|
|
41
|
-
if(Number.isFinite(width)&&width>0){
|
|
42
|
-
const readable=Math.ceil(width*14/(Number.isFinite(minimum)?minimum:14)*1000)/1000;
|
|
43
|
-
drawing.style.width='clamp('+readable+'px, 100%, '+width+'px)';
|
|
44
|
-
}
|
|
45
|
-
drawing.style.maxWidth='none';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
11
|
// Set the first view once. Subsequent rendering keeps the person's horizontal position.
|
|
49
12
|
export function positionNativeDiagram(drawing) {
|
|
50
13
|
const surface=drawing?.closest?.('.diagram-block,.diagram-live-preview');
|
|
@@ -67,6 +30,7 @@ export function positionNativeDiagram(drawing) {
|
|
|
67
30
|
surface.scrollLeft=Math.max(0,Math.min(surface.scrollWidth-surface.clientWidth,left));
|
|
68
31
|
}
|
|
69
32
|
|
|
33
|
+
// The document's renderer. DOM, codecs and host state are explicit inputs.
|
|
70
34
|
function createRenderer(runtime) {
|
|
71
35
|
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
36
|
let md = runtime.md;
|
|
@@ -376,7 +340,7 @@ function _rapierDocxRenderStyles(root) {
|
|
|
376
340
|
const surface = into.createElement('div'); surface.className = 'md-render'; into.body.appendChild(surface);
|
|
377
341
|
const canvas = into.createElement('canvas'); canvas.width = canvas.height = 1;
|
|
378
342
|
const paint = canvas.getContext('2d'), colours = new Map();
|
|
379
|
-
if (!paint) throw new Error('Word text
|
|
343
|
+
if (!paint) throw new Error('Word text colors could not be read.');
|
|
380
344
|
const hex = colour => {
|
|
381
345
|
if (!colours.has(colour)) {
|
|
382
346
|
// CSS may resolve an accent to oklch/color-mix. The browser supplies its
|
|
@@ -659,11 +623,12 @@ function _rapierSharedResolve(text, ids, images, definitions) {
|
|
|
659
623
|
// Authored # characters were encoded. A raw id after ]( can only be a structural
|
|
660
624
|
// substitution; matching its destination lets nested alt brackets survive unchanged.
|
|
661
625
|
const inline = new RegExp('(\\]\\([ \\t\\r\\n]*(?:<)?)#' + id + '(?=>?[ \\t\\r\\n]*\\)|>?[ \\t\\r\\n]+["\'(])', 'g');
|
|
662
|
-
// A definition may sit in a quote or a list item: its line starts with
|
|
663
|
-
const definition = new RegExp('(^[ \\t>]*\\[((?:\\\\.|[^\\]\\\\])+)\\]:[ \\t]*(?:(?:\\r\\n|\\r|\\n)[ \\t>]*)?(?:<)?)#' + id + '(?=>?[ \\t]*$|>?[ \\t]+["\'(])', 'gm');
|
|
626
|
+
// A definition may sit in a quote or a list item, as the writer found it: its line starts with their markers.
|
|
627
|
+
const definition = new RegExp('(^[ \\t>]*(?:(?:[-+*]|\\d{1,9}[.)])[ \\t]+[ \\t>]*)*\\[((?:\\\\.|[^\\]\\\\])+)\\]:[ \\t]*(?:(?:\\r\\n|\\r|\\n)[ \\t>]*)?(?:<)?)#' + id + '(?=>?[ \\t]*$|>?[ \\t]+["\'(])', 'gm');
|
|
664
628
|
const swapInline = (match, lead, value) => { const found = src(value); return found == null ? match : lead + found; };
|
|
665
629
|
const swapDefinition = (match, lead, label, value) => {
|
|
666
|
-
|
|
630
|
+
const decoded = _rapierSharedDecode(label), quoted = decoded.replace(/\r?\n[ \t]*(?:>[ \t]*)+/g, ' ');
|
|
631
|
+
if (!definitions.has(md.utils.normalizeReference(decoded)) && !definitions.has(md.utils.normalizeReference(quoted))) return match;
|
|
667
632
|
const found = src(value);
|
|
668
633
|
return found == null ? match : lead + found;
|
|
669
634
|
};
|
|
@@ -172,20 +172,6 @@ export function createParagraphFitter() {
|
|
|
172
172
|
return {lines, work, cost: totalCost, approximate: false};
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
const work = solveSteps(prepared, width, options);
|
|
177
|
-
try {
|
|
178
|
-
let step = work.next();
|
|
179
|
-
while (!step.done) {
|
|
180
|
-
if (step.value && !observe) return null;
|
|
181
|
-
step = work.next(step.value ? observe(step.value) : undefined);
|
|
182
|
-
}
|
|
183
|
-
return step.value;
|
|
184
|
-
} finally { work.return(); }
|
|
185
|
-
}
|
|
186
|
-
return {prepare, solve, solveSteps};
|
|
175
|
+
return {prepare, solveSteps};
|
|
187
176
|
}
|
|
188
177
|
|
|
189
|
-
const fitter = createParagraphFitter();
|
|
190
|
-
export const prepareParagraph = fitter.prepare;
|
|
191
|
-
export const fitParagraph = fitter.solve;
|
|
@@ -3,7 +3,8 @@ import {createPreparationScope, supportsRichStyle, captureFontAuthority} from '.
|
|
|
3
3
|
import {createParagraphFitter} from './paragraph-fit.mjs';
|
|
4
4
|
|
|
5
5
|
// Layout projections keep the original text in place. The factory also runs in an exported page.
|
|
6
|
-
export function createTypography(
|
|
6
|
+
export function createTypography() {
|
|
7
|
+
const fitter = createParagraphFitter();
|
|
7
8
|
const mark = 'data-rapier-typography', boundary = mark + '-break', hang = mark + '-hang';
|
|
8
9
|
const protectedInitial = mark + '-protected-initial';
|
|
9
10
|
const gapMark = mark + '-gap', fitMark = mark + '-fit';
|