rapier-markdown-kit 1.2.46 → 1.2.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/kit/artifact-modules.mjs +19 -0
- package/dist/kit/export-fonts.mjs +28 -19
- package/dist/kit/ledger/carried.mjs +17 -8
- package/dist/kit/ledger/source.mjs +31 -2
- package/dist/kit/pdf-source-digest.mjs +10 -0
- package/dist/kit/pdf-source.mjs +4 -1
- package/dist/kit/pdf-vectors.mjs +3 -2
- package/dist/kit/render-markdown.mjs +14 -8
- package/dist/kit/render-print.mjs +55 -7
- package/dist/kit/render.mjs +8 -7
- package/dist/layout/initial-range.mjs +18 -0
- package/dist/layout/typography.mjs +2 -18
- package/dist/packages/rapier-diff/concerns.mjs +2 -2
- package/dist/spec/document-fonts.mjs +60 -6
- package/package.json +1 -1
- package/style.css +5 -2
package/README.md
CHANGED
|
@@ -81,7 +81,9 @@ editor's own seam: each needs a `host` that supplies its DOM, Markdown, picture,
|
|
|
81
81
|
`createRenderStyles`, `createRenderSanitizer` and `createPrintRenderer`, plus the explicit font helpers
|
|
82
82
|
`exportFontCss`, `requireDocumentFonts`, `fontRequests`, `waitForFonts` and `waitForFontRequests`. These are the
|
|
83
83
|
functions used by the editor and exported pages. Each factory takes explicit host dependencies
|
|
84
|
-
in its first argument.
|
|
84
|
+
in its first argument. Refined print paragraphs use the host's retained `RapierArtifactLayoutDependencies`
|
|
85
|
+
(the same factory graph used by standalone HTML). They initialize in the print document so its own font set
|
|
86
|
+
owns every measurement. Missing factories or a blocked initialization refuse the export. `style.css` is the
|
|
85
87
|
MIT `spec/markdown-style.css`, including the numbered-list, checkbox, callout, table,
|
|
86
88
|
footnote and picture-layout treatment.
|
|
87
89
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
export function _rapierArtifactFactoryModules(dependencies, group) {
|
|
3
|
+
const paths = dependencies.groups && dependencies.groups[group] ? dependencies.groups[group] : Object.keys(dependencies.factories);
|
|
4
|
+
return paths.map(path => 'modules[' + JSON.stringify(path) + '] = (' + Function.prototype.toString.call(dependencies.factories[path]) + ')();').join('\n');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function printTypography(root, dependencies) {
|
|
8
|
+
if (!root.querySelector('p[data-md-hyphens="auto"],p[data-md-lines],p[data-md-hanging="on"],p[data-rapier-typography]')) return null;
|
|
9
|
+
const fail = () => Object.assign(new Error('The print document could not initialize its typography.'), {code: 'pdf_typography_unavailable'});
|
|
10
|
+
if (!dependencies?.factories?.['layout/typography.mjs']) throw fail();
|
|
11
|
+
const document = root.ownerDocument, script = document.createElement('script');
|
|
12
|
+
script.textContent = '(() => { const modules = Object.create(null);\n' + _rapierArtifactFactoryModules(dependencies, 'layout') +
|
|
13
|
+
'\ndocument.currentScript.rapierTypography = modules["layout/typography.mjs"].createTypography();\n})();';
|
|
14
|
+
try {
|
|
15
|
+
document.head.append(script);
|
|
16
|
+
if (!script.rapierTypography) throw fail();
|
|
17
|
+
return script.rapierTypography;
|
|
18
|
+
} finally { script.remove(); }
|
|
19
|
+
}
|
|
@@ -165,7 +165,6 @@ function fontSource(bytes) {
|
|
|
165
165
|
|
|
166
166
|
function writeFace(face, source) {
|
|
167
167
|
const properties = {...face.properties, src: source};
|
|
168
|
-
delete properties['unicode-range'];
|
|
169
168
|
return '@font-face{' + Object.entries(properties).map(([name, value]) => name + ':' + value).join(';') + '}';
|
|
170
169
|
}
|
|
171
170
|
|
|
@@ -188,8 +187,29 @@ function fontRuns(root, {settings, css = '', fontCss = '', faces = fontFaces(fon
|
|
|
188
187
|
'--md-font-main': settings?.mainfont?.css || settings?.sansfont?.css || FAMILY,
|
|
189
188
|
'--md-font-mono': settings?.monofont?.css || MONO}};
|
|
190
189
|
const computed = new WeakMap();
|
|
191
|
-
let
|
|
192
|
-
const breakRun = () => {
|
|
190
|
+
let pending = null, segmenter;
|
|
191
|
+
const breakRun = () => {
|
|
192
|
+
if (!pending) return;
|
|
193
|
+
const {text, families, state} = pending;
|
|
194
|
+
let prior = new Set();
|
|
195
|
+
for (const {segment} of (segmenter ||= new Intl.Segmenter(undefined, {granularity: 'grapheme'})).segment(text)) {
|
|
196
|
+
const selected = new Set();
|
|
197
|
+
for (const character of segment) for (const family of families) {
|
|
198
|
+
const candidates = faces.filter(face => face.family === family && unicodeIncludes(face, character));
|
|
199
|
+
if (!candidates.length) continue;
|
|
200
|
+
candidates.sort((a, b) => stretchOrder(a, b, state['font-stretch']) ||
|
|
201
|
+
styleDistance(a, state['font-style']) - styleDistance(b, state['font-style']) ||
|
|
202
|
+
weightDistance(a, state['font-weight']) - weightDistance(b, state['font-weight']));
|
|
203
|
+
selected.add(candidates[0]); break;
|
|
204
|
+
}
|
|
205
|
+
for (const face of selected) {
|
|
206
|
+
if (prior.has(face)) face.runs[face.runs.length - 1] += segment;
|
|
207
|
+
else face.runs.push(segment);
|
|
208
|
+
}
|
|
209
|
+
prior = selected;
|
|
210
|
+
}
|
|
211
|
+
pending = null;
|
|
212
|
+
};
|
|
193
213
|
function styleOf(element) {
|
|
194
214
|
if (computed.has(element)) return computed.get(element);
|
|
195
215
|
const parent = element === root ? initial : styleOf(element.parentElement || root);
|
|
@@ -231,22 +251,10 @@ function fontRuns(root, {settings, css = '', fontCss = '', faces = fontFaces(fon
|
|
|
231
251
|
else if (state['text-transform'] === 'capitalize') text = text.replace(/(^|\s)(\p{L})/gu, (_, gap, letter) => gap + letter.toUpperCase());
|
|
232
252
|
const families = splitCss(state['font-family'], ',').filter(name => /^\s*['"]/.test(name) || !CSS_GENERIC_FONT_FAMILIES.includes(unquote(name).toLowerCase()))
|
|
233
253
|
.map(name => unquote(name).toLowerCase());
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const candidates = faces.filter(face => face.family === family && unicodeIncludes(face, character));
|
|
239
|
-
if (!candidates.length) continue;
|
|
240
|
-
candidates.sort((a, b) => stretchOrder(a, b, state['font-stretch']) ||
|
|
241
|
-
styleDistance(a, state['font-style']) - styleDistance(b, state['font-style']) ||
|
|
242
|
-
weightDistance(a, state['font-weight']) - weightDistance(b, state['font-weight']));
|
|
243
|
-
selected = candidates[0]; break;
|
|
244
|
-
}
|
|
245
|
-
if (!selected) { breakRun(); continue; }
|
|
246
|
-
if (selected === priorFace && runStyle === priorStyle) selected.runs[selected.runs.length - 1] += character;
|
|
247
|
-
else selected.runs.push(character);
|
|
248
|
-
priorFace = selected; priorStyle = runStyle;
|
|
249
|
-
}
|
|
254
|
+
const key = JSON.stringify([families, state['font-weight'], state['font-style'], state['font-stretch']]);
|
|
255
|
+
if (pending?.key !== key) breakRun();
|
|
256
|
+
if (pending) pending.text += text;
|
|
257
|
+
else pending = {text, families, state, key};
|
|
250
258
|
}
|
|
251
259
|
function walk(element) {
|
|
252
260
|
const name = element.localName?.toLowerCase();
|
|
@@ -277,6 +285,7 @@ function fontRuns(root, {settings, css = '', fontCss = '', faces = fontFaces(fon
|
|
|
277
285
|
if (boundary) breakRun();
|
|
278
286
|
}
|
|
279
287
|
walk(root);
|
|
288
|
+
breakRun();
|
|
280
289
|
} finally {
|
|
281
290
|
if (oldClass == null) root.removeAttribute('class'); else root.setAttribute('class', oldClass);
|
|
282
291
|
}
|
|
@@ -56,11 +56,19 @@ export function documentTrailer(text, options) {
|
|
|
56
56
|
export function writeDocument(text, options) {
|
|
57
57
|
return text + documentTrailer(text, options);
|
|
58
58
|
}
|
|
59
|
+
function trailerOpening(source) {
|
|
60
|
+
const marker = '<!-- rapier-carried/1', at = source.lastIndexOf(marker);
|
|
61
|
+
if (at < 0) return null;
|
|
62
|
+
if (source.slice(at - 2, at + marker.length + 1) === OPEN) return {start: at - 2, body: at + marker.length + 1};
|
|
63
|
+
const before = /(?:\r\n|\r|\n){2}$/.exec(source.slice(Math.max(0, at - 4), at));
|
|
64
|
+
const after = /^(?:\r\n|\r|\n)/.exec(source.slice(at + marker.length, at + marker.length + 2));
|
|
65
|
+
return before && after ? {start: at - before[0].length, body: at + marker.length + after[0].length} : null;
|
|
66
|
+
}
|
|
59
67
|
function unfinishedTrailer(source) {
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
const opening = trailerOpening(source);
|
|
69
|
+
if (!opening) return false;
|
|
62
70
|
let fence = null;
|
|
63
|
-
for (const line of source.slice(0,
|
|
71
|
+
for (const line of source.slice(0, opening.start).split(/\r\n?|\n/)) {
|
|
64
72
|
const found = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
65
73
|
if (!found) continue;
|
|
66
74
|
if (!fence) fence = found[1];
|
|
@@ -69,17 +77,18 @@ function unfinishedTrailer(source) {
|
|
|
69
77
|
return !fence;
|
|
70
78
|
}
|
|
71
79
|
export function readDocument(source) {
|
|
72
|
-
|
|
80
|
+
const closing = /(?:\r\n|\r|\n)-->(?:\r\n|\r|\n)$/.exec(source.slice(-7));
|
|
81
|
+
if (!closing) {
|
|
73
82
|
if (unfinishedTrailer(source)) throw fault('unfinished document metadata');
|
|
74
83
|
return {text: source, ledger: null, authorship: null};
|
|
75
84
|
}
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
85
|
+
const opening = trailerOpening(source);
|
|
86
|
+
if (!opening) return {text: source, ledger: null, authorship: null};
|
|
78
87
|
let parts;
|
|
79
|
-
try { parts = JSON.parse(source.slice(
|
|
88
|
+
try { parts = JSON.parse(source.slice(opening.body, -closing[0].length)); }
|
|
80
89
|
catch (_) { throw fault('invalid document metadata'); }
|
|
81
90
|
if (!parts || Object.keys(parts).length !== 2 || !Object.hasOwn(parts, 'ledger') || !Object.hasOwn(parts, 'authorship')) throw fault('invalid document parts');
|
|
82
|
-
const text = source.slice(0,
|
|
91
|
+
const text = source.slice(0, opening.start);
|
|
83
92
|
return {text, ...validateParts(text, parts)};
|
|
84
93
|
}
|
|
85
94
|
|
|
@@ -70,13 +70,42 @@ function sourceString(node) {
|
|
|
70
70
|
}
|
|
71
71
|
const replacements = new WeakMap(), texts = new WeakMap(), edits = new WeakMap();
|
|
72
72
|
export const sourceEdit = source => edits.get(source) || null;
|
|
73
|
+
const INITIAL_LEAF_CHARS = 16384;
|
|
73
74
|
export class HistorySource {
|
|
74
75
|
#root;
|
|
75
|
-
constructor(text) {
|
|
76
|
+
constructor(text) {
|
|
77
|
+
if (typeof text === 'string' && text.length > INITIAL_LEAF_CHARS) {
|
|
78
|
+
const leaves = [];
|
|
79
|
+
for (let from = 0; from < text.length;) {
|
|
80
|
+
let to = Math.min(text.length, from + INITIAL_LEAF_CHARS);
|
|
81
|
+
if (to < text.length && (text.charCodeAt(to - 1) & 0xfc00) === 0xd800 && (text.charCodeAt(to) & 0xfc00) === 0xdc00) to++;
|
|
82
|
+
leaves.push(sourceLeaf(text, from, to));
|
|
83
|
+
from = to;
|
|
84
|
+
}
|
|
85
|
+
const build = (from, to) => from === to ? null : to - from === 1 ? leaves[from]
|
|
86
|
+
: sourceNode(build(from, (from + to) >> 1), build((from + to) >> 1, to));
|
|
87
|
+
this.#root = build(0, leaves.length);
|
|
88
|
+
} else this.#root = sourceLeaf(text);
|
|
89
|
+
Object.freeze(this);
|
|
90
|
+
}
|
|
76
91
|
static owns(value) { return value !== null && typeof value === 'object' && #root in value; }
|
|
77
92
|
static #fromRoot(root) { const source = new HistorySource(''); source.#root = root; return source; }
|
|
78
93
|
static fromParts(parts) {
|
|
79
|
-
const leaves =
|
|
94
|
+
const leaves = [];
|
|
95
|
+
for (const text of parts) {
|
|
96
|
+
if (typeof text !== 'string') throw new TypeError('source string required');
|
|
97
|
+
if (text.length <= INITIAL_LEAF_CHARS) {
|
|
98
|
+
const leaf = sourceLeaf(text);
|
|
99
|
+
if (leaf) leaves.push(leaf);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (let from = 0; from < text.length;) {
|
|
103
|
+
let to = Math.min(text.length, from + INITIAL_LEAF_CHARS);
|
|
104
|
+
if (to < text.length && (text.charCodeAt(to - 1) & 0xfc00) === 0xd800 && (text.charCodeAt(to) & 0xfc00) === 0xdc00) to++;
|
|
105
|
+
leaves.push(sourceLeaf(text, from, to));
|
|
106
|
+
from = to;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
80
109
|
const build = (from, to) => from === to ? null : to - from === 1 ? leaves[from]
|
|
81
110
|
: sourceNode(build(from, Math.floor((from + to) / 2)), build(Math.floor((from + to) / 2), to));
|
|
82
111
|
return HistorySource.#fromRoot(build(0, leaves.length));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
export const PDF_SOURCE_DIGEST = '/RapierSourceDigest <' + '0'.repeat(64) + '>';
|
|
3
|
+
|
|
4
|
+
export async function pdfSourceDigest(bytes, start, end) {
|
|
5
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || start + 64 > end || end > bytes.length)
|
|
6
|
+
throw new Error('Invalid PDF source digest range.');
|
|
7
|
+
const bound = bytes.slice(0, end);
|
|
8
|
+
bound.fill(48, start, start + 64);
|
|
9
|
+
return Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', bound)), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
10
|
+
}
|
package/dist/kit/pdf-source.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {PDF_SOURCE_DIGEST, pdfSourceDigest} from './pdf-source-digest.mjs';
|
|
2
3
|
function pdfStructure(input) {
|
|
3
4
|
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
4
5
|
const fail = () => { throw Object.assign(new Error('The PDF source could not be attached.'), {code: 'pdf_source_invalid'}); };
|
|
@@ -116,10 +117,12 @@ export async function attachPdfSource(input, {source, filename, maxBytes = Infin
|
|
|
116
117
|
table += place ? String(place.at).padStart(10, '0') + ' ' + String(place.generation).padStart(5, '0') + ' n \n' : '0000000000 00000 f \n';
|
|
117
118
|
}
|
|
118
119
|
const identity = trailer.fields.get('ID');
|
|
119
|
-
table += 'trailer\n<< /Size ' + totalSize + ' /Root ' + rootId + ' ' + generation + ' R /Info ' + infoId + ' 0 R' + (identity ? ' /ID ' + identity.value : '') + ' >>\nstartxref\n' + length + '\n%%EOF\n';
|
|
120
|
+
table += 'trailer\n<< /Size ' + totalSize + ' /Root ' + rootId + ' ' + generation + ' R /Info ' + infoId + ' 0 R' + (identity ? ' /ID ' + identity.value : '') + ' ' + PDF_SOURCE_DIGEST + ' >>\nstartxref\n' + length + '\n%%EOF\n';
|
|
121
|
+
const digestAt = length + table.indexOf(PDF_SOURCE_DIGEST) + PDF_SOURCE_DIGEST.indexOf('<') + 1;
|
|
120
122
|
parts.push(ascii(table)); length += table.length;
|
|
121
123
|
if (length > maxBytes) throw Object.assign(new Error('The exported file exceeds the file limit.'), {code: 'export_too_large'});
|
|
122
124
|
const result = new Uint8Array(length); let at = 0;
|
|
123
125
|
for (const chunk of parts) { result.set(chunk, at); at += chunk.length; }
|
|
126
|
+
result.set(encode.encode(await pdfSourceDigest(result, digestAt, result.length - 1)), digestAt);
|
|
124
127
|
check(); return result;
|
|
125
128
|
}
|
package/dist/kit/pdf-vectors.mjs
CHANGED
|
@@ -157,14 +157,15 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
|
|
|
157
157
|
const wrap = document.createElement('div');
|
|
158
158
|
wrap.style.cssText = `position:fixed;left:0;top:0;width:${box.width}px;height:${box.height}px;pointer-events:none;background:transparent`;
|
|
159
159
|
clone.style.setProperty('width', box.width + 'px', 'important'); clone.style.setProperty('height', box.height + 'px', 'important');
|
|
160
|
-
|
|
160
|
+
const surface = document.createElement('div'); surface.style.cssText = 'width:100%;height:100%';
|
|
161
|
+
surface.append(clone); wrap.attachShadow({mode: 'open'}).append(surface); document.body.append(wrap);
|
|
161
162
|
try {
|
|
162
163
|
const plainText = node.localName === 'text' && window.getComputedStyle(node).filter === 'none' && window.getComputedStyle(node).textShadow === 'none';
|
|
163
164
|
const bounds = plainText ? node.getBoundingClientRect() : box;
|
|
164
165
|
const left = Math.max(0, bounds.left - box.left - 2), top = Math.max(0, bounds.top - box.top - 2);
|
|
165
166
|
const right = Math.min(box.width, bounds.right - box.left + 2), bottom = Math.min(box.height, bounds.bottom - box.top + 2);
|
|
166
167
|
if (!(right > left && bottom > top)) return;
|
|
167
|
-
const result = await capture({root:
|
|
168
|
+
const result = await capture({root: surface, clip: {x: left, y: top, width: right - left, height: bottom - top}, transparent: true, scale: 2, signal, current, pixels: true}); check();
|
|
168
169
|
if (result?.rgb instanceof Uint8Array && result?.alpha instanceof Uint8Array) {
|
|
169
170
|
return {image: {rgb: await compress(result.rgb), alpha: await compress(result.alpha), width: result.width, height: result.height}, x: box.left - origin.left + left, y: box.top - origin.top + top, width: right - left, height: bottom - top};
|
|
170
171
|
}
|
|
@@ -715,7 +715,7 @@ function _rapierRenderSemanticRoot(canonical, metadata, options = {}) {
|
|
|
715
715
|
}
|
|
716
716
|
root.innerHTML = sanitizeRapierHtml(rendered, options.page ? 'source' : 'export');
|
|
717
717
|
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
718
|
-
_rapierApplyBlockDirection(root);
|
|
718
|
+
_rapierApplyBlockDirection(root, false, options);
|
|
719
719
|
|
|
720
720
|
_rapierAssignHeadingSlugs(root, RAPIER_RENDERED_HEADING_SELECTOR, false, options.carry?.slugs, options.carry?.headingIds);
|
|
721
721
|
_rapierDisambiguateRenderedAnchors(root);
|
|
@@ -791,7 +791,7 @@ async function _rapierRenderSemanticRootAsync(canonical, metadata, options = {})
|
|
|
791
791
|
if (html) append();
|
|
792
792
|
if (cutAt >= 0) _rapierAppendNestingCut(root, markdown, cutAt);
|
|
793
793
|
parsed = tokens = env = labels = null;
|
|
794
|
-
await finishAsync(_rapierSemanticDecorations(root, options.carry), work);
|
|
794
|
+
await finishAsync(_rapierSemanticDecorations(root, options.carry, options), work);
|
|
795
795
|
await pause(work, .6);
|
|
796
796
|
complete = true;
|
|
797
797
|
return root;
|
|
@@ -846,8 +846,8 @@ function _rapierReadPartDecorations(canonical, metadata, options = {}) {
|
|
|
846
846
|
return Object.freeze({headingIds, captionStart});
|
|
847
847
|
}
|
|
848
848
|
|
|
849
|
-
function* _rapierSemanticDecorations(root, carry = null) {
|
|
850
|
-
yield* _rapierApplyBlockDirectionSteps(root); yield;
|
|
849
|
+
function* _rapierSemanticDecorations(root, carry = null, options = {}) {
|
|
850
|
+
yield* _rapierApplyBlockDirectionSteps(root, false, options); yield;
|
|
851
851
|
yield* _rapierAssignHeadingSlugsSteps(root, RAPIER_RENDERED_HEADING_SELECTOR, false, carry?.slugs, carry?.headingIds); yield;
|
|
852
852
|
yield* _rapierDisambiguateRenderedAnchorsSteps(root); yield;
|
|
853
853
|
for (const code of root.querySelectorAll('pre > code')) { _rapierNormalizeCodeElement(code); yield; }
|
|
@@ -1159,14 +1159,20 @@ function _rapierRawHtmlFragment(source, rewrite, parser) {
|
|
|
1159
1159
|
return out;
|
|
1160
1160
|
}
|
|
1161
1161
|
|
|
1162
|
-
function _rapierApplyBlockDirection(root) { return finish(_rapierApplyBlockDirectionSteps(root)); }
|
|
1162
|
+
function _rapierApplyBlockDirection(root, includeRoot = false, options = {}) { return finish(_rapierApplyBlockDirectionSteps(root, includeRoot, options)); }
|
|
1163
1163
|
|
|
1164
|
-
function* _rapierApplyBlockDirectionSteps(root) {
|
|
1164
|
+
function* _rapierApplyBlockDirectionSteps(root, includeRoot = false, {authoredTableDirection = false} = {}) {
|
|
1165
1165
|
if (!root || typeof root.querySelectorAll !== 'function') return;
|
|
1166
|
+
const tableContainers = new Set();
|
|
1167
|
+
if (authoredTableDirection) for (const table of root.querySelectorAll('table')) {
|
|
1168
|
+
for (let parent = table.parentElement; parent && parent !== root && !tableContainers.has(parent); parent = parent.parentElement) {
|
|
1169
|
+
tableContainers.add(parent); yield;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1166
1172
|
const blocks = new Set(_RAPIER_DIR_BLOCKS.split(',')), elements = root.ownerDocument.createTreeWalker(root, 1);
|
|
1167
|
-
for (let el = elements.nextNode(); el; el = elements.nextNode()) {
|
|
1173
|
+
for (let el = includeRoot ? root : elements.nextNode(); el; el = elements.nextNode()) {
|
|
1168
1174
|
yield;
|
|
1169
|
-
if (!blocks.has(el.localName)) continue;
|
|
1175
|
+
if (!blocks.has(el.localName) || tableContainers.has(el)) continue;
|
|
1170
1176
|
let literal = false;
|
|
1171
1177
|
for (let parent = el.parentElement; parent; parent = parent.parentElement)
|
|
1172
1178
|
if (parent.localName === 'pre' || parent.localName === 'code') { literal = true; break; }
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import {collectPdfVectors} from './pdf-vectors.mjs';
|
|
3
3
|
import {collectPdfGlyphs} from './pdf-glyphs.mjs';
|
|
4
4
|
import {pdfFontSources, waitForFonts} from './export-fonts.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import {printTypography} from './artifact-modules.mjs';
|
|
6
6
|
import {documentLanguage, pageSizeOf, paperForLanguage} from '../spec/document-settings.mjs';
|
|
7
7
|
import {applyPrintCapitals} from './print-capitals.mjs';
|
|
8
8
|
import {stripOneTerminator as _rapierWillStripOneTerminator} from '../agent/will.mjs';
|
|
9
9
|
import {quotedWillSpan} from '../spec/md-marks.mjs';
|
|
10
|
+
import {PDF_SOURCE_DIGEST, pdfSourceDigest} from './pdf-source-digest.mjs';
|
|
10
11
|
const RAPIER_WILL_SENTINEL_MARK = '';
|
|
11
12
|
// SPDX-License-Identifier: MIT
|
|
12
13
|
function createPrintRenderer(runtime) {
|
|
@@ -209,6 +210,24 @@ const REFUSALS = {
|
|
|
209
210
|
export_too_large: 'The file is larger than the limit. Split the document.',
|
|
210
211
|
};
|
|
211
212
|
|
|
213
|
+
function sliceTallDrawings(main, pageHeight) {
|
|
214
|
+
const document = main.ownerDocument;
|
|
215
|
+
for (const svg of main.querySelectorAll('svg.rapier-native-flowchart, svg.rapier-diagram')) {
|
|
216
|
+
const box = svg.getBoundingClientRect(), view = (svg.getAttribute('viewBox') || '').trim().split(/[\s,]+/).map(Number);
|
|
217
|
+
if (!(box.height > pageHeight) || view.length !== 4 || !view.every(Number.isFinite) || !(view[2] > 0) || !(view[3] > 0)) continue;
|
|
218
|
+
const unit = view[3] / box.height, windows = [];
|
|
219
|
+
for (let offset = 0; offset < box.height; offset += pageHeight) {
|
|
220
|
+
const shown = Math.min(pageHeight, box.height - offset), part = svg.cloneNode(true);
|
|
221
|
+
part.setAttribute('viewBox', [view[0], view[1] + offset * unit, view[2], shown * unit].join(' '));
|
|
222
|
+
part.setAttribute('preserveAspectRatio', 'xMidYMin meet');
|
|
223
|
+
part.style.setProperty('width', box.width + 'px', 'important'); part.style.setProperty('height', shown + 'px', 'important');
|
|
224
|
+
part.style.setProperty('break-before', 'column', 'important'); part.style.setProperty('break-inside', 'avoid', 'important');
|
|
225
|
+
windows.push(part);
|
|
226
|
+
}
|
|
227
|
+
svg.replaceWith(...windows);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
212
231
|
async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = false, filename, title, settings, templatePage = null}, {capture, signal, current, maxBytes = 8 * 1024 * 1024, film, ink} = {}) {
|
|
213
232
|
const refuse = code => { throw Object.assign(new Error(code), {code}); };
|
|
214
233
|
const spoken = error => {
|
|
@@ -229,7 +248,8 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
229
248
|
if (bodyWidth < 1 || bodyHeight < 1) refuse('pdf_page_geometry_invalid');
|
|
230
249
|
const tiled = bodyWidth > CAPTURE_EDGE || bodyHeight > CAPTURE_EDGE || bodyWidth * bodyHeight > CAPTURE_PIXELS;
|
|
231
250
|
const frame = document.createElement('iframe');
|
|
232
|
-
let vectors, backdrop = null, nativeGlyphs, filmSession;
|
|
251
|
+
let vectors, backdrop = null, nativeGlyphs, filmSession, typography;
|
|
252
|
+
const omitted = [];
|
|
233
253
|
let rejectAbort;
|
|
234
254
|
const aborted = new Promise((_, reject) => { rejectAbort = reject; });
|
|
235
255
|
aborted.catch(() => {});
|
|
@@ -281,7 +301,19 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
281
301
|
main.getBoundingClientRect();
|
|
282
302
|
if (fontCss) await step(waitForFonts(main, {fontCss, css, settings}));
|
|
283
303
|
await step(into.fonts.ready);
|
|
284
|
-
for (const image of main.querySelectorAll('img')) {
|
|
304
|
+
for (const image of main.querySelectorAll('img')) {
|
|
305
|
+
image.loading = 'eager';
|
|
306
|
+
try { await step(image.decode()); }
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (error?.name !== 'EncodingError') throw error;
|
|
309
|
+
const source = image.getAttribute('src') || '';
|
|
310
|
+
omitted.push(source.startsWith('data:') ? image.getAttribute('alt') || 'a picture' : source.slice(0, 120));
|
|
311
|
+
const holder = image.closest('p,figure');
|
|
312
|
+
image.remove();
|
|
313
|
+
if (holder && !holder.textContent.trim() && !holder.querySelector('img,svg,math,table,pre')) holder.remove();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
sliceTallDrawings(main, bodyHeight);
|
|
285
317
|
wrapMasks(main);
|
|
286
318
|
check();
|
|
287
319
|
for (const node of [main, ...main.querySelectorAll('*')]) {
|
|
@@ -303,7 +335,8 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
303
335
|
main.style.setProperty(name, value, 'important');
|
|
304
336
|
globalThis.RapierMath?.fit(main);
|
|
305
337
|
applyPrintCapitals(main);
|
|
306
|
-
|
|
338
|
+
typography = printTypography(main, globalThis.RapierArtifactLayoutDependencies);
|
|
339
|
+
typography?.apply(main, {all: true});
|
|
307
340
|
ink?.(main);
|
|
308
341
|
const count = Math.max(1, Math.ceil((main.scrollWidth - 1) / bodyWidth));
|
|
309
342
|
if (!Number.isSafeInteger(count)) refuse('pdf_page_geometry_invalid');
|
|
@@ -421,9 +454,9 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
421
454
|
const bytes = await writeRasterPdf({pages, textPages, linkPages, vectorPages: vectors.pages, canonical, bom, filename, title, font, width, height, margins, backdrop: backdrop?.pages[0] || null,
|
|
422
455
|
pixelWidth: bodyWidth, pixelHeight: bodyHeight, pageNumbers: settings?.pagestyle === 'plain', maxBytes, check});
|
|
423
456
|
check();
|
|
424
|
-
return {bytes, pages: count, painted: nativeGlyphs.painted};
|
|
457
|
+
return {bytes, pages: count, painted: nativeGlyphs.painted, omitted};
|
|
425
458
|
} catch (error) { throw spoken(error); }
|
|
426
|
-
finally { filmSession?.close(); backdrop?.restore(); vectors?.restore(); nativeGlyphs?.restore(); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
459
|
+
finally { filmSession?.close(); backdrop?.restore(); vectors?.restore(); nativeGlyphs?.restore(); typography?.retire(frame.contentDocument); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
427
460
|
}
|
|
428
461
|
|
|
429
462
|
async function _rapierPrintTextPages(main, pageWidth, count, check, origin = main.getBoundingClientRect(), nativeNodes = null, markerNodes = null) {
|
|
@@ -442,6 +475,18 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
442
475
|
}
|
|
443
476
|
const marker = markerNodes?.get(node);
|
|
444
477
|
if (marker && marker.page < count) pages[marker.page].push({...marker, block: node, carrier: null, marker: true});
|
|
478
|
+
const taskItem = node.nodeType === 1 && node.matches('input[type="checkbox"]') && node.closest('li');
|
|
479
|
+
const taskParent = taskItem && node.parentElement;
|
|
480
|
+
if (taskItem && taskParent.firstElementChild === node && (taskParent === taskItem || taskParent.localName === 'p' && taskParent.parentElement === taskItem && taskItem.firstElementChild === taskParent)) {
|
|
481
|
+
const box = node.getBoundingClientRect(), style = into.defaultView.getComputedStyle(node);
|
|
482
|
+
if (box.width > 0 && box.height > 0 && style.visibility !== 'hidden' && style.visibility !== 'collapse') {
|
|
483
|
+
const page = Math.max(0, Math.min(count - 1, Math.floor((box.left - origin.left + .01) / pageWidth)));
|
|
484
|
+
const size = parseFloat(style.fontSize) || 16, width = Math.min(size, box.width), height = Math.min(size, box.height);
|
|
485
|
+
const text = node.checked ? '[x] ' : '[ ] ', part = {text, x: box.left - origin.left - page * pageWidth + (box.width - width) / 2,
|
|
486
|
+
y: box.top - origin.top + (box.height - height) / 2, width, height};
|
|
487
|
+
pages[page].push({...part, parts: [part], block: taskItem, carrier: null, marker: true});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
445
490
|
const formula = node.nodeType === 1 && node.matches('.math-rendered[data-math-src]') && node.querySelector('svg');
|
|
446
491
|
if (node.nodeType === 1 && !formula || node.nodeType === 3 && element.closest('.math-rendered[data-math-src]')) continue;
|
|
447
492
|
let value;
|
|
@@ -885,7 +930,8 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
885
930
|
cursor += (id + ' 0 obj\n').length + objects[id].length + 8;
|
|
886
931
|
}
|
|
887
932
|
const xrefAt = cursor;
|
|
888
|
-
const
|
|
933
|
+
const table = '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 ' + PDF_SOURCE_DIGEST + ' >>\nstartxref\n' + xrefAt + '\n%%EOF\n';
|
|
934
|
+
const xref = encode(table), digestAt = xrefAt + table.indexOf(PDF_SOURCE_DIGEST) + PDF_SOURCE_DIGEST.indexOf('<') + 1;
|
|
889
935
|
if (cursor + xref.length > maxBytes) fail('export_too_large');
|
|
890
936
|
const bytes = new Uint8Array(cursor + xref.length);
|
|
891
937
|
bytes.set(header, 0);
|
|
@@ -899,6 +945,8 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
899
945
|
bytes.set(suffix, at); at += suffix.length;
|
|
900
946
|
}
|
|
901
947
|
bytes.set(xref, at);
|
|
948
|
+
bytes.set(encode(await pdfSourceDigest(bytes, digestAt, bytes.length - 1)), digestAt);
|
|
949
|
+
check();
|
|
902
950
|
return bytes;
|
|
903
951
|
}
|
|
904
952
|
export {createPrintRenderer, applyPrintCapitals, RAPIER_WILL_SENTINEL_MARK as sentinel};
|
package/dist/kit/render.mjs
CHANGED
|
@@ -9,6 +9,7 @@ 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
11
|
import {isTemplatePage, isPageBorderLayout} from '../spec/template-page.mjs';
|
|
12
|
+
import {_rapierArtifactFactoryModules} from './artifact-modules.mjs';
|
|
12
13
|
export function positionNativeDiagram(drawing) {
|
|
13
14
|
const surface=drawing?.closest?.('.diagram-block,.diagram-live-preview');
|
|
14
15
|
if(!surface?.clientWidth)return;
|
|
@@ -348,9 +349,14 @@ function* _rapierProjectStyledRootSteps(semanticRoot, options) {
|
|
|
348
349
|
yield;
|
|
349
350
|
cache.innerHTML = sanitizeRapierHtml(cache.innerHTML, 'export');
|
|
350
351
|
}
|
|
351
|
-
return root;
|
|
352
352
|
}
|
|
353
|
-
|
|
353
|
+
const admitted = opts.sanitized ? root : yield* cleanTree(root, sanitizeRapierHtml, 'export');
|
|
354
|
+
for (const list of admitted.querySelectorAll('ol[start]')) {
|
|
355
|
+
yield;
|
|
356
|
+
const start = list.getAttribute('start');
|
|
357
|
+
if (/^-?\d{1,9}$/.test(start)) list.style.setProperty('counter-reset', 'rapier-ol ' + (Number(start) - 1));
|
|
358
|
+
}
|
|
359
|
+
return admitted;
|
|
354
360
|
}
|
|
355
361
|
|
|
356
362
|
function _rapierDocxRenderStyles(root) {
|
|
@@ -1280,11 +1286,6 @@ function _rapierImageOpenRunsList(tokens) {
|
|
|
1280
1286
|
return list;
|
|
1281
1287
|
}
|
|
1282
1288
|
|
|
1283
|
-
function _rapierArtifactFactoryModules(dependencies, group) {
|
|
1284
|
-
const paths = dependencies.groups && dependencies.groups[group] ? dependencies.groups[group] : Object.keys(dependencies.factories);
|
|
1285
|
-
return paths.map(path => 'modules[' + JSON.stringify(path) + '] = (' + Function.prototype.toString.call(dependencies.factories[path]) + ')();').join('\n');
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
1289
|
function _rapierProjectArtifactLayout(root, metadata, geometry, preparation) {
|
|
1289
1290
|
if (!root || !metadata || !geometry || !preparation) return null;
|
|
1290
1291
|
const doc = root.ownerDocument, view = doc.defaultView;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
let graphemes;
|
|
3
|
+
export const segment = text => (graphemes ||= new Intl.Segmenter(undefined, {granularity: 'grapheme'})).segment(text);
|
|
4
|
+
export const opening = /^[\p{Ps}\p{Pi}\p{Pf}"']+$/u;
|
|
5
|
+
|
|
6
|
+
export function initialRange(input) {
|
|
7
|
+
const text = typeof input === 'string' ? input : input?.textContent;
|
|
8
|
+
if (typeof text !== 'string') return null;
|
|
9
|
+
const start = /^\s*/u.exec(text)[0].length;
|
|
10
|
+
if (start === text.length) return null;
|
|
11
|
+
let end = start;
|
|
12
|
+
for (const part of segment(text.slice(start))) {
|
|
13
|
+
if (/^\s/u.test(part.segment)) break;
|
|
14
|
+
end = start + part.index + part.segment.length;
|
|
15
|
+
if (!opening.test(part.segment)) break;
|
|
16
|
+
}
|
|
17
|
+
return end > start ? {start, end, text: text.slice(start, end)} : null;
|
|
18
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
import {createPreparationScope, supportsRichStyle, captureFontAuthority} from './preparation.mjs';
|
|
3
3
|
import {createParagraphFitter} from './paragraph-fit.mjs';
|
|
4
|
+
import {initialRange, segment, opening} from './initial-range.mjs';
|
|
4
5
|
|
|
5
6
|
export function createTypography() {
|
|
6
7
|
const fitter = createParagraphFitter();
|
|
@@ -16,25 +17,8 @@ export function createTypography() {
|
|
|
16
17
|
if (!value) { value = createPreparationScope(document); documents.set(document, value); }
|
|
17
18
|
return value;
|
|
18
19
|
}
|
|
19
|
-
let graphemes;
|
|
20
|
-
const segment = text => (graphemes ||= new Intl.Segmenter(undefined, {granularity: 'grapheme'})).segment(text);
|
|
21
|
-
const opening = /^[\p{Ps}\p{Pi}\p{Pf}"']+$/u;
|
|
22
20
|
const closing = /^[\p{Pe}\p{Pi}\p{Pf}"'.,;:!?]+$/u;
|
|
23
21
|
|
|
24
|
-
function initialRange(input) {
|
|
25
|
-
const text = typeof input === 'string' ? input : input?.textContent;
|
|
26
|
-
if (typeof text !== 'string') return null;
|
|
27
|
-
const start = /^\s*/u.exec(text)[0].length;
|
|
28
|
-
if (start === text.length) return null;
|
|
29
|
-
let end = start;
|
|
30
|
-
for (const part of segment(text.slice(start))) {
|
|
31
|
-
if (/^\s/u.test(part.segment)) break;
|
|
32
|
-
end = start + part.index + part.segment.length;
|
|
33
|
-
if (!opening.test(part.segment)) break;
|
|
34
|
-
}
|
|
35
|
-
return end > start ? {start, end, text: text.slice(start, end)} : null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
22
|
const flowFloat = '.rapier-flow-float';
|
|
39
23
|
const own = paragraph => [paragraph, ...[...paragraph.querySelectorAll('*')].filter(element => !element.closest(flowFloat))];
|
|
40
24
|
|
|
@@ -770,7 +754,7 @@ export const apply = typography.apply;
|
|
|
770
754
|
export const clear = typography.clear;
|
|
771
755
|
export const clearSelection = typography.clearSelection;
|
|
772
756
|
export const firstLineEnd = typography.firstLineEnd;
|
|
773
|
-
export
|
|
757
|
+
export {initialRange};
|
|
774
758
|
export const protectInitial = typography.protectInitial;
|
|
775
759
|
export const invalidateFonts = typography.invalidateFonts;
|
|
776
760
|
export const retire = typography.retire;
|
|
@@ -5,7 +5,7 @@ export function separate(c, endA = c.before.length, endB = c.after.length) {
|
|
|
5
5
|
const {before, after, safe, raw, spend} = c;
|
|
6
6
|
if (!raw) for (let i = c.result.length - 1; i > 0; i--) {
|
|
7
7
|
const p = c.result[i - 1], q = c.result[i], side = p[0][0] === p[0][1] ? 1 : p[1][0] === p[1][1] ? 0 : -1, text = side ? after : before;
|
|
8
|
-
if (side >= 0 && !/\S/.test(text.slice(...p[side]))) {
|
|
8
|
+
if (!c.evidence && side >= 0 && !/\S/.test(text.slice(...p[side]))) {
|
|
9
9
|
let n = 0;
|
|
10
10
|
while (p[0][1] + n < q[0][0] && text[p[side][0] + n] === text[p[side][1] + n] && spend()) n++;
|
|
11
11
|
if (p[0][1] + n === q[0][0] && p.slice(0, 2).every((span, k) => safe(k ? after : before, span[0] + n) && safe(k ? after : before, span[1] + n)))
|
|
@@ -20,7 +20,7 @@ export function separate(c, endA = c.before.length, endB = c.after.length) {
|
|
|
20
20
|
keep[3][0] = x[0] === x[1] ? 2 : y[0] === y[1] ? 0 : 1;
|
|
21
21
|
c.result.splice(i - 1, 2, [x, y, false, keep[3]]);
|
|
22
22
|
}
|
|
23
|
-
if (!raw) for (let i = 0; i < c.result.length; i++) {
|
|
23
|
+
if (!raw && !c.evidence) for (let i = 0; i < c.result.length; i++) {
|
|
24
24
|
const p = c.result[i], q = c.result[i + 1], side = p[0][0] === p[0][1] ? 1 : p[1][0] === p[1][1] ? 0 : -1;
|
|
25
25
|
if (side < 0 || p[2]) continue;
|
|
26
26
|
const text = side ? after : before;
|
|
@@ -18,12 +18,19 @@ function parserFor(parser) {
|
|
|
18
18
|
return md;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
const sourceMetadataFields = ['license', 'origin'];
|
|
22
|
+
function sourceMetadata(value) {
|
|
23
|
+
return Object.fromEntries(sourceMetadataFields.filter(field => value[field] != null).map(field => [field, value[field]]));
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
function faceOf(value) {
|
|
22
27
|
const family = normalizeFontFamily(value?.family), style = value?.style || 'normal';
|
|
23
28
|
const weight = value?.weight ?? 400, stretch = value?.stretch ?? 100;
|
|
24
29
|
if (!family || !['normal', 'italic', 'oblique'].includes(style) || !Number.isFinite(weight) || weight < 1 || weight > 1000 ||
|
|
25
30
|
!Number.isFinite(stretch) || stretch <= 0 || stretch > 1000) return null;
|
|
26
31
|
const result = {family, style, weight, stretch};
|
|
32
|
+
if (value.subsetted != null && typeof value.subsetted !== 'boolean') return null;
|
|
33
|
+
if (value.subsetted === true || value.subset?.subset === true) result.subsetted = true;
|
|
27
34
|
if (value.weightRange != null) {
|
|
28
35
|
const range = value.weightRange;
|
|
29
36
|
if (!Array.isArray(range) || range.length !== 2 || range.some(number => !Number.isFinite(number) || number < 1 || number > 1000) || range[0] > range[1]) return null;
|
|
@@ -44,6 +51,25 @@ function faceOf(value) {
|
|
|
44
51
|
if (!/^f[0-9a-f]{24}$/.test(value.sourceId)) return null;
|
|
45
52
|
result.sourceId = value.sourceId;
|
|
46
53
|
}
|
|
54
|
+
if (value.license != null) {
|
|
55
|
+
const license = value.license;
|
|
56
|
+
if (!license || !['OFL-1.0', 'OFL-1.1', 'Apache-2.0', 'Ubuntu-font-1.0'].includes(license.id) ||
|
|
57
|
+
typeof license.text !== 'string' || !license.text.trim() || new TextEncoder().encode(license.text).length > 128 * 1024 ||
|
|
58
|
+
typeof license.url !== 'string' || typeof license.sri !== 'string' || !/^sha384-[A-Za-z0-9+/]{64}$/.test(license.sri)) return null;
|
|
59
|
+
let url;
|
|
60
|
+
try { url = new URL(license.url); } catch (_) { return null; }
|
|
61
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) return null;
|
|
62
|
+
result.license = {id: license.id, text: license.text, url: license.url, sri: license.sri};
|
|
63
|
+
}
|
|
64
|
+
if (value.origin != null) {
|
|
65
|
+
const origin = value.origin;
|
|
66
|
+
if (!result.license || result.subsetted || !origin || origin.catalog !== 'google-fonts' ||
|
|
67
|
+
typeof origin.family !== 'string' || !/^[a-z0-9][a-z0-9-]{0,95}$/.test(origin.family) ||
|
|
68
|
+
typeof origin.version !== 'string' || !origin.version || origin.version.length > 128 || /[\u0000-\u001f\u007f]/u.test(origin.version) ||
|
|
69
|
+
typeof origin.file !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}\.woff2$/.test(origin.file) ||
|
|
70
|
+
typeof origin.sri !== 'string' || !/^sha384-[A-Za-z0-9+/]{64}$/.test(origin.sri)) return null;
|
|
71
|
+
result.origin = {catalog: origin.catalog, family: origin.family, version: origin.version, file: origin.file, sri: origin.sri};
|
|
72
|
+
}
|
|
47
73
|
return result;
|
|
48
74
|
}
|
|
49
75
|
|
|
@@ -63,7 +89,7 @@ export function readFontAssets(markdown, parser) {
|
|
|
63
89
|
else if (source[at] === '\n') starts.push(at + 1);
|
|
64
90
|
}
|
|
65
91
|
md.block.parse(frame.body.replace(/\r\n?/g, '\n').replace(/\0/g, '\ufffd'), md, env, tokens);
|
|
66
|
-
const labels = new Set(), faces = new Set();
|
|
92
|
+
const labels = new Set(), faces = new Set(), metadata = new Map(), faceMetadata = new Map(), subsetUrls = new Set();
|
|
67
93
|
for (const token of tokens) {
|
|
68
94
|
if (token.type !== 'reference_definition' || !token.map) continue;
|
|
69
95
|
const id = token.meta.label, first = !labels.has(id);
|
|
@@ -79,18 +105,33 @@ export function readFontAssets(markdown, parser) {
|
|
|
79
105
|
if (!match) continue;
|
|
80
106
|
const label = md.utils.unescapeAll(match[1]);
|
|
81
107
|
if (!data) { refused.push({name: label, reason: 'The font reference must contain an embedded font.'}); continue; }
|
|
82
|
-
let
|
|
108
|
+
let definitionMetadata = {family: label.slice(5)};
|
|
83
109
|
if (definition.title) {
|
|
84
|
-
try {
|
|
110
|
+
try { definitionMetadata = JSON.parse(definition.title); }
|
|
85
111
|
catch (_) { refused.push({name: label, reason: 'The font reference has invalid metadata.'}); continue; }
|
|
86
112
|
}
|
|
87
|
-
const face = faceOf(
|
|
113
|
+
const face = faceOf(definitionMetadata);
|
|
88
114
|
if (!face) { refused.push({name: label, reason: 'The font reference has invalid metadata.'}); continue; }
|
|
89
115
|
const key = fontAssetKey(face), active = first && !faces.has(key);
|
|
116
|
+
const conflict = sourceMetadataFields.find(field => face[field] && [metadata.get(url)?.[field], faceMetadata.get(key)?.[field]]
|
|
117
|
+
.some(prior => prior && JSON.stringify(prior) !== JSON.stringify(face[field])));
|
|
118
|
+
if (conflict) { refused.push({name: label, reason: 'The font references have conflicting ' + conflict + ' metadata.'}); continue; }
|
|
119
|
+
if (face.subsetted && metadata.get(url)?.origin || face.origin && subsetUrls.has(url)) {
|
|
120
|
+
refused.push({name: label, reason: 'A font subset cannot claim an original catalog file.'}); continue;
|
|
121
|
+
}
|
|
122
|
+
metadata.set(url, {...metadata.get(url), ...sourceMetadata(face)});
|
|
123
|
+
faceMetadata.set(key, {...faceMetadata.get(key), ...sourceMetadata(face)});
|
|
124
|
+
if (face.subsetted) subsetUrls.add(url);
|
|
90
125
|
const row = {...face, ...data, url, label, id, key, source: raw, start, end, next, active, topLevel: token.level === 0};
|
|
91
126
|
blocks.push(row);
|
|
92
127
|
if (active) { assets.push(row); faces.add(key); }
|
|
93
128
|
}
|
|
129
|
+
for (let index = 0; index < assets.length; index++) {
|
|
130
|
+
const asset = assets[index], declared = metadata.get(asset.url);
|
|
131
|
+
for (const field of sourceMetadataFields) if (!asset[field] && declared?.[field])
|
|
132
|
+
assets[index] = {...assets[index], [field]: declared[field]};
|
|
133
|
+
if (subsetUrls.has(asset.url) && !asset.subsetted) assets[index] = {...assets[index], subsetted: true};
|
|
134
|
+
}
|
|
94
135
|
return {assets, blocks, refused, references: env.references || Object.create(null)};
|
|
95
136
|
}
|
|
96
137
|
|
|
@@ -113,13 +154,26 @@ function escapeFontLabel(label) {
|
|
|
113
154
|
}
|
|
114
155
|
|
|
115
156
|
export function writeFontAssets(markdown, assets, parser) {
|
|
116
|
-
const source = String(markdown ?? ''), md = parserFor(parser), index = readFontAssets(source, md), wanted = new Map();
|
|
157
|
+
const source = String(markdown ?? ''), md = parserFor(parser), index = readFontAssets(source, md), wanted = new Map(), metadata = new Map(), subsetUrls = new Set();
|
|
117
158
|
for (const asset of assets || []) {
|
|
118
|
-
const key = fontAssetKey(asset);
|
|
159
|
+
const key = fontAssetKey(asset), face = faceOf(asset);
|
|
119
160
|
if (!key || !fontDataUrl(asset?.url)) throw new TypeError('font: invalid embedded asset');
|
|
120
161
|
if (wanted.has(key) && wanted.get(key).url !== asset.url) throw new TypeError('font: one file owns each family face');
|
|
162
|
+
const conflict = sourceMetadataFields.find(field => face[field] && metadata.get(asset.url)?.[field] &&
|
|
163
|
+
JSON.stringify(metadata.get(asset.url)[field]) !== JSON.stringify(face[field]));
|
|
164
|
+
if (conflict) throw new TypeError('font: conflicting ' + conflict + ' metadata');
|
|
165
|
+
if (face.subsetted && metadata.get(asset.url)?.origin || face.origin && subsetUrls.has(asset.url))
|
|
166
|
+
throw new TypeError('font: a subset cannot claim an original catalog file');
|
|
167
|
+
metadata.set(asset.url, {...metadata.get(asset.url), ...sourceMetadata(face)});
|
|
168
|
+
if (face.subsetted) subsetUrls.add(asset.url);
|
|
121
169
|
wanted.set(key, asset);
|
|
122
170
|
}
|
|
171
|
+
for (const [key, asset] of wanted) {
|
|
172
|
+
const declared = metadata.get(asset.url);
|
|
173
|
+
for (const field of sourceMetadataFields) if (!asset[field] && declared?.[field])
|
|
174
|
+
wanted.set(key, {...wanted.get(key), [field]: declared[field]});
|
|
175
|
+
if (subsetUrls.has(asset.url) && !asset.subsetted) wanted.set(key, {...wanted.get(key), subsetted: true});
|
|
176
|
+
}
|
|
123
177
|
const edits = [], consumed = new Set(), labels = new Set(Object.keys(index.references));
|
|
124
178
|
const retired = index.assets.filter(row => !wanted.has(row.key));
|
|
125
179
|
for (const row of index.blocks) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.49",
|
|
4
4
|
"description": "Read, render and write Self-contained Markdown: one .md file that carries its pictures, drawings and layout, and that any Markdown app can read. HTML renderer, stylesheet, text layout, Will/1 agent permissions, and an edit history with authorship and offline merge. MIT, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"markdown",
|
package/style.css
CHANGED
|
@@ -264,9 +264,12 @@
|
|
|
264
264
|
@media (prefers-reduced-motion:reduce){
|
|
265
265
|
:where(.md-render) svg.rapier-diagram[aria-roledescription^="flowchart"] :is(path,line,polyline):is(.edge-animation-fast,.edge-animation-slow){animation:none!important;stroke-dashoffset:0!important}
|
|
266
266
|
}
|
|
267
|
-
/* Printed diagrams fit the page
|
|
267
|
+
/* Printed diagrams fit the page's width, at their own size; a drawing taller than a page continues onto the next (the
|
|
268
|
+
export slices it, the browser's print fragments it) and the figure's screen height is not held. */
|
|
268
269
|
@media print{
|
|
269
|
-
:where(.md-render) .diagram-block{overflow:visible!important}
|
|
270
|
+
:where(.md-render) .diagram-block{overflow:visible!important;min-height:0!important}
|
|
271
|
+
/* The frame's rules are a background, not ink, and the figure's box and its cache hold no words: the drawing alone is the block. */
|
|
272
|
+
:where(.md-render) .diagram-block:is([data-diagram-state="ready"],[data-diagram-state="idle"]),:where(.md-render) .diagram-block .diagram-cache{display:contents!important}
|
|
270
273
|
:where(.md-render) svg.rapier-diagram,:where(.md-render) svg.rapier-native-flowchart{width:auto!important;max-width:100%!important}
|
|
271
274
|
}
|
|
272
275
|
:where(.md-render) .diagram-reason {
|