rapier-markdown-kit 1.2.46 → 1.2.48
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 +36 -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/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) {
|
|
@@ -229,7 +230,8 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
229
230
|
if (bodyWidth < 1 || bodyHeight < 1) refuse('pdf_page_geometry_invalid');
|
|
230
231
|
const tiled = bodyWidth > CAPTURE_EDGE || bodyHeight > CAPTURE_EDGE || bodyWidth * bodyHeight > CAPTURE_PIXELS;
|
|
231
232
|
const frame = document.createElement('iframe');
|
|
232
|
-
let vectors, backdrop = null, nativeGlyphs, filmSession;
|
|
233
|
+
let vectors, backdrop = null, nativeGlyphs, filmSession, typography;
|
|
234
|
+
const omitted = [];
|
|
233
235
|
let rejectAbort;
|
|
234
236
|
const aborted = new Promise((_, reject) => { rejectAbort = reject; });
|
|
235
237
|
aborted.catch(() => {});
|
|
@@ -281,7 +283,18 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
281
283
|
main.getBoundingClientRect();
|
|
282
284
|
if (fontCss) await step(waitForFonts(main, {fontCss, css, settings}));
|
|
283
285
|
await step(into.fonts.ready);
|
|
284
|
-
for (const image of main.querySelectorAll('img')) {
|
|
286
|
+
for (const image of main.querySelectorAll('img')) {
|
|
287
|
+
image.loading = 'eager';
|
|
288
|
+
try { await step(image.decode()); }
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error?.name !== 'EncodingError') throw error;
|
|
291
|
+
const source = image.getAttribute('src') || '';
|
|
292
|
+
omitted.push(source.startsWith('data:') ? image.getAttribute('alt') || 'a picture' : source.slice(0, 120));
|
|
293
|
+
const holder = image.closest('p,figure');
|
|
294
|
+
image.remove();
|
|
295
|
+
if (holder && !holder.textContent.trim() && !holder.querySelector('img,svg,math,table,pre')) holder.remove();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
285
298
|
wrapMasks(main);
|
|
286
299
|
check();
|
|
287
300
|
for (const node of [main, ...main.querySelectorAll('*')]) {
|
|
@@ -303,7 +316,8 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
303
316
|
main.style.setProperty(name, value, 'important');
|
|
304
317
|
globalThis.RapierMath?.fit(main);
|
|
305
318
|
applyPrintCapitals(main);
|
|
306
|
-
|
|
319
|
+
typography = printTypography(main, globalThis.RapierArtifactLayoutDependencies);
|
|
320
|
+
typography?.apply(main, {all: true});
|
|
307
321
|
ink?.(main);
|
|
308
322
|
const count = Math.max(1, Math.ceil((main.scrollWidth - 1) / bodyWidth));
|
|
309
323
|
if (!Number.isSafeInteger(count)) refuse('pdf_page_geometry_invalid');
|
|
@@ -421,9 +435,9 @@ async function _rapierWritePrintPdf({root, css, fontCss = '', canonical, bom = f
|
|
|
421
435
|
const bytes = await writeRasterPdf({pages, textPages, linkPages, vectorPages: vectors.pages, canonical, bom, filename, title, font, width, height, margins, backdrop: backdrop?.pages[0] || null,
|
|
422
436
|
pixelWidth: bodyWidth, pixelHeight: bodyHeight, pageNumbers: settings?.pagestyle === 'plain', maxBytes, check});
|
|
423
437
|
check();
|
|
424
|
-
return {bytes, pages: count, painted: nativeGlyphs.painted};
|
|
438
|
+
return {bytes, pages: count, painted: nativeGlyphs.painted, omitted};
|
|
425
439
|
} catch (error) { throw spoken(error); }
|
|
426
|
-
finally { filmSession?.close(); backdrop?.restore(); vectors?.restore(); nativeGlyphs?.restore(); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
440
|
+
finally { filmSession?.close(); backdrop?.restore(); vectors?.restore(); nativeGlyphs?.restore(); typography?.retire(frame.contentDocument); signal?.removeEventListener('abort', abort); frame.remove(); }
|
|
427
441
|
}
|
|
428
442
|
|
|
429
443
|
async function _rapierPrintTextPages(main, pageWidth, count, check, origin = main.getBoundingClientRect(), nativeNodes = null, markerNodes = null) {
|
|
@@ -442,6 +456,18 @@ async function _rapierPrintTextPages(main, pageWidth, count, check, origin = mai
|
|
|
442
456
|
}
|
|
443
457
|
const marker = markerNodes?.get(node);
|
|
444
458
|
if (marker && marker.page < count) pages[marker.page].push({...marker, block: node, carrier: null, marker: true});
|
|
459
|
+
const taskItem = node.nodeType === 1 && node.matches('input[type="checkbox"]') && node.closest('li');
|
|
460
|
+
const taskParent = taskItem && node.parentElement;
|
|
461
|
+
if (taskItem && taskParent.firstElementChild === node && (taskParent === taskItem || taskParent.localName === 'p' && taskParent.parentElement === taskItem && taskItem.firstElementChild === taskParent)) {
|
|
462
|
+
const box = node.getBoundingClientRect(), style = into.defaultView.getComputedStyle(node);
|
|
463
|
+
if (box.width > 0 && box.height > 0 && style.visibility !== 'hidden' && style.visibility !== 'collapse') {
|
|
464
|
+
const page = Math.max(0, Math.min(count - 1, Math.floor((box.left - origin.left + .01) / pageWidth)));
|
|
465
|
+
const size = parseFloat(style.fontSize) || 16, width = Math.min(size, box.width), height = Math.min(size, box.height);
|
|
466
|
+
const text = node.checked ? '[x] ' : '[ ] ', part = {text, x: box.left - origin.left - page * pageWidth + (box.width - width) / 2,
|
|
467
|
+
y: box.top - origin.top + (box.height - height) / 2, width, height};
|
|
468
|
+
pages[page].push({...part, parts: [part], block: taskItem, carrier: null, marker: true});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
445
471
|
const formula = node.nodeType === 1 && node.matches('.math-rendered[data-math-src]') && node.querySelector('svg');
|
|
446
472
|
if (node.nodeType === 1 && !formula || node.nodeType === 3 && element.closest('.math-rendered[data-math-src]')) continue;
|
|
447
473
|
let value;
|
|
@@ -885,7 +911,8 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
885
911
|
cursor += (id + ' 0 obj\n').length + objects[id].length + 8;
|
|
886
912
|
}
|
|
887
913
|
const xrefAt = cursor;
|
|
888
|
-
const
|
|
914
|
+
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';
|
|
915
|
+
const xref = encode(table), digestAt = xrefAt + table.indexOf(PDF_SOURCE_DIGEST) + PDF_SOURCE_DIGEST.indexOf('<') + 1;
|
|
889
916
|
if (cursor + xref.length > maxBytes) fail('export_too_large');
|
|
890
917
|
const bytes = new Uint8Array(cursor + xref.length);
|
|
891
918
|
bytes.set(header, 0);
|
|
@@ -899,6 +926,8 @@ async function writeRasterPdf({pages, textPages, linkPages = [], vectorPages = [
|
|
|
899
926
|
bytes.set(suffix, at); at += suffix.length;
|
|
900
927
|
}
|
|
901
928
|
bytes.set(xref, at);
|
|
929
|
+
bytes.set(encode(await pdfSourceDigest(bytes, digestAt, bytes.length - 1)), digestAt);
|
|
930
|
+
check();
|
|
902
931
|
return bytes;
|
|
903
932
|
}
|
|
904
933
|
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.48",
|
|
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",
|