rapier-markdown-kit 1.1.102 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -2
- package/README.md +245 -54
- package/dist/agent/vendor/hyphen-en-us/LICENSE +14 -0
- package/dist/agent/vendor/hyphen-en-us/SOURCE.json +33 -0
- package/dist/agent/vendor/hyphen-en-us/hyph-en-us.hyp.txt +14 -0
- package/dist/agent/vendor/hyphen-en-us/hyph-en-us.pat.txt +4938 -0
- package/dist/agent/vendor/hyphen-en-us/patterns.js +25 -0
- package/dist/agent/vendor/pretext/LICENSE +208 -0
- package/dist/agent/vendor/pretext/NOTICE +26 -13
- package/dist/agent/vendor/pretext/SOURCE.json +213 -45
- package/dist/agent/vendor/pretext/analysis.js +675 -1224
- package/dist/agent/vendor/pretext/entry-geometry.js +183 -0
- package/dist/agent/vendor/pretext/gecko-line-breaks.js +688 -0
- package/dist/agent/vendor/pretext/generated/engine-break-data.js +57 -0
- package/dist/agent/vendor/pretext/generated/webkit-generic-families.js +80 -0
- package/dist/agent/vendor/pretext/graphemes.js +98 -0
- package/dist/agent/vendor/pretext/han-kerning.js +253 -0
- package/dist/agent/vendor/pretext/layout.js +352 -290
- package/dist/agent/vendor/pretext/line-break.js +1154 -588
- package/dist/agent/vendor/pretext/line-breaks.js +892 -0
- package/dist/agent/vendor/pretext/line-text.js +81 -39
- package/dist/agent/vendor/pretext/measurement.js +1105 -176
- package/dist/agent/vendor/pretext/prepare.js +807 -0
- package/dist/agent/vendor/pretext/rich-inline.js +1242 -273
- package/dist/agent/will.mjs +2 -0
- package/dist/images/gif.mjs +47 -0
- package/dist/images/playback.mjs +101 -0
- package/dist/kit/assets.mjs +5 -1
- package/dist/kit/conformance/03-wrap-around-silhouette.md +1 -1
- package/dist/kit/conformance/04-wrap-box.md +1 -1
- package/dist/kit/conformance/05-behind.md +1 -1
- package/dist/kit/conformance/06-front.md +1 -1
- package/dist/kit/conformance/07-rotate-raster.md +1 -1
- package/dist/kit/conformance/08-drawing-rotation.md +1 -1
- package/dist/kit/conformance/09-drawing-ring-interior.expected.json +18 -18
- package/dist/kit/conformance/09-drawing-ring-interior.md +1 -1
- package/dist/kit/conformance/10-both-sides.md +1 -1
- package/dist/kit/conformance/11-neighbour-skips-image.md +1 -1
- package/dist/kit/conformance/{12-heading-barrier.expected.json → 12-rule-barrier.expected.json} +1 -1
- package/dist/kit/conformance/{12-heading-barrier.md → 12-rule-barrier.md} +2 -2
- package/dist/kit/conformance/README.md +19 -28
- package/dist/kit/conformance/run.mjs +13 -11
- package/dist/kit/export-fonts.mjs +26 -11
- package/dist/kit/index.mjs +1 -0
- package/dist/kit/ledger/carried.mjs +17 -10
- package/dist/kit/ledger/data.mjs +73 -13
- package/dist/kit/ledger/format.mjs +176 -54
- package/dist/kit/ledger/hash.mjs +91 -18
- package/dist/kit/ledger/history.mjs +156 -116
- package/dist/kit/ledger/inverse-source.mjs +158 -19
- package/dist/kit/ledger/journal-records.mjs +292 -67
- package/dist/kit/ledger/merge.mjs +4 -3
- package/dist/kit/model.mjs +1 -1
- package/dist/kit/pdf-glyphs.mjs +4 -4
- package/dist/kit/pdf-vectors.mjs +4 -1
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-ink.mjs +1 -1
- package/dist/kit/render-markdown.mjs +400 -148
- package/dist/kit/render-print.mjs +131 -55
- package/dist/kit/render-sanitize.mjs +30 -7
- package/dist/kit/render-work.mjs +99 -20
- package/dist/kit/render.mjs +133 -140
- package/dist/kit/typography.mjs +2 -0
- package/dist/layout/hyphenation.mjs +127 -0
- package/dist/layout/line-plan.mjs +88 -96
- package/dist/layout/model.mjs +71 -88
- package/dist/layout/paragraph-fit.mjs +191 -0
- package/dist/layout/preparation.mjs +373 -0
- package/dist/layout/prepared-paragraph.mjs +223 -0
- package/dist/layout/typography.mjs +345 -111
- package/dist/packages/rapier-diff/index.mjs +359 -224
- package/dist/spec/document-fonts.mjs +14 -5
- package/dist/spec/document-settings.mjs +11 -164
- package/dist/spec/html-blocks.mjs +618 -79
- package/dist/spec/md-assets.mjs +178 -36
- package/dist/spec/md-layout.mjs +13 -2
- package/dist/spec/md-marks.mjs +220 -127
- package/dist/spec/md-source.mjs +157 -0
- package/package.json +2 -1
- package/style.css +15 -50
- package/dist/agent/vendor/pretext/bidi.js +0 -151
- package/dist/agent/vendor/pretext/generated/bidi-data.js +0 -3831
package/dist/spec/md-assets.mjs
CHANGED
|
@@ -22,7 +22,7 @@ export function installMarkdownImages(md) {
|
|
|
22
22
|
const normalize = md.normalizeLink;
|
|
23
23
|
// An exact base64 data URL contains no URI escapes or host name to normalize.
|
|
24
24
|
// Preserve its spelling and avoid allocating one URI-normalizer item per raster byte.
|
|
25
|
-
md.normalizeLink = url => /^data:image\/(?:jxl|png|jpeg|webp|svg\+xml);base64,[A-Za-z0-9+/]+={0,2}$/i.test(url) ? url : normalize(url);
|
|
25
|
+
md.normalizeLink = url => /^data:image\/(?:jxl|png|jpeg|webp|gif|svg\+xml);base64,[A-Za-z0-9+/]+={0,2}$/i.test(url) ? url : normalize(url);
|
|
26
26
|
const reference = md.block.ruler.__rules__.find(row => row.name === 'reference').fn;
|
|
27
27
|
md.block.ruler.at('reference', (state, start, end, silent) => {
|
|
28
28
|
if (silent) return reference(state, start, end, silent);
|
|
@@ -73,11 +73,133 @@ function parserFor(factory = configured || globalThis.markdownit) {
|
|
|
73
73
|
export {parserFor as markdownParser};
|
|
74
74
|
export const markdownBodyOffset = source => splitSource ? splitSource(source).bodyOffset : 0;
|
|
75
75
|
|
|
76
|
+
// markdown-it's block state builds five tables with one entry per line of its whole input, and its rules index
|
|
77
|
+
// them by line. A long body gets the parser's one block pass with tables that hold a run of lines instead: a read
|
|
78
|
+
// past the lines they hold fills them further from the parser's own line scan, and they are emptied between
|
|
79
|
+
// top-level blocks. No line is parsed twice, so every token and `env` are the ones a whole-input state yields.
|
|
80
|
+
const BLOCK_WINDOW = 1024, TABLE_STEP = 128, WINDOW_END = Symbol('block window end');
|
|
81
|
+
const LINE_TABLES = ['bMarks', 'eMarks', 'tShift', 'sCount', 'bsCount'];
|
|
82
|
+
const lineIndex = key => typeof key === 'string' && /^(?:0|[1-9]\d*)$/.test(key);
|
|
83
|
+
// An array reaches its prototype only for an index it does not hold. One prototype serves every pass, so the
|
|
84
|
+
// parser's table reads see one more array shape, not one per pass.
|
|
85
|
+
let unheldLine = null;
|
|
86
|
+
const unheldLines = new Proxy(Array.prototype, {
|
|
87
|
+
get: (target, key, table) => unheldLine && lineIndex(key) ? unheldLine(table, Number(key)) : Reflect.get(target, key, table),
|
|
88
|
+
set: (target, key, value, table) => unheldLine && lineIndex(key) ? unheldLine(table, Number(key), true, value) : Reflect.set(target, key, value, table),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// The parser's block pass over source.slice(bodyOffset). `visit` receives top-level blocks as they finish:
|
|
92
|
+
// `tokens` with line numbers counted from `firstLine`, and lineStart(line), the source offset where one of those
|
|
93
|
+
// lines begins (undefined when the source ends before it). `env` is the whole pass's. The tables hold at most
|
|
94
|
+
// the lines between two visits: BLOCK_WINDOW of them, or as many as one block's rules read.
|
|
95
|
+
export function eachBlockWindow(source, bodyOffset, parser, env, visit, keepNul = false) {
|
|
96
|
+
const lineEnds = source.slice(bodyOffset).replace(/\r\n?/g, '\n'), body = keepNul ? lineEnds : lineEnds.replace(/\0/g, '\ufffd');
|
|
97
|
+
// The body is the source with every CRLF one unit shorter: count the pairs before a body offset, from the last
|
|
98
|
+
// offset asked.
|
|
99
|
+
let pair = source.indexOf('\r\n', bodyOffset), shift = bodyOffset;
|
|
100
|
+
const sourceOffset = at => {
|
|
101
|
+
while (pair >= 0 && at + shift > pair) { shift++; pair = source.indexOf('\r\n', pair + 2); }
|
|
102
|
+
for (let last; shift > bodyOffset && at + shift - 1 <= (last = source.lastIndexOf('\r\n', (pair < 0 ? source.length : pair) - 1)); pair = last) shift--;
|
|
103
|
+
return at + shift;
|
|
104
|
+
};
|
|
105
|
+
const tokens = [];
|
|
106
|
+
let origin = 0, first = 0, state;
|
|
107
|
+
// A line begins after the newline that ends the line before it. No rule writes eMarks.
|
|
108
|
+
const report = () => visit({tokens, firstLine: first, lineStart: line => {
|
|
109
|
+
if (!line) return sourceOffset(origin);
|
|
110
|
+
const end = Object.hasOwn(state.eMarks, line - 1) ? state.eMarks[line - 1] : body.length;
|
|
111
|
+
return end < body.length ? sourceOffset(end + 1) : undefined;
|
|
112
|
+
}});
|
|
113
|
+
let lines = 0;
|
|
114
|
+
for (let at = 0; at < body.length; at++) if (body.charCodeAt(at) === 10) lines++;
|
|
115
|
+
if (lines <= BLOCK_WINDOW) {
|
|
116
|
+
state = new parser.block.State(body, parser, env, tokens);
|
|
117
|
+
parser.block.tokenize(state, state.line, state.lineMax);
|
|
118
|
+
return report();
|
|
119
|
+
}
|
|
120
|
+
// The parser's own count of a last line that no newline ends: it does not count one of only spaces.
|
|
121
|
+
state = new parser.block.State(body.slice(body.lastIndexOf('\n') + 1), parser, env, tokens);
|
|
122
|
+
lines += state.lineMax;
|
|
123
|
+
state.src = body;
|
|
124
|
+
const tables = new WeakMap();
|
|
125
|
+
for (const name of LINE_TABLES) { state[name].length = 0; tables.set(Object.setPrototypeOf(state[name], unheldLines), name); }
|
|
126
|
+
let frontier = 0, ended = false, betweenBlocks = false;
|
|
127
|
+
// A rule may be reading a table when it grows, so the five arrays are replaced whole; an array a rule still
|
|
128
|
+
// holds is left empty, and `answer` reads and writes the state's current one in its place.
|
|
129
|
+
const fill = line => {
|
|
130
|
+
while (!ended && line >= state.bMarks.length) {
|
|
131
|
+
const held = state.bMarks.length;
|
|
132
|
+
let until = frontier;
|
|
133
|
+
for (let more = held ? Math.max(TABLE_STEP, held - BLOCK_WINDOW) : BLOCK_WINDOW; more && until < body.length; more--) {
|
|
134
|
+
const end = body.indexOf('\n', until);
|
|
135
|
+
until = end < 0 ? body.length : end + 1;
|
|
136
|
+
}
|
|
137
|
+
const scanned = new parser.block.State(body.slice(frontier, until), parser, env, []);
|
|
138
|
+
ended = until === body.length;
|
|
139
|
+
// The scan's last entry stands for the end of its input, which is the body's end only for the last scan.
|
|
140
|
+
const added = ended ? scanned.lineMax + 1 : scanned.lineMax;
|
|
141
|
+
for (const name of LINE_TABLES) {
|
|
142
|
+
const old = state[name], next = scanned[name], offset = name === 'bMarks' || name === 'eMarks' ? frontier : 0, table = [];
|
|
143
|
+
for (let at = 0; at < held; at++) table.push(old[at]);
|
|
144
|
+
for (let at = 0; at < added; at++) table.push(next[at] + offset);
|
|
145
|
+
old.length = 0;
|
|
146
|
+
tables.set(table, name);
|
|
147
|
+
state[name] = Object.setPrototypeOf(table, unheldLines);
|
|
148
|
+
}
|
|
149
|
+
frontier = until;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const answer = (table, line, write, value) => {
|
|
153
|
+
const name = tables.get(table);
|
|
154
|
+
if (line >= state[name].length) {
|
|
155
|
+
if (betweenBlocks) throw WINDOW_END;
|
|
156
|
+
fill(line);
|
|
157
|
+
}
|
|
158
|
+
const current = state[name], held = Object.hasOwn(current, line);
|
|
159
|
+
if (!write) return held ? current[line] : undefined;
|
|
160
|
+
if (held) current[line] = value;
|
|
161
|
+
else Object.defineProperty(current, line, {value, writable: true, enumerable: true, configurable: true});
|
|
162
|
+
return true;
|
|
163
|
+
};
|
|
164
|
+
// The top-level loop calls this between two blocks with the line it stands on. There every token is a finished
|
|
165
|
+
// block and no rule is running, so the pass can leave the tokenizer and enter it again at that line.
|
|
166
|
+
const skipEmptyLines = state.skipEmptyLines;
|
|
167
|
+
state.skipEmptyLines = function (line) {
|
|
168
|
+
if (this.level || line !== this.line || !line) return skipEmptyLines.call(this, line);
|
|
169
|
+
if (line >= BLOCK_WINDOW) throw WINDOW_END;
|
|
170
|
+
betweenBlocks = true;
|
|
171
|
+
line = skipEmptyLines.call(this, line);
|
|
172
|
+
betweenBlocks = false;
|
|
173
|
+
return line;
|
|
174
|
+
};
|
|
175
|
+
for (;;) {
|
|
176
|
+
// Between top-level blocks the parser's loop only steps over empty lines, so a run of them is never tabled.
|
|
177
|
+
for (let at = origin; at < body.length; at++) {
|
|
178
|
+
const code = body.charCodeAt(at);
|
|
179
|
+
if (code === 10) { origin = at + 1; first++; }
|
|
180
|
+
else if (!parser.utils.isSpace(code)) break;
|
|
181
|
+
}
|
|
182
|
+
frontier = origin; ended = false; betweenBlocks = false;
|
|
183
|
+
state.line = 0; state.lineMax = lines - first;
|
|
184
|
+
const outer = unheldLine;
|
|
185
|
+
unheldLine = answer;
|
|
186
|
+
let finished = true;
|
|
187
|
+
try { parser.block.tokenize(state, 0, state.lineMax); }
|
|
188
|
+
catch (error) { if (error !== WINDOW_END) throw error; finished = false; }
|
|
189
|
+
finally { unheldLine = outer; }
|
|
190
|
+
report();
|
|
191
|
+
if (finished) return;
|
|
192
|
+
tokens.length = 0;
|
|
193
|
+
origin = state.eMarks[state.line - 1] + 1; first += state.line;
|
|
194
|
+
for (const name of LINE_TABLES) state[name].length = 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
76
198
|
export function dataImage(url) {
|
|
77
199
|
if (typeof url !== 'string' || url.length > IMAGE_LIMITS.sourceChars) return null;
|
|
78
200
|
const prepared = preparedDefinitions.find(row => row.image.url === url)?.image;
|
|
79
201
|
if (prepared) return {codec: prepared.codec, byteLength: prepared.byteLength, payloadStart: url.indexOf(',') + 1};
|
|
80
|
-
const match = /^data:(image\/(?:jxl|png|jpeg|webp|svg\+xml));base64,([A-Za-z0-9+/]+={0,2})$/i.exec(url);
|
|
202
|
+
const match = /^data:(image\/(?:jxl|png|jpeg|webp|gif|svg\+xml));base64,([A-Za-z0-9+/]+={0,2})$/i.exec(url);
|
|
81
203
|
if (!match || match[2].length % 4) return null;
|
|
82
204
|
const padding = match[2].endsWith('==') ? 2 : match[2].endsWith('=') ? 1 : 0;
|
|
83
205
|
const byteLength = match[2].length / 4 * 3 - padding;
|
|
@@ -94,36 +216,33 @@ export function parseAssets(source, factory = configured || globalThis.markdowni
|
|
|
94
216
|
if (prior) return prior.index;
|
|
95
217
|
const definition = !bodyOffset && preparedDefinitions.find(row => row.source === source && row.factory === factory);
|
|
96
218
|
if (definition) return definition.index;
|
|
97
|
-
const parser = parserFor(factory), env = {}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
blocks.push(row);
|
|
125
|
-
if (first) assets.set(id, row);
|
|
126
|
-
}
|
|
219
|
+
const parser = parserFor(factory), env = {};
|
|
220
|
+
const assets = new Map(), blocks = [], seen = new Set(), duplicateLabels = new Set();
|
|
221
|
+
eachBlockWindow(source, bodyOffset, parser, env, ({tokens, lineStart}) => {
|
|
222
|
+
for (const token of tokens) {
|
|
223
|
+
if (token.type !== 'reference_definition' || !token.map) continue;
|
|
224
|
+
const id = token.meta.label, definition = token.meta.mdImageDefinition, first = !seen.has(id);
|
|
225
|
+
if (!first) duplicateLabels.add(id);
|
|
226
|
+
seen.add(id);
|
|
227
|
+
const info = dataImage(definition?.href);
|
|
228
|
+
if (!info) continue;
|
|
229
|
+
const start = lineStart(token.map[0]), stop = lineStart(token.map[1]) ?? source.length;
|
|
230
|
+
let end = stop;
|
|
231
|
+
if (source[end - 1] === '\n') end--;
|
|
232
|
+
if (source[end - 1] === '\r') end--;
|
|
233
|
+
// Repeated definitions are source, not additional image owners.
|
|
234
|
+
const raw = source.slice(start, end), found = raw.indexOf(definition.href);
|
|
235
|
+
const repeated = found >= 0 ? raw.indexOf(definition.href, found + 1) : -1;
|
|
236
|
+
const labelContainsUrl = definition.href.length <= id.length && id.includes(parser.utils.normalizeReference(definition.href));
|
|
237
|
+
const urlStart = found >= 0 && repeated < 0 && !labelContainsUrl ? start + found : null;
|
|
238
|
+
const row = {id, label: id, source: raw, url: definition.href, title: definition.title || '', start, end,
|
|
239
|
+
...info, urlStart, urlEnd: urlStart == null ? null : urlStart + definition.href.length,
|
|
240
|
+
payloadStart: urlStart == null ? null : urlStart + info.payloadStart, payloadEnd: urlStart == null ? null : urlStart + definition.href.length, status: 'unverified', active: first, topLevel: token.level === 0};
|
|
241
|
+
blocks.push(row);
|
|
242
|
+
if (first) assets.set(id, row);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
const references = env.references || Object.create(null);
|
|
127
246
|
if (assets.size > IMAGE_LIMITS.assets) for (const asset of assets.values()) asset.status = 'asset_count_limit';
|
|
128
247
|
let appendixStart = source.length;
|
|
129
248
|
for (let index = blocks.length - 1; index >= 0; index--) {
|
|
@@ -332,7 +451,7 @@ function occurrenceChange(image, splices) {
|
|
|
332
451
|
|
|
333
452
|
// A permissive gate keeps ordinary typing out of full-document image parsing.
|
|
334
453
|
// Recognition and retirement proof still belong to Markdown-it below.
|
|
335
|
-
export function mayRetireImageDefinitions(source, splices, factory) {
|
|
454
|
+
export function mayRetireImageDefinitions(source, splices, factory, hint) {
|
|
336
455
|
const store = typeof source === 'string' ? null : source;
|
|
337
456
|
// A definition's closing bracket is immediately followed by its colon (parseAssets uses the same necessary condition).
|
|
338
457
|
if (store ? !store.contains(']:') || !store.contains('data:image/', true)
|
|
@@ -349,13 +468,36 @@ export function mayRetireImageDefinitions(source, splices, factory) {
|
|
|
349
468
|
if (!row.removed && !row.inserted) return false;
|
|
350
469
|
const start = original(row.pos, false, index), end = original(row.pos + row.removed.length, true, index);
|
|
351
470
|
if (store) {
|
|
471
|
+
// No image use exists without this marker. A definition appendix alone must
|
|
472
|
+
// not make a prose keystroke join the document to prove that.
|
|
473
|
+
if (!store.contains('![')) return false;
|
|
352
474
|
const from = Math.max(0, start - 8192);
|
|
353
475
|
const local = store.readSlice(from, Math.min(store.length, end + 4));
|
|
354
476
|
const image = local.lastIndexOf('![', end - from);
|
|
355
477
|
const separator = Math.max(local.lastIndexOf('\n\n', start - from),
|
|
356
478
|
local.lastIndexOf('\r\n\r\n', start - from), local.lastIndexOf('\r\r', start - from));
|
|
357
479
|
if (image >= 0) return image + from >= start || separator < image;
|
|
358
|
-
|
|
480
|
+
const exclude = left => {
|
|
481
|
+
if (hint && splices.length === 1) hint.excluded = {start: left, end};
|
|
482
|
+
return false;
|
|
483
|
+
};
|
|
484
|
+
if (separator >= 0 || from === 0) return exclude(separator >= 0 ? from + separator + 4 : 0);
|
|
485
|
+
// Still inside a longer paragraph. Walk back in slices to the blank line.
|
|
486
|
+
// Finding an image there is the same "possible" answer as the 8KiB window;
|
|
487
|
+
// finding the break, or none, does not read the joined document.
|
|
488
|
+
let cursor = from;
|
|
489
|
+
while (cursor > 0) {
|
|
490
|
+
const sliceFrom = Math.max(0, cursor - 8192);
|
|
491
|
+
const chunk = store.readSlice(sliceFrom, Math.min(store.length, cursor + 3));
|
|
492
|
+
const limit = cursor - sliceFrom;
|
|
493
|
+
const sep = Math.max(chunk.lastIndexOf('\n\n', limit), chunk.lastIndexOf('\r\n\r\n', limit),
|
|
494
|
+
chunk.lastIndexOf('\r\r', limit));
|
|
495
|
+
const imageAt = chunk.lastIndexOf('![', limit);
|
|
496
|
+
if (imageAt >= 0 && (sep < 0 || imageAt > sep)) return true;
|
|
497
|
+
if (sep >= 0 || sliceFrom === 0) return exclude(sep >= 0 ? sliceFrom + sep + 4 : 0);
|
|
498
|
+
cursor = sliceFrom;
|
|
499
|
+
}
|
|
500
|
+
return false;
|
|
359
501
|
}
|
|
360
502
|
const image = source.lastIndexOf('![', end);
|
|
361
503
|
return image >= 0 && (image >= start || Math.max(source.lastIndexOf('\n\n', start),
|
|
@@ -443,10 +585,10 @@ export function serializeAsset(asset) {
|
|
|
443
585
|
}
|
|
444
586
|
// Pure: re-derived at commit against the moved document; appends only at the text's end.
|
|
445
587
|
// A replay supplies the retained definition's line ending instead of choosing one from later prose.
|
|
446
|
-
export function appendAssetText(source, asset, lineEnding) {
|
|
588
|
+
export function appendAssetText(source, asset, lineEnding, {preserveLabel = false} = {}) {
|
|
447
589
|
if (typeof source !== 'string' || !asset || normalizeLabel(asset.label) !== asset.id) return fail('image_source_invalid');
|
|
448
590
|
const parsed = documentAssets(source), title = assetTitle(asset.title);
|
|
449
|
-
const existing = [...parsed.assets.values()].find(row => row.url === asset.url && row.title === title);
|
|
591
|
+
const existing = !preserveLabel && [...parsed.assets.values()].find(row => row.url === asset.url && row.title === title);
|
|
450
592
|
if (existing) return {source, id: existing.id, reference: existing.label, added: false, suffix: ''};
|
|
451
593
|
const previous = parsed.references[asset.id];
|
|
452
594
|
const block = serializeAsset(asset);
|
package/dist/spec/md-layout.mjs
CHANGED
|
@@ -103,14 +103,16 @@ export const parseLayoutAttribute = value => parseLayout(decodeLayoutAttribute(v
|
|
|
103
103
|
export function textStyle(value, {portable = false, pointSize, indentOffset = 0} = {}) {
|
|
104
104
|
if (!validLayout(value)) return '';
|
|
105
105
|
const property = {first: 'text-indent', indent: 'margin-inline-start'};
|
|
106
|
-
|
|
106
|
+
const css = (portable ? ['first', 'indent'] : ['first', 'indent', 'marker'])
|
|
107
107
|
.filter(key => has(value, key) || key === 'indent' && indentOffset).map(key => {
|
|
108
108
|
const level = (value[key] || 0) + (key === 'indent' ? indentOffset : 0);
|
|
109
109
|
const measure = decimal(Number((level * 2).toFixed(6)));
|
|
110
110
|
return (portable ? property[key] : '--md-' + key) + ':' + (portable ?
|
|
111
111
|
pointSize ? decimal(Math.round(level * pointSize * 40) / 20) + 'pt' : measure + 'em' :
|
|
112
112
|
'calc(' + measure + ' * var(--md-text-body))');
|
|
113
|
-
})
|
|
113
|
+
});
|
|
114
|
+
if (portable && value.hyphens === 'auto') css.push('hyphens:auto');
|
|
115
|
+
return css.join(';');
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
// Raw HTML tables carry ordinary paragraph CSS. Read the browser's parsed declarations on the same
|
|
@@ -127,6 +129,7 @@ export function textLayout(style, {listMarker = false, pointSize, offset = 0} =
|
|
|
127
129
|
// rather than each intermediate declaration, is the measurement the writer must keep.
|
|
128
130
|
if (Number.isFinite(level) && level) value[key] = level;
|
|
129
131
|
}
|
|
132
|
+
if (!listMarker && style?.getPropertyValue('hyphens').trim() === 'auto') value.hyphens = 'auto';
|
|
130
133
|
return value;
|
|
131
134
|
}
|
|
132
135
|
|
|
@@ -149,6 +152,14 @@ export function imageStyle(value) {
|
|
|
149
152
|
|
|
150
153
|
export const linesHeightCss = lines => lines === 1 ? '1cap' : 'calc(' + (lines - 1) + ' * var(--md-line, 1.6em) + 1cap)';
|
|
151
154
|
|
|
155
|
+
// An HTML object has an outer clip box and an independent browser viewport.
|
|
156
|
+
// SCALE keeps that authored viewport; REFLOW keeps measured content height.
|
|
157
|
+
export function objectHeight(width, naturalHeight, attrs, line) {
|
|
158
|
+
if (attrs.height && attrs.height !== 'auto') return Math.min(4096, Number(attrs.height) * line);
|
|
159
|
+
const [w, h] = (attrs.viewport || '640x360').split('x').map(Number);
|
|
160
|
+
return attrs.resize === 'scale' ? Math.min(4096, width * h / w) : naturalHeight;
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
// Controls remain real DOM nodes while their surrounding text flows. A checkbox, link or
|
|
153
164
|
// disclosure is not a reason to exclude its entire block from picture placement.
|
|
154
165
|
export function wrapTextBlock(element) {
|