rapier-markdown-kit 1.2.31 → 1.2.33
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 +2 -2
- package/dist/kit/ledger/carried.mjs +14 -2
- package/dist/kit/ledger/text-merge.mjs +163 -26
- package/dist/kit/markdown-it.mjs +19 -5
- package/dist/kit/pdf-glyphs.mjs +2 -2
- package/dist/kit/pdf-vectors.mjs +29 -9
- package/dist/kit/render-markdown.mjs +8 -1
- package/dist/kit/render-print.mjs +67 -26
- package/dist/kit/render.mjs +36 -19
- package/dist/layout/hyphenation.mjs +15 -3
- package/dist/layout/model.mjs +9 -1
- package/dist/layout/paragraph-fit.mjs +3 -3
- package/dist/layout/typography.mjs +149 -29
- package/dist/spec/document-fonts.mjs +5 -0
- package/dist/spec/document-settings.mjs +7 -5
- package/dist/spec/html-blocks.mjs +30 -11
- package/dist/spec/template-page.mjs +2 -4
- package/package.json +1 -1
- package/style.css +29 -5
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ import {parseAssets} from 'rapier-markdown-kit/assets'; // the picture appendi
|
|
|
57
57
|
## Choose a Rapier workflow
|
|
58
58
|
|
|
59
59
|
Rapier is a phone-first Markdown editor for notes, diagrams, drawing and watercolor painting. It runs offline
|
|
60
|
-
as one HTML file on Android, Web and
|
|
60
|
+
as one HTML file on Android, Web, Windows and Linux.
|
|
61
61
|
|
|
62
62
|
1. **Work together live.** Connect to `https://mcp.rapier.website/door/mcp`, then call `open` to share one document with the person.
|
|
63
63
|
2. **Deliver an offline editor.** Run `npx rapier-html notes.md` to put the document and editor in one HTML file ([rapier-html](https://github.com/jackskip22/rapier-plugins/tree/main/npm/rapier-html)).
|
|
@@ -164,7 +164,7 @@ family name share the host's font namespace. Print retains its existing factory
|
|
|
164
164
|
|
|
165
165
|
| Import | What it is |
|
|
166
166
|
| --- | --- |
|
|
167
|
-
| `rapier-markdown-kit/markdown-it` | `md.use(rapierMarkdown)`: the reference style's markup in markdown-it, in one line. |
|
|
167
|
+
| `rapier-markdown-kit/markdown-it` | `md.use(rapierMarkdown)`: the reference style's markup in markdown-it, in one line, with rung 1 of `md-layout:v1` (align, indents, list markers, picture width, lines and opacity, paragraph typography). |
|
|
168
168
|
| `rapier-markdown-kit/parsed` | A frozen, serializable view of completed Markdown tokens, bound to source, revision and grammar. |
|
|
169
169
|
| `rapier-markdown-kit/layout` | The `md-layout:v1` comment a picture carries: parse it, validate it, write it back. |
|
|
170
170
|
| `rapier-markdown-kit/marks` | Unified typography values, span writer and parser installer; preserved color/font comments, ink and page breaks. |
|
|
@@ -50,15 +50,27 @@ const OPEN = '\n\n<!-- rapier-carried/1\n', CLOSE = '\n-->\n';
|
|
|
50
50
|
export function documentTrailer(text, options) {
|
|
51
51
|
const parts = validateParts(text, options);
|
|
52
52
|
if (!parts.ledger && !parts.authorship) return '';
|
|
53
|
-
const json = JSON.stringify(parts).replace(/</g, '\\u003c').replace(/>/g, '\\u003e');
|
|
53
|
+
const json = JSON.stringify(parts).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/-/g, '\\u002d');
|
|
54
54
|
return OPEN + json + CLOSE;
|
|
55
55
|
}
|
|
56
56
|
export function writeDocument(text, options) {
|
|
57
57
|
return text + documentTrailer(text, options);
|
|
58
58
|
}
|
|
59
|
+
function unfinishedTrailer(source) {
|
|
60
|
+
const at = source.lastIndexOf(OPEN);
|
|
61
|
+
if (at < 0) return false;
|
|
62
|
+
let fence = null;
|
|
63
|
+
for (const line of source.slice(0, at).split('\n')) {
|
|
64
|
+
const found = /^ {0,3}(`{3,}|~{3,})/.exec(line);
|
|
65
|
+
if (!found) continue;
|
|
66
|
+
if (!fence) fence = found[1];
|
|
67
|
+
else if (found[1][0] === fence[0] && found[1].length >= fence.length && !line.slice(found[0].length).trim()) fence = null;
|
|
68
|
+
}
|
|
69
|
+
return !fence;
|
|
70
|
+
}
|
|
59
71
|
export function readDocument(source) {
|
|
60
72
|
if (!source.endsWith(CLOSE)) {
|
|
61
|
-
if (source
|
|
73
|
+
if (unfinishedTrailer(source)) throw fault('unfinished document metadata');
|
|
62
74
|
return {text: source, ledger: null, authorship: null};
|
|
63
75
|
}
|
|
64
76
|
const at = source.lastIndexOf(OPEN);
|
|
@@ -272,13 +272,167 @@ function textDeclarations(units) {
|
|
|
272
272
|
return [...definitions, ...continuations, ...(text.match(/<!--[\s\S]*?(?:-->|$)/g) || [])].join('\0');
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
function* textGroups(units, a, b) {
|
|
276
|
+
const changes = [...a.map(e => ({...e, side: 0})), ...b.map(e => ({...e, side: 1}))].sort((x, y) => x.start - y.start || x.end - y.end || x.side - y.side);
|
|
277
|
+
for (let i = 0; i < changes.length;) {
|
|
278
|
+
const group = [changes[i++]], start = group[0].start; let end = group[0].end;
|
|
279
|
+
while (i < changes.length && (changes[i].start < end || group.some(e => {
|
|
280
|
+
const next = changes[i];
|
|
281
|
+
const afterBlock = (edit, insert) => insert.start === insert.end && insert.start === edit.end && edit.start < edit.end && units.slice(edit.start, edit.end).every(unit => textEnvelopeAt(unit));
|
|
282
|
+
if (afterBlock(e, next) || afterBlock(next, e)) return false;
|
|
283
|
+
if (e.start === e.end && next.start <= e.start && e.start <= next.end) return true;
|
|
284
|
+
if (next.start === next.end && e.start <= next.start && next.start <= e.end) return true;
|
|
285
|
+
return next.start >= e.end && units.slice(e.start, e.end).every(textSpace) &&
|
|
286
|
+
units.slice(e.end, next.start).every(textSpace) && units.slice(next.start, next.end).every(textSpace);
|
|
287
|
+
}))) {
|
|
288
|
+
group.push(changes[i]); end = Math.max(end, changes[i++].end);
|
|
289
|
+
}
|
|
290
|
+
const before = units.slice(start, end).join('');
|
|
291
|
+
const version = side => {
|
|
292
|
+
let value = '', at = start;
|
|
293
|
+
for (const e of group.filter(e => e.side === side)) { value += units.slice(at, e.start).join('') + e.text; at = e.end; }
|
|
294
|
+
return value + units.slice(at, end).join('');
|
|
295
|
+
};
|
|
296
|
+
yield {start, end, before, av: version(0), bv: version(1)};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function textMoves(groups) {
|
|
301
|
+
const blank = value => !value.trim();
|
|
302
|
+
const side = (g, which) => which === 0 ? g.av : g.bv;
|
|
303
|
+
for (const g of groups) {
|
|
304
|
+
if (g.resolved !== undefined || blank(g.before)) continue;
|
|
305
|
+
for (const gone of [0, 1]) {
|
|
306
|
+
const removed = side(g, gone), other = side(g, 1 - gone);
|
|
307
|
+
if (!blank(removed) || other === g.before) continue;
|
|
308
|
+
if (other.split(g.before).length === 2) { g.resolved = other.replace(g.before, ''); break; }
|
|
309
|
+
const homes = groups.filter(h => h !== g && h.resolved === undefined && blank(h.before) && blank(side(h, 1 - gone)) && side(h, gone).trim() === g.before.trim());
|
|
310
|
+
if (homes.length === 1) {
|
|
311
|
+
const trailing = side(homes[0], gone).match(/\s*$/)[0], edited = other.replace(/\s*$/, '');
|
|
312
|
+
g.resolved = ''; homes[0].resolved = side(homes[0], gone).match(/^\s*/)[0] + edited + trailing; break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return groups;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const textLines = text => text.match(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g) || [];
|
|
320
|
+
const textWords = text => text.match(/\s+|\S+/g) || [];
|
|
321
|
+
const TEXT_ITEM = /^( {0,3})(?:[-+*]|\d{1,9}[.)])[ \t]+\S/;
|
|
322
|
+
const TEXT_TAGS = /^(tags|aliases):[ \t]*\[([^\[\]\r\n]*)\][ \t]*(\r\n|\r|\n|$)/;
|
|
323
|
+
function textCounts(text) {
|
|
324
|
+
const counts = new Map();
|
|
325
|
+
for (const word of text.match(/[\p{L}\p{N}_]+/gu) || []) counts.set(word, (counts.get(word) || 0) + 1);
|
|
326
|
+
return counts;
|
|
327
|
+
}
|
|
328
|
+
function textConserved(before, av, bv, merged) {
|
|
329
|
+
const [b, a, t, m] = [before, av, bv, merged].map(textCounts);
|
|
330
|
+
for (const word of new Set([...b.keys(), ...a.keys(), ...t.keys(), ...m.keys()])) {
|
|
331
|
+
const base = b.get(word) || 0, da = (a.get(word) || 0) - base, dt = (t.get(word) || 0) - base, got = (m.get(word) || 0) - base;
|
|
332
|
+
const both = [da + dt, da, dt];
|
|
333
|
+
if (da * dt > 0 ? got < Math.min(...both) || got > Math.max(...both) : got !== da + dt) return false;
|
|
334
|
+
}
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
function textRefine(before, av, bv, options, level = 'line') {
|
|
338
|
+
const merged = textRefineLevel(before, av, bv, options, level);
|
|
339
|
+
return merged !== null && textConserved(before, av, bv, merged) ? merged : null;
|
|
340
|
+
}
|
|
341
|
+
function textRefineLevel(before, av, bv, options, level) {
|
|
342
|
+
const split = level === 'line' ? textLines : textWords;
|
|
343
|
+
const units = split(before), oursUnits = split(av), theirsUnits = split(bv);
|
|
344
|
+
const reordered = textPermuted(units, oursUnits, theirsUnits);
|
|
345
|
+
if (reordered !== null) return reordered;
|
|
346
|
+
const a = textEdits(units, oursUnits), b = textEdits(units, theirsUnits);
|
|
347
|
+
if (!a || !b) return null;
|
|
348
|
+
let out = '', pos = 0;
|
|
349
|
+
for (const g of textGroups(units, a, b)) {
|
|
350
|
+
out += units.slice(pos, g.start).join('');
|
|
351
|
+
if (g.av === g.bv) out += g.av;
|
|
352
|
+
else if (g.av === g.before) out += g.bv;
|
|
353
|
+
else if (g.bv === g.before) out += g.av;
|
|
354
|
+
else {
|
|
355
|
+
const fine = level === 'line' ? textRefineGroup(g, options) : null;
|
|
356
|
+
if (fine === null) return null;
|
|
357
|
+
out += fine;
|
|
358
|
+
}
|
|
359
|
+
pos = g.end;
|
|
360
|
+
}
|
|
361
|
+
return out + units.slice(pos).join('');
|
|
362
|
+
}
|
|
363
|
+
function textPermuted(units, ours, theirs) {
|
|
364
|
+
const solid = list => list.filter(unit => !textSpace(unit));
|
|
365
|
+
const baseSolid = solid(units), known = new Map(baseSolid.map((unit, k) => [unit, k]));
|
|
366
|
+
if (known.size !== baseSolid.length) return null;
|
|
367
|
+
const reorders = side => { const x = solid(side); return x.length === baseSolid.length && x.every(unit => known.has(unit)) && new Set(x).size === x.length && side.join('') !== units.join(''); };
|
|
368
|
+
const [first, second] = [reorders(ours), reorders(theirs)];
|
|
369
|
+
if (first === second) return null;
|
|
370
|
+
const [moved, changed] = first ? [ours, theirs] : [theirs, ours];
|
|
371
|
+
const edits = textEdits(units, changed);
|
|
372
|
+
if (!edits) return null;
|
|
373
|
+
const replace = new Map(), after = new Map();
|
|
374
|
+
for (const e of edits) {
|
|
375
|
+
const inner = []; for (let i = e.start; i < e.end; i++) if (!textSpace(units[i])) inner.push(i);
|
|
376
|
+
if (!inner.length) {
|
|
377
|
+
if (!e.text.trim()) continue;
|
|
378
|
+
let k = -1; for (let i = e.start - 1; i >= 0; i--) if (!textSpace(units[i])) { k = known.get(units[i]); break; }
|
|
379
|
+
const gap = e.start > 0 && textSpace(units[e.start - 1]);
|
|
380
|
+
after.set(k, [...(after.get(k) || []), {text: e.text, gap}]);
|
|
381
|
+
} else if (inner.length === 1) {
|
|
382
|
+
if (!e.text.trim()) replace.set(known.get(units[inner[0]]), units.slice(e.start, e.end).some(textSpace) ? ' ' : '');
|
|
383
|
+
else if (units.slice(e.start, e.end).some(textSpace)) return null;
|
|
384
|
+
else replace.set(known.get(units[inner[0]]), e.text);
|
|
385
|
+
} else return null;
|
|
386
|
+
}
|
|
387
|
+
const out = [], place = (k, from) => {
|
|
388
|
+
const rows = after.get(k) || [];
|
|
389
|
+
return rows.filter(row => row.gap === from).map(row => row.text);
|
|
390
|
+
};
|
|
391
|
+
out.push(...place(-1, false));
|
|
392
|
+
for (let i = 0; i < moved.length; i++) {
|
|
393
|
+
const unit = moved[i];
|
|
394
|
+
if (textSpace(unit)) { out.push(unit); continue; }
|
|
395
|
+
const k = known.get(unit);
|
|
396
|
+
if (replace.get(k) === '' || replace.get(k) === ' ') {
|
|
397
|
+
if (replace.get(k) === ' ') { if (i + 1 < moved.length && textSpace(moved[i + 1])) i++; else if (out.length && textSpace(out[out.length - 1])) out.pop(); }
|
|
398
|
+
} else out.push(replace.has(k) ? replace.get(k) : unit);
|
|
399
|
+
out.push(...place(k, false));
|
|
400
|
+
if (after.get(k)?.some(row => row.gap)) { if (i + 1 < moved.length && textSpace(moved[i + 1])) out.push(moved[++i]); out.push(...place(k, true)); }
|
|
401
|
+
}
|
|
402
|
+
return out.join('');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function textRefineGroup({before, av, bv}, options) {
|
|
406
|
+
const [first, second] = textDeviceOptions(options), ids = first < second ? [0, 1] : [1, 0];
|
|
407
|
+
const added = (side, base) => side.startsWith(base) ? side.slice(base.length) : null;
|
|
408
|
+
const oursAdded = added(av, before), theirsAdded = added(bv, before);
|
|
409
|
+
if (oursAdded !== null && theirsAdded !== null && textLines(oursAdded + theirsAdded).every(line => TEXT_ITEM.test(line))) {
|
|
410
|
+
const tail = value => value && !/[\r\n]$/.test(value) ? value + (before.includes('\r\n') ? '\r\n' : '\n') : value;
|
|
411
|
+
const parts = [tail(oursAdded), tail(theirsAdded)];
|
|
412
|
+
const last = tail(before) === before ? '' : (before.includes('\r\n') ? '\r\n' : '\n');
|
|
413
|
+
return before + last + parts[ids[0]] + parts[ids[1]];
|
|
414
|
+
}
|
|
415
|
+
const lines = textLines(before);
|
|
416
|
+
if (lines.length === 1) {
|
|
417
|
+
const tags = TEXT_TAGS.exec(before), x = TEXT_TAGS.exec(av), y = TEXT_TAGS.exec(bv);
|
|
418
|
+
if (tags && x && y && x[1] === tags[1] && y[1] === tags[1]) {
|
|
419
|
+
const list = row => row[2].split(',').map(item => item.trim()).filter(Boolean);
|
|
420
|
+
const base = list(tags), ours = list(x), theirs = list(y);
|
|
421
|
+
const kept = base.filter(item => ours.includes(item) && theirs.includes(item));
|
|
422
|
+
const added = [ours, theirs].map(list => list.filter(item => !base.includes(item)));
|
|
423
|
+
const joined = [...new Set([...kept, ...added[ids[0]], ...added[ids[1]]])];
|
|
424
|
+
return tags[1] + ': [' + joined.join(', ') + ']' + tags[3];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return textRefineLevel(before, av, bv, options, 'word');
|
|
428
|
+
}
|
|
275
429
|
export function mergeText(base, ours, theirs, options = {}) {
|
|
276
430
|
canonicalNote(ours); canonicalNote(theirs); if (base !== null) canonicalNote(base);
|
|
277
431
|
const unchanged = value => ({text: value, clean: !value.includes(TEXT_CONFLICT), conflicts: value.includes(TEXT_CONFLICT) ? [{kind: 'existing-conflict', advisory: true}] : []});
|
|
278
432
|
if (ours === theirs) return unchanged(ours);
|
|
279
433
|
if (base === ours) return unchanged(theirs);
|
|
280
434
|
if (base === theirs) return unchanged(ours);
|
|
281
|
-
const conflicts = []; let text = '';
|
|
435
|
+
const conflicts = []; let text = '', a, b;
|
|
282
436
|
const conflict = (before, a, b, kind) => {
|
|
283
437
|
const ab = inspectTextConflicts(a), bb = inspectTextConflicts(b);
|
|
284
438
|
const au = textUnits(a), bu = textUnits(b); let first = 0, last = 0;
|
|
@@ -316,38 +470,21 @@ export function mergeText(base, ours, theirs, options = {}) {
|
|
|
316
470
|
if (base === null) conflict(null, ours, theirs, 'no-ancestor');
|
|
317
471
|
else if (textDeclarations(oursUnits) !== declared || textDeclarations(theirsUnits) !== declared) conflict(base, ours, theirs, 'document-bindings');
|
|
318
472
|
else {
|
|
319
|
-
const
|
|
320
|
-
if (
|
|
473
|
+
const reordered = textPermuted(units, oursUnits, theirsUnits);
|
|
474
|
+
if (reordered !== null && textConserved(base, ours, theirs, reordered)) text += reordered;
|
|
475
|
+
else if (!(a = textEdits(units, oursUnits)) || !(b = textEdits(units, theirsUnits))) conflict(base, ours, theirs, 'ambiguous-alignment-or-budget');
|
|
321
476
|
else {
|
|
322
|
-
const changes = [...a.map(e => ({...e, side: 0})), ...b.map(e => ({...e, side: 1}))].sort((x, y) => x.start - y.start || x.end - y.end || x.side - y.side);
|
|
323
477
|
let pos = 0;
|
|
324
|
-
for (
|
|
325
|
-
const group = [changes[i++]], start = group[0].start; let end = group[0].end;
|
|
326
|
-
while (i < changes.length && (changes[i].start < end || group.some(e => {
|
|
327
|
-
const next = changes[i];
|
|
328
|
-
const afterBlock = (edit, insert) => insert.start === insert.end && insert.start === edit.end && edit.start < edit.end && units.slice(edit.start, edit.end).every(unit => textEnvelopeAt(unit));
|
|
329
|
-
if (afterBlock(e, next) || afterBlock(next, e)) return false;
|
|
330
|
-
if (e.start === e.end && next.start <= e.start && e.start <= next.end) return true;
|
|
331
|
-
if (next.start === next.end && e.start <= next.start && next.start <= e.end) return true;
|
|
332
|
-
return next.start >= e.end && units.slice(e.start, e.end).every(textSpace) &&
|
|
333
|
-
units.slice(e.end, next.start).every(textSpace) && units.slice(next.start, next.end).every(textSpace);
|
|
334
|
-
}))) {
|
|
335
|
-
group.push(changes[i]); end = Math.max(end, changes[i++].end);
|
|
336
|
-
}
|
|
478
|
+
for (const {start, end, before, av, bv, resolved} of textMoves([...textGroups(units, a, b)])) {
|
|
337
479
|
text += units.slice(pos, start).join('');
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
let value = '', at = start;
|
|
341
|
-
for (const e of group.filter(e => e.side === side)) { value += units.slice(at, e.start).join('') + e.text; at = e.end; }
|
|
342
|
-
return value + units.slice(at, end).join('');
|
|
343
|
-
};
|
|
344
|
-
const av = version(0), bv = version(1);
|
|
345
|
-
if (av === bv) text += av;
|
|
480
|
+
if (resolved !== undefined) text += resolved;
|
|
481
|
+
else if (av === bv) text += av;
|
|
346
482
|
else if (av === before) text += bv;
|
|
347
483
|
else if (bv === before) text += av;
|
|
348
484
|
else {
|
|
349
485
|
const tick = end === start + 1 ? textTick(before, av, bv) : null;
|
|
350
|
-
|
|
486
|
+
const fine = tick === null ? textRefine(before, av, bv, options) : null;
|
|
487
|
+
if (tick !== null) text += tick; else if (fine !== null) text += fine; else conflict(before, av, bv, 'text');
|
|
351
488
|
}
|
|
352
489
|
pos = end;
|
|
353
490
|
}
|
package/dist/kit/markdown-it.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
import {parseLayout, imageStyle} from './layout.mjs';
|
|
2
|
+
import {parseLayout, imageStyle, textStyle} from './layout.mjs';
|
|
3
3
|
import {typography, isPageBreakBlock} from './marks.mjs';
|
|
4
4
|
import {installMarkdownImages} from './assets.mjs';
|
|
5
5
|
import {tuneMarkdownEngine} from '../spec/md-engine.mjs';
|
|
@@ -26,11 +26,25 @@ export function rapierMarkdown(md) {
|
|
|
26
26
|
const layout = parseLayout(marker.content);
|
|
27
27
|
if (!layout) continue;
|
|
28
28
|
children.pop();
|
|
29
|
-
if (layout.align) owner.attrSet('data-md-align', layout.align);
|
|
29
|
+
if (layout.align && (layout.align !== 'justify' || owner.type === 'paragraph_open')) owner.attrSet('data-md-align', layout.align);
|
|
30
30
|
const visible = children.filter(token => token.type !== 'text' || token.content.trim());
|
|
31
|
-
|
|
32
|
-
visible[0].
|
|
33
|
-
|
|
31
|
+
const picture = visible.length === 1 && visible[0].type === 'image' ? visible[0]
|
|
32
|
+
: visible.length === 3 && visible[0].type === 'link_open' && visible[1].type === 'image' && visible[2].type === 'link_close' ? visible[1] : null;
|
|
33
|
+
if (picture) {
|
|
34
|
+
if (layout.width != null || layout.lines != null || layout.opacity != null) {
|
|
35
|
+
const style = imageStyle(layout);
|
|
36
|
+
if (style) picture.attrSet('style', style);
|
|
37
|
+
if (layout.width != null) picture.attrSet('data-md-image-width', String(layout.width));
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const key of ['first', 'indent', 'before', 'after', 'lines', 'hyphens', 'caps', 'hanging']) if (layout[key]) owner.attrSet('data-md-' + key, String(layout[key]));
|
|
42
|
+
const style = [textStyle(layout), layout.lines != null ? '--md-drop-lines:' + layout.lines : ''].filter(Boolean).join(';');
|
|
43
|
+
if (style) owner.attrSet('style', style);
|
|
44
|
+
const item = state.tokens[i - 2];
|
|
45
|
+
if (layout.marker && item?.type === 'list_item_open') {
|
|
46
|
+
item.attrSet('data-md-marker', String(layout.marker));
|
|
47
|
+
item.attrSet('style', (item.attrGet('style') ? item.attrGet('style') + ';' : '') + textStyle({marker: layout.marker}));
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
});
|
package/dist/kit/pdf-glyphs.mjs
CHANGED
|
@@ -337,8 +337,8 @@ export async function collectPdfGlyphs(main, {width, check, faces = [], prepareF
|
|
|
337
337
|
probe.style.setProperty('visibility', 'hidden', 'important'); probe.textContent = String(value); item.prepend(probe);
|
|
338
338
|
const parts = await measure(probe.firstChild, style, font); probe.remove();
|
|
339
339
|
if (!parts.length || parts.some(part => !part.font)) continue;
|
|
340
|
-
parts.at(-1).text += ' ';
|
|
341
|
-
markers.set(item, {text: String(value) + ' ', parts, ...Object.fromEntries(['x','y','width','height','page'].map(key => [key, parts[0][key]]))});
|
|
340
|
+
parts.at(-1).text += '. ';
|
|
341
|
+
markers.set(item, {text: String(value) + '. ', parts, ...Object.fromEntries(['x','y','width','height','page'].map(key => [key, parts[0][key]]))});
|
|
342
342
|
const id = 'm' + markers.size, old = item.getAttribute('data-rapier-pdf-marker'); item.setAttribute('data-rapier-pdf-marker', id);
|
|
343
343
|
restoreSteps.push(() => old == null ? item.removeAttribute('data-rapier-pdf-marker') : item.setAttribute('data-rapier-pdf-marker', old));
|
|
344
344
|
markerRules.push('[data-rapier-pdf-marker="' + id + '"]::before{-webkit-text-fill-color:transparent!important}');
|
package/dist/kit/pdf-vectors.mjs
CHANGED
|
@@ -92,7 +92,7 @@ function geometry(node) {
|
|
|
92
92
|
unsupported();
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
export async function collectPdfVectors(main, {width, height, count, capture, signal, current, check, maxBytes, sanitizeSvg, collectText}) {
|
|
95
|
+
export async function collectPdfVectors(main, {width, height, count, capture, signal, current, check, maxBytes, sanitizeSvg, collectText, pictures = false, underlay = false}) {
|
|
96
96
|
const document = main.ownerDocument, window = document.defaultView, origin = main.getBoundingClientRect();
|
|
97
97
|
const pages = Array.from({length: count}, () => []), hidden = [], temporary = [], textRoots = [], textRestores = [];
|
|
98
98
|
const colorCanvas = document.createElement('canvas'); colorCanvas.width = colorCanvas.height = 1;
|
|
@@ -117,20 +117,33 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
|
|
|
117
117
|
try {
|
|
118
118
|
for (const original of roots) {
|
|
119
119
|
check();
|
|
120
|
-
let root = original, mount = null;
|
|
120
|
+
let root = original, mount = null, turned = false, groupOpacity = 1, asImage = false, under = false;
|
|
121
121
|
const originalBox = original.getBoundingClientRect();
|
|
122
122
|
if (!(originalBox.width > 0 && originalBox.height > 0)) continue;
|
|
123
123
|
if (original.localName === 'img') {
|
|
124
124
|
if (typeof sanitizeSvg !== 'function') continue;
|
|
125
125
|
const address = original.getAttribute('src') || '', match = /^data:image\/svg\+xml(?:;charset=[^;,]+)?(;base64)?,([\s\S]*)$/i.exec(address);
|
|
126
|
-
if (!match) continue;
|
|
126
|
+
if (!match && !(pictures && /^data:image\//i.test(address))) continue;
|
|
127
|
+
asImage = !match;
|
|
128
|
+
}
|
|
129
|
+
if (original.localName === 'img' && !asImage) {
|
|
130
|
+
const address = original.getAttribute('src') || '', match = /^data:image\/svg\+xml(?:;charset=[^;,]+)?(;base64)?,([\s\S]*)$/i.exec(address);
|
|
127
131
|
let text;
|
|
128
132
|
try {text = match[1] ? new TextDecoder('utf-8', {fatal: true}).decode(Uint8Array.from(atob(match[2]), ch => ch.charCodeAt(0))) : decodeURIComponent(match[2]);} catch (_) {continue;}
|
|
129
133
|
const parsed = new window.DOMParser().parseFromString(sanitizeSvg(text), 'image/svg+xml');
|
|
130
134
|
if (parsed.querySelector('parsererror') || parsed.documentElement.namespaceURI !== SVG) continue;
|
|
135
|
+
const shown = window.getComputedStyle(original);
|
|
136
|
+
under = Number(shown.zIndex) < 0;
|
|
137
|
+
if (under && !underlay) continue;
|
|
138
|
+
turned = shown.transform !== 'none'; groupOpacity = Number(shown.opacity);
|
|
139
|
+
const unturnedWidth = turned ? Number.parseFloat(shown.width) : originalBox.width, unturnedHeight = turned ? Number.parseFloat(shown.height) : originalBox.height;
|
|
140
|
+
if (!(unturnedWidth > 0 && unturnedHeight > 0)) continue;
|
|
141
|
+
const [originX, originY] = shown.transformOrigin.split(' ').map(Number.parseFloat);
|
|
142
|
+
if (turned && !(Math.abs(originX - unturnedWidth / 2) < .5 && Math.abs(originY - unturnedHeight / 2) < .5)) continue;
|
|
143
|
+
const left = originalBox.left + originalBox.width / 2 - unturnedWidth / 2, top = originalBox.top + originalBox.height / 2 - unturnedHeight / 2;
|
|
131
144
|
root = document.importNode(parsed.documentElement, true);
|
|
132
|
-
root.style.setProperty('width',
|
|
133
|
-
mount = document.createElement('div'); mount.style.cssText = `position:fixed;left:${
|
|
145
|
+
root.style.setProperty('width', unturnedWidth + 'px', 'important'); root.style.setProperty('height', unturnedHeight + 'px', 'important');
|
|
146
|
+
mount = document.createElement('div'); mount.style.cssText = `position:fixed;left:${left}px;top:${top}px;width:${unturnedWidth}px;height:${unturnedHeight}px;pointer-events:none` + (turned ? `;transform:${shown.transform};transform-origin:50% 50%` : '');
|
|
134
147
|
mount.attachShadow({mode: 'open'}).append(root); document.body.append(mount); temporary.push(mount); await document.fonts.ready;
|
|
135
148
|
const glyphs = await collectText?.(root); if (glyphs) textRestores.push(glyphs.restore);
|
|
136
149
|
textRoots.push({root, original, text: glyphs});
|
|
@@ -181,7 +194,7 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
|
|
|
181
194
|
} catch (_) { sequence.push({raster: node}); return; }
|
|
182
195
|
if (!path) return;
|
|
183
196
|
const matrix = node.getScreenCTM(); if (!matrix || ![matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f].every(Number.isFinite)) {sequence.push({raster: node}); return;}
|
|
184
|
-
const opacity = Number(style.opacity), fillAlpha = fill ? fill[3] * Number(style.fillOpacity) * opacity : 1, strokeAlpha = stroke ? stroke[3] * Number(style.strokeOpacity) * opacity : 1;
|
|
197
|
+
const opacity = Number(style.opacity) * groupOpacity, fillAlpha = fill ? fill[3] * Number(style.fillOpacity) * opacity : 1, strokeAlpha = stroke ? stroke[3] * Number(style.strokeOpacity) * opacity : 1;
|
|
185
198
|
let commands = nums([matrix.a, matrix.b, matrix.c, matrix.d, matrix.e - origin.left, matrix.f - origin.top]) + ' cm\n';
|
|
186
199
|
if (fill) commands += nums(fill.slice(0, 3)) + ' rg\n';
|
|
187
200
|
if (stroke) {
|
|
@@ -193,12 +206,19 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
|
|
|
193
206
|
commands += path + (fill && stroke ? style.fillRule === 'evenodd' ? 'B*' : 'B' : fill ? style.fillRule === 'evenodd' ? 'f*' : 'f' : stroke ? 'S' : 'n') + '\n';
|
|
194
207
|
sequence.push({commands, fillAlpha, strokeAlpha}); geometries++;
|
|
195
208
|
};
|
|
196
|
-
await walk(root);
|
|
197
|
-
if (
|
|
209
|
+
if (asImage) sequence.push({raster: root}); else await walk(root);
|
|
210
|
+
if ((turned || groupOpacity !== 1) && (sequence.some(piece => piece.raster) || textRoots.find(entry => entry.root === root)?.text?.nodes.size)) {
|
|
211
|
+
const entry = textRoots.find(row => row.root === root);
|
|
212
|
+
if (entry?.text) textRestores.pop()?.();
|
|
213
|
+
if (entry) textRoots.splice(textRoots.indexOf(entry), 1);
|
|
214
|
+
mount?.remove(); temporary.splice(temporary.indexOf(mount), 1);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!geometries && !asImage && !textRoots.find(entry => entry.root === root)?.text?.nodes.size) continue;
|
|
198
218
|
const painted = [];
|
|
199
219
|
for (const piece of sequence) {const held = piece.raster ? await raster(piece.raster) : piece; if (held) painted.push(held);}
|
|
200
220
|
const first = Math.max(0, Math.floor((box.left - origin.left + .01) / width)), last = Math.min(count - 1, Math.floor((box.right - origin.left - .01) / width));
|
|
201
|
-
for (let page = first; page <= last; page++) pages[page].push({sequence: painted, x: -page * width, clip: {x: box.left - origin.left, y: box.top - origin.top, width: box.width, height: box.height}});
|
|
221
|
+
for (let page = first; page <= last; page++) pages[page].push({sequence: painted, x: -page * width, clip: {x: box.left - origin.left, y: box.top - origin.top, width: box.width, height: box.height}, ...(under && {under: true})});
|
|
202
222
|
hidden.push({node: original, style: original.getAttribute('style')}); original.style.setProperty('visibility', 'hidden', 'important');
|
|
203
223
|
}
|
|
204
224
|
return {pages, textRoots, restore};
|
|
@@ -1243,7 +1243,7 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1243
1243
|
? (element.style.backgroundColor || RAPIER_PORTABLE_HIGHLIGHT_HEX[String(element.getAttribute('data-rapier-highlight') || '').toLowerCase()] || RAPIER_PORTABLE_HIGHLIGHT_HEX.yellow)
|
|
1244
1244
|
: null;
|
|
1245
1245
|
|
|
1246
|
-
const isTableCaption = element.tagName === 'P' && element.classList.contains('rapier-table-caption');
|
|
1246
|
+
const isTableCaption = element.tagName === 'P' && (element.classList.contains('rapier-table-caption') || element.classList.contains('rapier-figure-caption'));
|
|
1247
1247
|
|
|
1248
1248
|
const isPageBreak = element.hasAttribute('data-md-break');
|
|
1249
1249
|
for (const attribute of Array.from(element.attributes)) { yield;
|
|
@@ -1271,6 +1271,13 @@ function* _rapierProjectPortableNodesSteps(semanticRoot, options) {
|
|
|
1271
1271
|
if (paragraphLayout.before != null) element.style.marginTop = paragraphLayout.before + 'pt';
|
|
1272
1272
|
if (paragraphLayout.after != null) element.style.marginBottom = paragraphLayout.after + 'pt';
|
|
1273
1273
|
}
|
|
1274
|
+
if (!keepLayoutData && paragraphLayout && /^(?:P|H[1-6])$/.test(element.tagName) && !element.closest('table,pre') && (paragraphLayout.first != null || paragraphLayout.indent != null)) {
|
|
1275
|
+
const indents = {...(paragraphLayout.first != null && {first: paragraphLayout.first}), ...(paragraphLayout.indent != null && {indent: paragraphLayout.indent})};
|
|
1276
|
+
for (const declaration of globalThis.RapierMarkdownLayout.textStyle(indents, {portable: true}).split(';').filter(Boolean)) {
|
|
1277
|
+
const at = declaration.indexOf(':');
|
|
1278
|
+
element.style.setProperty(declaration.slice(0, at), declaration.slice(at + 1));
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1274
1281
|
if (!keepLayoutData && element.tagName === 'P' && !element.closest('table,pre')) {
|
|
1275
1282
|
if (paragraphLayout?.hyphens === 'auto') { element.style.hyphens = 'auto'; element.style.textWrap = 'pretty'; }
|
|
1276
1283
|
if (paragraphLayout?.hanging === 'on') element.style.setProperty('hanging-punctuation', 'first allow-end');
|