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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {paragraphHyphens} from './hyphenation.mjs';
|
|
3
|
+
import {prepareRichInlineSegments} from '../agent/vendor/pretext/rich-inline.js';
|
|
4
|
+
import {getEngineProfile, getPreparationLanguage, getFontMeasurement, parseFontSize,
|
|
5
|
+
getTextWidth, getEmojiCorrection, textMayContainEmoji, measureWithLetterSpacing} from '../agent/vendor/pretext/measurement.js';
|
|
6
|
+
import {KIND_BITS, TEXT, SPACE, SOFT_HYPHEN, ZERO_WIDTH_BREAK, ZERO_WIDTH_GLUE, OBJECT, SPACED, UNBROKEN} from '../agent/vendor/pretext/analysis.js';
|
|
7
|
+
|
|
8
|
+
// The sole pin-specific bridge to bac1774c0f8b7a92e44b9da43b6d5443d55d0d9b.
|
|
9
|
+
// Preparation seals source, whole-word geometry and legal edges. A separate captured fit
|
|
10
|
+
// observes only requested edge intervals before sealing its plan. Neither phase exports a
|
|
11
|
+
// vendor handle, a mutable metric object or a function that can measure after readiness.
|
|
12
|
+
const opening = /^[\p{Ps}\p{Pi}\p{Pf}"']+/u;
|
|
13
|
+
const closing = /[\p{Pe}\p{Pi}\p{Pf}"'.,;:!?]+$/u;
|
|
14
|
+
const empty = values => values == null || !values.some(value => value !== null && value !== 0);
|
|
15
|
+
const finite = value => Number.isFinite(value) && value >= 0;
|
|
16
|
+
const noContexts = Object.freeze([]);
|
|
17
|
+
|
|
18
|
+
function lowerBound(values, offset) {
|
|
19
|
+
let lo = 0, hi = values.length;
|
|
20
|
+
while (lo < hi) {
|
|
21
|
+
const mid = lo + hi >>> 1;
|
|
22
|
+
if (values[mid] < offset) lo = mid + 1; else hi = mid;
|
|
23
|
+
}
|
|
24
|
+
return lo;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sourceMeasurer(items, text, offsets, language) {
|
|
28
|
+
const metrics = [];
|
|
29
|
+
return (start, end, suffix = '') => {
|
|
30
|
+
if (start >= end) return 0;
|
|
31
|
+
// The last positive overlap owns the suffix. Duplicate offsets from empty runs
|
|
32
|
+
// must not move it into an empty run or the next font.
|
|
33
|
+
let lo = 0, hi = items.length;
|
|
34
|
+
while (lo < hi) {
|
|
35
|
+
const mid = lo + hi >>> 1;
|
|
36
|
+
if (offsets[mid + 1] <= start) lo = mid + 1; else hi = mid;
|
|
37
|
+
}
|
|
38
|
+
const last = lowerBound(offsets, end) - 1;
|
|
39
|
+
let width = 0;
|
|
40
|
+
for (let index = lo; index <= last; index++) {
|
|
41
|
+
const a = Math.max(start, offsets[index]), b = Math.min(end, offsets[index + 1]);
|
|
42
|
+
if (a >= b) continue;
|
|
43
|
+
const item = items[index];
|
|
44
|
+
let metric = metrics[index];
|
|
45
|
+
if (!metric) {
|
|
46
|
+
const font = getFontMeasurement(item.font, language, item.letterSpacing !== 0);
|
|
47
|
+
metric = metrics[index] = {font, emoji: textMayContainEmoji(item.text) ? getEmojiCorrection(item.font, font) : 0};
|
|
48
|
+
}
|
|
49
|
+
const body = text.slice(a, b).replace(/[\t\r\n\f ]+/g, ' ').replace(/\u00ad/g, '') + (index === last ? suffix : '');
|
|
50
|
+
const measured = item.letterSpacing
|
|
51
|
+
? measureWithLetterSpacing(body, item.letterSpacing, metric.emoji, metric.font)
|
|
52
|
+
: getTextWidth(body, metric.font, metric.emoji);
|
|
53
|
+
if (measured === null) return NaN;
|
|
54
|
+
// Keep the original per-run shaping and left-to-right addition order.
|
|
55
|
+
width += measured + (item.break === 'never' ? item.extraWidth : 0);
|
|
56
|
+
}
|
|
57
|
+
return width;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Only the preparation scope calls this, while its source/font ticket is live. The
|
|
62
|
+
// short-lived observer owns the vendor metrics and context rows; the sealed geometry
|
|
63
|
+
// retains only immutable coordinates, corrections and its resolved shaping language.
|
|
64
|
+
export function createParagraphObserver(items, geometry) {
|
|
65
|
+
const measure = sourceMeasurer(items, geometry.text, geometry.offsets, geometry.language);
|
|
66
|
+
const rows = new Map();
|
|
67
|
+
return ({segment, word: index, from, to}) => {
|
|
68
|
+
const word = geometry.segments[segment].words[index], contexts = word.contexts;
|
|
69
|
+
const start = from < 0 ? null : word.breaks[from], end = to < 0 ? null : word.breaks[to];
|
|
70
|
+
if (!start) return measure(word.start, end.offset, end.glyph || '') + end.prefixContext - end.prefixTrim;
|
|
71
|
+
if (!end) {
|
|
72
|
+
let correction = 0;
|
|
73
|
+
for (let at = start.contextAfter; at < contexts.length; at++) correction += contexts[at].delta;
|
|
74
|
+
return measure(start.offset, word.end) + correction + start.startExtra;
|
|
75
|
+
}
|
|
76
|
+
// For a fixed start the fitter visits increasing ends. Extend that row's exact
|
|
77
|
+
// left-to-right sum; no total-minus-prefix arithmetic changes its rounding.
|
|
78
|
+
let row = rows.get(start);
|
|
79
|
+
if (!row || end.offset < row.end) rows.set(start, row = {at: start.contextInside, sum: 0, end: start.offset});
|
|
80
|
+
while (row.at < contexts.length && contexts[row.at].end <= end.offset) row.sum += contexts[row.at++].delta;
|
|
81
|
+
row.end = end.offset;
|
|
82
|
+
return measure(start.offset, end.offset, end.glyph || '') + start.startExtra - (end.glyph ? 0 : end.endTrim) + row.sum;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function* prepareParagraphCandidateSteps(items, options) {
|
|
87
|
+
const profile = getEngineProfile();
|
|
88
|
+
if (profile.lineBreakScan !== 'blink' || options.whiteSpace !== 'normal' || items.some(item => item.text === undefined)) return null;
|
|
89
|
+
const language = getPreparationLanguage(profile), offsets = [0], chunks = [];
|
|
90
|
+
let cutCount = 0;
|
|
91
|
+
for (const item of items) offsets.push(offsets.at(-1) + item.text.length);
|
|
92
|
+
const text = items.map(item => item.text).join('');
|
|
93
|
+
const measure = sourceMeasurer(items, text, offsets, language);
|
|
94
|
+
const boundaries = options.paragraphBreaks || [0, items.length];
|
|
95
|
+
for (let chunkIndex = 0; chunkIndex < boundaries.length - 1; chunkIndex++) {
|
|
96
|
+
const from = boundaries[chunkIndex], to = boundaries[chunkIndex + 1], source = items.slice(from, to);
|
|
97
|
+
const chunk = {start: offsets[from], end: offsets[to], words: []};
|
|
98
|
+
chunks.push(chunk);
|
|
99
|
+
if (!source.some(item => item.text)) continue;
|
|
100
|
+
const flow = prepareRichInlineSegments(source, {whiteSpace: 'normal', wordBreak: options.wordBreak});
|
|
101
|
+
const data = flow.data, flags = data.segmentFlags, units = [];
|
|
102
|
+
// These are object-edge contracts that this text-only projection cannot paint. Refuse
|
|
103
|
+
// before making a plan, rather than dropping an advance or inventing a native equivalent.
|
|
104
|
+
if (!empty(data.items?.insideExtras) || !empty(data.items?.fillExtras) || !empty(data.items?.openingEdges)) return null;
|
|
105
|
+
for (let index = 0; index < flags.length; index++) {
|
|
106
|
+
const kind = flags[index] & KIND_BITS;
|
|
107
|
+
if (![TEXT, SPACE, SOFT_HYPHEN, ZERO_WIDTH_BREAK, ZERO_WIDTH_GLUE, OBJECT].includes(kind)) return null;
|
|
108
|
+
const itemIndex = from + (flow.segmentItems ? flow.segmentItems[index] : flow.onlyItem);
|
|
109
|
+
const item = items[itemIndex], start = offsets[itemIndex] + flow.sourceStarts[index];
|
|
110
|
+
const end = kind === OBJECT ? offsets[itemIndex + 1] : offsets[itemIndex] + flow.sourceEnds[index];
|
|
111
|
+
const width = data.widths[index] + ((flags[index] & SPACED) ? data.letterSpacing : 0);
|
|
112
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < chunk.start || end > chunk.end || end < start || !finite(width)) return null;
|
|
113
|
+
units.push({index, kind, itemIndex, start, end, width,
|
|
114
|
+
startExtra: data.lineStartExtras?.[index] || 0,
|
|
115
|
+
endTrim: data.lineEndTrims?.[index] || 0});
|
|
116
|
+
}
|
|
117
|
+
let word = null, gapWidth = 0;
|
|
118
|
+
const finish = () => { if (word) { chunk.words.push(word); word = null; } };
|
|
119
|
+
for (let at = 0; at < units.length; at++) {
|
|
120
|
+
const unit = units[at], next = units[at + 1];
|
|
121
|
+
if (unit.kind === SPACE && (!next || !(flags[next.index] & UNBROKEN))) {
|
|
122
|
+
finish(); gapWidth += unit.width; continue;
|
|
123
|
+
}
|
|
124
|
+
if (!word) {
|
|
125
|
+
const previous = chunk.words.at(-1);
|
|
126
|
+
if (previous) previous.gap = {start: previous.end, end: unit.start, width: gapWidth};
|
|
127
|
+
word = {start: unit.start, end: unit.end, width: 0, first: at, last: at, breaks: [],
|
|
128
|
+
protected: false, startExtra: unit.startExtra, endTrim: 0, opening: 0, closing: 0};
|
|
129
|
+
gapWidth = 0;
|
|
130
|
+
}
|
|
131
|
+
word.end = unit.end; word.width += unit.width; word.last = at; word.endTrim = unit.endTrim;
|
|
132
|
+
word.protected ||= unit.kind === OBJECT || unit.kind === SPACE;
|
|
133
|
+
if (next && next.kind !== SPACE && !(flags[next.index] & UNBROKEN)) {
|
|
134
|
+
const kind = unit.kind === SOFT_HYPHEN ? 'soft' : text[unit.end - 1] === '-' ? 'hyphen' : 'break';
|
|
135
|
+
word.breaks.push({offset: unit.end, kind, at});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
finish();
|
|
139
|
+
yield;
|
|
140
|
+
|
|
141
|
+
// Native language data contributes only legal source positions. Every advance,
|
|
142
|
+
// including the displayed discretionary glyph, still comes from Pretext below.
|
|
143
|
+
const automatic = paragraphHyphens(chunk.words, text, items, offsets, options.language, options.document);
|
|
144
|
+
if (!automatic) return null;
|
|
145
|
+
for (const word of chunk.words) for (const offset of automatic.get(word) || []) {
|
|
146
|
+
if (!word.breaks.some(cut => cut.offset === offset)) word.breaks.push({offset, kind:'auto', at:word.first});
|
|
147
|
+
}
|
|
148
|
+
yield;
|
|
149
|
+
|
|
150
|
+
// Whole words and optical edges use the same font/language/letter-spacing cache
|
|
151
|
+
// as ordinary wrapping. Discretionary edges belong to the captured fit phase.
|
|
152
|
+
let measuredWords = 0;
|
|
153
|
+
for (const word of chunk.words) {
|
|
154
|
+
if (word.protected) word.breaks = [];
|
|
155
|
+
word.breaks.sort((a,b) => a.offset-b.offset);
|
|
156
|
+
const joined = word.breaks.length || /\u00ad/.test(text.slice(word.start, word.end));
|
|
157
|
+
const contexts = [];
|
|
158
|
+
let measuredEdges = 0;
|
|
159
|
+
if (joined) {
|
|
160
|
+
// A joined word and its cut edges are separate shaping observations. Retain the
|
|
161
|
+
// engine's contextual advances (including positive following-space kerning and
|
|
162
|
+
// punctuation trims) when replacing the sum of pieces with the joined advance.
|
|
163
|
+
// A leading combining-only segment already belongs to that joined observation;
|
|
164
|
+
// its mark-context correction must not be added a second time.
|
|
165
|
+
for (let at = word.first; at <= word.last; at++) {
|
|
166
|
+
const unit = units[at];
|
|
167
|
+
if (unit.kind !== TEXT || /^\p{M}+$/u.test(text.slice(unit.start, unit.end))) continue;
|
|
168
|
+
const delta = unit.width - measure(unit.start, unit.end);
|
|
169
|
+
if (delta !== 0) contexts.push(Object.freeze({start: unit.start, end: unit.end, delta}));
|
|
170
|
+
if (++measuredEdges % 8 === 0) yield;
|
|
171
|
+
}
|
|
172
|
+
word.width = measure(word.start, word.end) + contexts.reduce((sum, item) => sum + item.delta, 0);
|
|
173
|
+
}
|
|
174
|
+
let autoBefore = word.first - 1, autoAfter = word.first, contextAfter = 0, contextInside = 0, prefixContext = 0;
|
|
175
|
+
for (const cut of word.breaks) {
|
|
176
|
+
if (cut.kind === 'soft' || cut.kind === 'auto') {
|
|
177
|
+
// The projection's explicit glyph policy: the text font's hyphen-minus.
|
|
178
|
+
// Its whole shaped prefix is measured below; no fallback-family search.
|
|
179
|
+
cut.glyph = '-';
|
|
180
|
+
}
|
|
181
|
+
let before = cut.at, after = cut.at + 1;
|
|
182
|
+
if (cut.kind === 'auto') {
|
|
183
|
+
while (autoBefore < word.last && units[autoBefore + 1].start < cut.offset) autoBefore++;
|
|
184
|
+
while (autoAfter <= word.last && units[autoAfter].end <= cut.offset) autoAfter++;
|
|
185
|
+
before = autoBefore; after = autoAfter;
|
|
186
|
+
}
|
|
187
|
+
while (before >= word.first && units[before].kind !== TEXT) before--;
|
|
188
|
+
while (after <= word.last && units[after].kind !== TEXT) after++;
|
|
189
|
+
const last = before >= word.first ? units[before] : null, first = after <= word.last ? units[after] : null;
|
|
190
|
+
cut.startExtra = first?.start === cut.offset ? first.startExtra : 0;
|
|
191
|
+
cut.endTrim = last?.end === cut.offset ? last.endTrim : 0;
|
|
192
|
+
cut.prefixTrim = cut.glyph ? 0 : last?.endTrim || 0;
|
|
193
|
+
while (contextAfter < contexts.length && contexts[contextAfter].end <= cut.offset) prefixContext += contexts[contextAfter++].delta;
|
|
194
|
+
while (contextInside < contexts.length && contexts[contextInside].start < cut.offset) contextInside++;
|
|
195
|
+
cut.prefixContext = prefixContext;
|
|
196
|
+
cut.contextAfter = contextAfter; cut.contextInside = contextInside;
|
|
197
|
+
delete cut.at;
|
|
198
|
+
}
|
|
199
|
+
word.breaks = word.breaks.filter(cut => cut.offset > word.start && cut.offset < word.end);
|
|
200
|
+
for (const cut of word.breaks) cut.slot = cutCount++;
|
|
201
|
+
word.contexts = contexts.length ? Object.freeze(contexts) : noContexts;
|
|
202
|
+
if (options.hanging && !word.protected) {
|
|
203
|
+
const body = text.slice(word.start, word.end), head = opening.exec(body), tail = closing.exec(body);
|
|
204
|
+
if (head && head[0].length < body.length && !/^\p{M}/u.test(body.slice(head[0].length))) {
|
|
205
|
+
word.openEnd = word.start + head[0].length;
|
|
206
|
+
word.opening = Math.min(parseFontSize(items[units[word.first].itemIndex].font)*.5, Math.max(0, measure(word.start, word.openEnd)) * .7);
|
|
207
|
+
}
|
|
208
|
+
if (tail && tail.index > 0) {
|
|
209
|
+
word.closeStart = word.start + tail.index;
|
|
210
|
+
word.closing = Math.min(parseFontSize(items[units[word.last].itemIndex].font)*.5, Math.max(0, measure(word.closeStart, word.end)) * .7);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!finite(word.width) || !finite(word.opening) || !finite(word.closing)) return null;
|
|
214
|
+
delete word.first; delete word.last;
|
|
215
|
+
if (word.gap) Object.freeze(word.gap);
|
|
216
|
+
word.breaks.forEach(Object.freeze); Object.freeze(word.breaks); Object.freeze(word);
|
|
217
|
+
if (++measuredWords % 8 === 0) yield;
|
|
218
|
+
}
|
|
219
|
+
Object.freeze(chunk.words); Object.freeze(chunk);
|
|
220
|
+
}
|
|
221
|
+
for (const chunk of chunks) { Object.freeze(chunk.words); Object.freeze(chunk); }
|
|
222
|
+
return Object.freeze({text, language, cutCount, offsets: Object.freeze(offsets), segments: Object.freeze(chunks)});
|
|
223
|
+
}
|