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-marks.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// The one owner of
|
|
2
|
+
// The one owner of inline typography, ink and page breaks.
|
|
3
|
+
import {markdownSourcePositions} from './md-source.mjs';
|
|
3
4
|
|
|
4
5
|
// The exported page retains this reader alone. The editor uses the same closure, then adds its writers and scans.
|
|
5
6
|
const inkReader = () => {
|
|
@@ -16,8 +17,8 @@ const inkReader = () => {
|
|
|
16
17
|
// size when the stroke was drawn and at the frame's offset from the words' box, both in hundredths of an em; w is the pen's width as Draw
|
|
17
18
|
// counts a nib (2 to 24; 9 is the default and is never written): the stroke is drawn 0.11 em wide at 9 and in proportion to w otherwise; PATH
|
|
18
19
|
// is the stroke, its first point absolute in the frame and the rest as moves, hundredths of an em. One spelling: the fields in this order,
|
|
19
|
-
// one space between, integers only. A comment that does not read this way is ordinary comment text and marks nothing. Ink pairs
|
|
20
|
-
//
|
|
20
|
+
// one space between, integers only. A comment that does not read this way is ordinary comment text and marks nothing. Ink pairs nest:
|
|
21
|
+
// each closer takes the innermost open ink opener. Arrow/end alone take a pairing number, an end takes no other data; under, strike and arrow may omit a path for a line
|
|
21
22
|
// derived from their words.
|
|
22
23
|
const INK_KINDS = Object.freeze(['under', 'strike', 'ring', 'bracket', 'free', 'arrow', 'end']);
|
|
23
24
|
|
|
@@ -28,9 +29,6 @@ const inkReader = () => {
|
|
|
28
29
|
+ '(?: (' + INK_INT + ',' + INK_INT + '(?: ' + INK_INT + ',' + INK_INT + ')*))?';
|
|
29
30
|
const INK_OPEN_PATTERN = '<!--ink ' + INK_OPEN_BODY + '-->';
|
|
30
31
|
|
|
31
|
-
// Relocated into the parse Worker by .toString() with parseInkOpen and isInkClose (engine.js _buildParseWorkerSource):
|
|
32
|
-
// reads only its parameter and the planted INK_PATH_MAX, INK_INT_MAX and TEXT_COLOR_NAMES; parseInkOpen reads
|
|
33
|
-
// INK_OPEN_PATTERN and this function; isInkClose reads INK_CLOSE.
|
|
34
32
|
function readInkMatch(match) {
|
|
35
33
|
const linked = match[1] === 'arrow' || match[1] === 'end';
|
|
36
34
|
if (linked !== (match[2] != null) || (!linked && match[1] !== 'under' && match[1] !== 'strike' && match[9] == null)) return null;
|
|
@@ -68,73 +66,20 @@ const inkReader = () => {
|
|
|
68
66
|
}
|
|
69
67
|
return { TEXT_COLOR_NAMES, COLOR_NAME_ALT, COLOR_VALUE, INK_KINDS, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, readInkMatch, parseInkOpen, parseInkBody };
|
|
70
68
|
};
|
|
71
|
-
const { TEXT_COLOR_NAMES,
|
|
69
|
+
const { TEXT_COLOR_NAMES, COLOR_VALUE, INK_KINDS, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN, readInkMatch, parseInkOpen, parseInkBody } = inkReader();
|
|
72
70
|
export { readInkMatch, parseInkOpen, parseInkBody };
|
|
73
71
|
|
|
74
72
|
const TEXT_COLOR_HEX_TO_NAME = Object.freeze(
|
|
75
73
|
Object.fromEntries(Object.entries(TEXT_COLOR_NAMES).map(([name, hex]) => [hex, name]))
|
|
76
74
|
);
|
|
77
|
-
const COLOR_CLOSE = '<!--/c-->';
|
|
78
75
|
const PAGE_BREAK_MARKER = '<!--md-break:v1 page-->';
|
|
79
76
|
|
|
80
|
-
export { TEXT_COLOR_NAMES, TEXT_COLOR_HEX_TO_NAME,
|
|
81
|
-
|
|
82
|
-
export function formatColorOpen(hex) {
|
|
83
|
-
return '<!--c ' + (TEXT_COLOR_HEX_TO_NAME[hex] || hex) + '-->';
|
|
84
|
-
}
|
|
77
|
+
export { TEXT_COLOR_NAMES, TEXT_COLOR_HEX_TO_NAME, PAGE_BREAK_MARKER };
|
|
85
78
|
|
|
86
79
|
export function formatColorRun(hex, content) {
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Relocated into the parse Worker by .toString() (engine.js _buildParseWorkerSource): read only parameters, TEXT_COLOR_NAMES and COLOR_CLOSE.
|
|
91
|
-
|
|
92
|
-
// Returns {hex, length in UTF-16 units} or null; not end-anchored.
|
|
93
|
-
export function matchColorOpen(text) {
|
|
94
|
-
if (typeof text !== 'string') return null;
|
|
95
|
-
const match = new RegExp('^<!--c (#[0-9a-f]{6}|' + Object.keys(TEXT_COLOR_NAMES).join('|') + ')-->').exec(text);
|
|
96
|
-
if (!match) return null;
|
|
97
|
-
const value = match[1];
|
|
98
|
-
return { hex: value.charCodeAt(0) === 0x23 /* '#' */ ? value : TEXT_COLOR_NAMES[value], length: match[0].length };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function parseColorOpen(comment) {
|
|
102
|
-
if (typeof comment !== 'string') return null;
|
|
103
|
-
const match = new RegExp('^<!--c (#[0-9a-f]{6}|' + Object.keys(TEXT_COLOR_NAMES).join('|') + ')-->$').exec(comment);
|
|
104
|
-
if (!match) return null;
|
|
105
|
-
const value = match[1];
|
|
106
|
-
return value.charCodeAt(0) === 0x23 /* '#' */ ? value : TEXT_COLOR_NAMES[value];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function isColorClose(comment) {
|
|
110
|
-
return comment === COLOR_CLOSE;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function scanColorMarkers(source) {
|
|
114
|
-
const text = String(source == null ? '' : source);
|
|
115
|
-
const scan = new RegExp('<!--c (?:(#[0-9a-f]{6})|(' + COLOR_NAME_ALT + '))-->|<!--/c-->', 'g');
|
|
116
|
-
const markers = [];
|
|
117
|
-
let match;
|
|
118
|
-
while ((match = scan.exec(text))) {
|
|
119
|
-
const hex = match[1] || (match[2] ? TEXT_COLOR_NAMES[match[2]] : null) || null;
|
|
120
|
-
markers.push({ start: match.index, end: match.index + match[0].length, hex });
|
|
121
|
-
}
|
|
122
|
-
return markers;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function pairColorMarkers(source) {
|
|
126
|
-
return pairMarkers(source, 'color');
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function stripColorMarkers(text) {
|
|
130
|
-
return String(text == null ? '' : text).replace(new RegExp('<!--(?:c ' + COLOR_VALUE + '|/c)-->', 'g'), '');
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function hasColorMarker(text) {
|
|
134
|
-
return new RegExp('<!--(?:c ' + COLOR_VALUE + '|/c)-->').test(String(text == null ? '' : text));
|
|
80
|
+
return typography.wrap({color: hex}, content);
|
|
135
81
|
}
|
|
136
82
|
|
|
137
|
-
export const FONT_CLOSE = '<!--/f-->';
|
|
138
83
|
export const FONT_STACKS = Object.freeze({
|
|
139
84
|
sans: Object.freeze({stack: 'sans', css: '', word: ''}),
|
|
140
85
|
serif: Object.freeze({stack: 'serif', css: "Georgia,'Palatino Linotype',Palatino,'Times New Roman',Times,serif", word: 'Georgia'}),
|
|
@@ -143,8 +88,7 @@ export const FONT_STACKS = Object.freeze({
|
|
|
143
88
|
hand: Object.freeze({stack: 'hand', css: 'cursive', word: 'Segoe Print'}),
|
|
144
89
|
});
|
|
145
90
|
|
|
146
|
-
// A family is one font-table name, never a CSS list or an HTML fragment.
|
|
147
|
-
// relocated into the parse worker with FONT_CLOSE and their original function names.
|
|
91
|
+
// A family is one font-table name, never a CSS list or an HTML fragment.
|
|
148
92
|
export function normalizeFontFamily(value) {
|
|
149
93
|
if (typeof value !== 'string') return null;
|
|
150
94
|
const family = value.trim();
|
|
@@ -158,56 +102,212 @@ export function fontFamilyCss(value) {
|
|
|
158
102
|
return generic ? generic.css || "'Geist',system-ui,sans-serif" : '"' + family.replace(/[\\"]/g, '\\$&') + '"';
|
|
159
103
|
}
|
|
160
104
|
|
|
161
|
-
export function formatFontOpen(family) {
|
|
162
|
-
const name = normalizeFontFamily(family);
|
|
163
|
-
if (!name) throw new TypeError('font: use one family name');
|
|
164
|
-
return '<!--f ' + name + '-->';
|
|
165
|
-
}
|
|
166
|
-
|
|
167
105
|
export function formatFontRun(family, content) {
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const text = String(source ?? ''), markers = [];
|
|
189
|
-
const scan = /<!--(?:f [^<>\r\n]{1,256}|\/f)-->/g;
|
|
190
|
-
for (const match of text.matchAll(scan)) {
|
|
191
|
-
const family = match[0] === FONT_CLOSE ? null : parseFontOpen(match[0]);
|
|
192
|
-
if (family || match[0] === FONT_CLOSE) markers.push({start: match.index, end: match.index + match[0].length, family});
|
|
106
|
+
return typography.wrap({family}, content);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// One typed interpretation for source, DOM and native-file adapters. The worker carries this
|
|
110
|
+
// exact factory and its explicit grammar inputs; no DOM, CSS evaluator or resource loader.
|
|
111
|
+
export function createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACKS}) {
|
|
112
|
+
const LIMIT = 4096, DEPTH = 64;
|
|
113
|
+
const generics = {serif: 'serif', 'sans-serif': 'sans', monospace: 'mono', cursive: 'hand',
|
|
114
|
+
fantasy: 'fantasy', 'system-ui': 'system', 'ui-serif': 'ui-serif', 'ui-sans-serif': 'ui-sans-serif',
|
|
115
|
+
'ui-monospace': 'ui-monospace', 'ui-rounded': 'ui-rounded', emoji: 'emoji', math: 'math', fangsong: 'fangsong'};
|
|
116
|
+
const escape = value => String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
117
|
+
function size(value) {
|
|
118
|
+
if (typeof value !== 'string' || value.length > 48) return null;
|
|
119
|
+
const match = /^(\d+(?:\.\d{0,12})?|\.\d{1,12})\s*(pt|px)$/i.exec(value.trim());
|
|
120
|
+
if (!match) return null;
|
|
121
|
+
const number = Number(match[1]), unit = match[2].toLowerCase(), points = number * (unit === 'px' ? .75 : 1);
|
|
122
|
+
if (!(points > 0 && points <= 1638)) return null;
|
|
123
|
+
const [integer, fraction = ''] = match[1].split('.'), tail = fraction.replace(/0+$/, '');
|
|
124
|
+
return Object.freeze({css: (integer.replace(/^0+/, '') || '0') + (tail ? '.' + tail : '') + unit,
|
|
125
|
+
unit, value: number, points, halfPoints: points * 2});
|
|
193
126
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
127
|
+
function color(value) {
|
|
128
|
+
if (typeof value !== 'string') return null;
|
|
129
|
+
if (/^#[0-9a-f]{6}$/i.test(value.trim())) return value.trim().toLowerCase();
|
|
130
|
+
// CSSOM serializes admitted hex to opaque rgb(). Native HTML import uses this same boundary.
|
|
131
|
+
const rgb = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*1\s*)?\)$/i.exec(value.trim());
|
|
132
|
+
return rgb && rgb.slice(1).every(channel => Number(channel) <= 255)
|
|
133
|
+
? '#' + rgb.slice(1).map(channel => Number(channel).toString(16).padStart(2, '0')).join('') : null;
|
|
134
|
+
}
|
|
135
|
+
function familyCss(value) {
|
|
136
|
+
if (typeof value !== 'string' || value.length > 2048) return null;
|
|
137
|
+
const css = value.trim();
|
|
138
|
+
const stackKey = text => text.replace(/'/g, '"').replace(/"([\w-]+)"/g, '$1').replace(/\s*,\s*/g, ',');
|
|
139
|
+
for (const [name] of Object.entries(FONT_STACKS)) if ((!/^["']/.test(css) || css.includes(',')) && stackKey(css) === stackKey(fontFamilyCss(name)))
|
|
140
|
+
return Object.freeze({name, css: fontFamilyCss(name), custom: false});
|
|
141
|
+
if (Object.hasOwn(generics, css.toLowerCase())) return Object.freeze({name: generics[css.toLowerCase()], css: css.toLowerCase(), custom: false});
|
|
142
|
+
let name = css;
|
|
143
|
+
if (css[0] === '"' || css[0] === "'") {
|
|
144
|
+
if (css.at(-1) !== css[0]) return null;
|
|
145
|
+
name = '';
|
|
146
|
+
for (let at = 1; at < css.length - 1; at++) {
|
|
147
|
+
if (css[at] === css[0] || /[\r\n\f]/.test(css[at])) return null;
|
|
148
|
+
if (css[at] !== '\\') { name += css[at]; continue; }
|
|
149
|
+
const escaped = /^\\(?:([0-9a-f]{1,6})(?:\r\n|[ \t\r\n\f])?|([^\r\n\f]))/i.exec(css.slice(at));
|
|
150
|
+
if (!escaped || at + escaped[0].length > css.length - 1) return null;
|
|
151
|
+
const code = escaped[1] ? parseInt(escaped[1], 16) : 0;
|
|
152
|
+
name += escaped[1] ? code && code <= 0x10ffff && !(code >= 0xd800 && code <= 0xdfff) ? String.fromCodePoint(code) : '\ufffd' : escaped[2];
|
|
153
|
+
at += escaped[0].length - 1;
|
|
154
|
+
}
|
|
155
|
+
} else if (!/^[\p{L}_-][\p{L}\p{N}_ -]*$/u.test(css) || /^(?:inherit|initial|unset|revert|revert-layer)$/i.test(css)) return null;
|
|
156
|
+
name = normalizeFontFamily(name);
|
|
157
|
+
// These five names are the existing family-choice aliases, not custom font identities.
|
|
158
|
+
if (name && Object.hasOwn(FONT_STACKS, name.toLowerCase())) return null;
|
|
159
|
+
return name ? Object.freeze({name, css: '"' + name.replace(/[\\"]/g, '\\$&') + '"', custom: true}) : null;
|
|
160
|
+
}
|
|
161
|
+
function family(value) {
|
|
162
|
+
if (value && typeof value === 'object') {
|
|
163
|
+
const parsed = familyCss(value.css);
|
|
164
|
+
return parsed && parsed.name === value.name && parsed.custom === value.custom ? parsed : null;
|
|
165
|
+
}
|
|
166
|
+
const name = normalizeFontFamily(value);
|
|
167
|
+
return name ? familyCss(fontFamilyCss(name)) : null;
|
|
168
|
+
}
|
|
169
|
+
function normalize(value, removals = false) {
|
|
170
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new TypeError('Typography requires size, color or family.');
|
|
171
|
+
const result = {};
|
|
172
|
+
for (const key of Object.keys(value)) {
|
|
173
|
+
if (!['size', 'color', 'family'].includes(key)) throw new TypeError('Unknown typography property: ' + key);
|
|
174
|
+
if (value[key] === null && removals) { result[key] = null; continue; }
|
|
175
|
+
const parsed = key === 'size' ? size(value[key])?.css : key === 'color' ? color(value[key]) : family(value[key]);
|
|
176
|
+
if (!parsed) throw new TypeError('Invalid typography ' + key + '.');
|
|
177
|
+
result[key] = parsed;
|
|
178
|
+
}
|
|
179
|
+
return Object.freeze(result);
|
|
180
|
+
}
|
|
181
|
+
// Rendering drops unsupported declarations; rich editing admits only wholly owned
|
|
182
|
+
// spans so unrelated HTML and rejected attributes keep their exact-source editor.
|
|
183
|
+
function parseStyle(value, editable = false) {
|
|
184
|
+
if (typeof value !== 'string' || value.length > LIMIT) return editable ? null : Object.freeze({});
|
|
185
|
+
const parts = []; let quote = '', start = 0;
|
|
186
|
+
for (let at = 0; at <= value.length; at++) {
|
|
187
|
+
const ch = value[at];
|
|
188
|
+
if (quote) { if (ch === '\\') at++; else if (ch === quote) quote = ''; }
|
|
189
|
+
else if (ch === '"' || ch === "'") quote = ch;
|
|
190
|
+
else if (ch === ';' || at === value.length) { parts.push(value.slice(start, at)); start = at + 1; }
|
|
191
|
+
}
|
|
192
|
+
if (quote || parts.length > 32) return editable ? null : Object.freeze({});
|
|
193
|
+
const result = {};
|
|
194
|
+
for (const part of parts) {
|
|
195
|
+
if (!part.trim()) continue;
|
|
196
|
+
const colon = part.indexOf(':'); if (colon < 0) { if (editable) return null; continue; }
|
|
197
|
+
const key = part.slice(0, colon).trim().toLowerCase(), raw = part.slice(colon + 1).trim();
|
|
198
|
+
const parsed = key === 'font-size' ? size(raw)?.css : key === 'color' ? color(raw) : key === 'font-family' ? familyCss(raw) : null;
|
|
199
|
+
if (parsed) result[key === 'font-size' ? 'size' : key === 'font-family' ? 'family' : 'color'] = parsed;
|
|
200
|
+
else if (editable) return null;
|
|
201
|
+
}
|
|
202
|
+
return Object.freeze(result);
|
|
203
|
+
}
|
|
204
|
+
function style(value) {
|
|
205
|
+
const held = normalize(value), parts = [];
|
|
206
|
+
if (held.size) parts.push('font-size:' + held.size);
|
|
207
|
+
if (held.color) parts.push('color:' + held.color);
|
|
208
|
+
if (held.family) parts.push('font-family:' + held.family.css);
|
|
209
|
+
return parts.join(';');
|
|
210
|
+
}
|
|
211
|
+
const open = value => { const css = style(value); return css ? '<span style="' + escape(css) + '">' : ''; };
|
|
212
|
+
const wrap = (value, content) => {
|
|
213
|
+
const start = open(value);
|
|
214
|
+
if (!start || !content) return content;
|
|
215
|
+
// Importers can supply a styled break by itself, or before/after words. Keep
|
|
216
|
+
// those exact line-edge bytes outside the new span so its opener has words.
|
|
217
|
+
const prefix = /^(?:[ \t]*(?:\r\n|\r(?!\n)|\n))+/.exec(content)?.[0] || '';
|
|
218
|
+
let body = content.slice(prefix.length);
|
|
219
|
+
const suffix = /(?:[ \t]*(?:\r\n|\r(?!\n)|\n))+[ \t]*$/.exec(body)?.[0] || '';
|
|
220
|
+
body = body.slice(0, body.length - suffix.length);
|
|
221
|
+
if (!body) return content;
|
|
222
|
+
// Soft/hard line breaks can belong to one Markdown paragraph. A blank line or
|
|
223
|
+
// an opener on a line of its own would instead change the block grammar.
|
|
224
|
+
if (/^\s*\n|\n[ \t]*\n/.test(body.replace(/\r\n?/g, '\n')))
|
|
225
|
+
throw new TypeError('Typography wraps one inline paragraph at a time.');
|
|
226
|
+
return prefix + start + body + '</span>' + suffix;
|
|
227
|
+
};
|
|
228
|
+
function parseOpen(source, decode = value => value, editable = false) {
|
|
229
|
+
if (typeof source !== 'string' || source.length > LIMIT + 1024 || !/^<span(?=[\s>])/i.test(source) || !source.endsWith('>') || /\/\s*>$/.test(source)) return null;
|
|
230
|
+
const attrs = /\s+([^\s=<>/"']+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s=<>`"']+)))?/y;
|
|
231
|
+
let at = 5, css = null;
|
|
232
|
+
while (at < source.length - 1) {
|
|
233
|
+
if (/^\s*>$/.test(source.slice(at))) break;
|
|
234
|
+
attrs.lastIndex = at; const match = attrs.exec(source);
|
|
235
|
+
if (!match) return null;
|
|
236
|
+
at = attrs.lastIndex;
|
|
237
|
+
if (match[1].toLowerCase() !== 'style') { if (editable) return null; continue; }
|
|
238
|
+
if (css !== null) return null;
|
|
239
|
+
css = decode(match[2] ?? match[3] ?? match[4] ?? '');
|
|
240
|
+
}
|
|
241
|
+
const facts = parseStyle(css, editable);
|
|
242
|
+
return facts && Object.keys(facts).length ? facts : null;
|
|
243
|
+
}
|
|
244
|
+
function nodeStyle(node) {
|
|
245
|
+
const result = {...parseStyle(node?.getAttribute?.('style') || '')};
|
|
246
|
+
const authoredSize = size(node?.getAttribute?.('data-md-size'));
|
|
247
|
+
const authoredColor = color(node?.getAttribute?.('data-md-color'));
|
|
248
|
+
const authoredFamily = normalizeFontFamily(node?.getAttribute?.('data-md-font'));
|
|
249
|
+
if (authoredSize) result.size = authoredSize.css;
|
|
250
|
+
if (authoredColor) result.color = authoredColor;
|
|
251
|
+
if (authoredFamily && !result.family) result.family = family(authoredFamily);
|
|
252
|
+
return Object.freeze(result);
|
|
253
|
+
}
|
|
254
|
+
function runs(root) {
|
|
255
|
+
let text = ''; const paint = [], leaves = [];
|
|
256
|
+
const visit = (node, inherited, blocked) => {
|
|
257
|
+
if (node.nodeType === 3) {
|
|
258
|
+
const value = String(node.nodeValue || '').replace(/\u2060/g, '\n'), start = text.length;
|
|
259
|
+
text += value;
|
|
260
|
+
for (let index = 0; index < value.length; index++) paint.push(inherited);
|
|
261
|
+
if (value) leaves.push({start, end: text.length, blocked});
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (node.nodeType !== 1 && node !== root) return;
|
|
265
|
+
const own = nodeStyle(node), next = {...inherited, ...own};
|
|
266
|
+
const literal = blocked || /^(?:CODE|PRE|KBD|SAMP|MATH)$/.test(node.tagName) ||
|
|
267
|
+
node.classList?.contains('math-rendered') || node.classList?.contains('math-placeholder');
|
|
268
|
+
for (const child of node.childNodes || []) visit(child, next, !!literal);
|
|
269
|
+
};
|
|
270
|
+
visit(root, {}, false);
|
|
271
|
+
return {text, paint, leaves};
|
|
272
|
+
}
|
|
273
|
+
function install(md) {
|
|
274
|
+
if (md.__rapierTypography) return md;
|
|
275
|
+
md.__rapierTypography = true;
|
|
276
|
+
md.core.ruler.after('linkify', 'rapier-typography', state => {
|
|
277
|
+
for (const block of state.tokens) {
|
|
278
|
+
if (block.type !== 'inline' || !block.children) continue;
|
|
279
|
+
const stack = []; let depth = 0, overflow = 0;
|
|
280
|
+
for (const token of block.children) {
|
|
281
|
+
if (token.type !== 'html_inline') { depth += token.nesting || 0; continue; }
|
|
282
|
+
const source = token.content, close = /^<\/span\s*>$/i.test(source);
|
|
283
|
+
if (!close && !/^<span(?=[\s>])/i.test(source)) continue;
|
|
284
|
+
if (overflow) { overflow += close ? -1 : 1; continue; }
|
|
285
|
+
if (!close) {
|
|
286
|
+
if (stack.length === DEPTH) { overflow = 1; continue; }
|
|
287
|
+
const facts = parseOpen(source, value => md.utils.unescapeAll(value.replace(/\\/g, '\\\\')));
|
|
288
|
+
stack.push({token, facts, depth, source}); continue;
|
|
289
|
+
}
|
|
290
|
+
const held = stack.pop();
|
|
291
|
+
if (!held || !held.facts || depth !== held.depth) continue;
|
|
292
|
+
const type = 'typography', opener = held.token;
|
|
293
|
+
opener.type = 'rapier_' + type + '_open'; opener.tag = 'span'; opener.nesting = 1; opener.content = '';
|
|
294
|
+
(opener.meta ||= {}).typography = Object.freeze({style: held.facts, open: held.source, close: source});
|
|
295
|
+
opener.attrSet('style', style(held.facts));
|
|
296
|
+
if (held.facts.color) opener.attrSet('data-md-color', held.facts.color);
|
|
297
|
+
if (held.facts.size) opener.attrSet('data-md-size', held.facts.size);
|
|
298
|
+
if (held.facts.family) opener.attrSet('data-md-font', held.facts.family.name);
|
|
299
|
+
token.type = 'rapier_' + type + '_close'; token.tag = 'span'; token.nesting = -1; token.content = '';
|
|
300
|
+
(token.meta ||= {}).typography = opener.meta.typography;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return md;
|
|
305
|
+
}
|
|
306
|
+
return Object.freeze({size, color, family, familyCss, normalize, parseStyle, style, open, wrap, parseOpen, nodeStyle, runs, install});
|
|
206
307
|
}
|
|
207
308
|
|
|
208
|
-
export
|
|
209
|
-
|
|
210
|
-
}
|
|
309
|
+
export const typography = createTypography({normalizeFontFamily, fontFamilyCss, FONT_STACKS});
|
|
310
|
+
export function typographySourceRanges(source, parser) { return markdownSourcePositions(source, parser).typography; }
|
|
211
311
|
|
|
212
312
|
const INK_CLOSE = '<!--/ink-->';
|
|
213
313
|
export { INK_KINDS, INK_CLOSE, INK_PATH_MAX, INK_INT_MAX, INK_OPEN_PATTERN };
|
|
@@ -270,7 +370,7 @@ export function matchInkOpen(text) {
|
|
|
270
370
|
// The opener's body is what a rendered span carries (a renderer's sanitizer drops an attribute whose value holds the
|
|
271
371
|
// comment's own close): the opener after `<!--ink ` and before `-->`, so the attribute reads `under red box=… 0,95 …`,
|
|
272
372
|
// and only a body that reads as a mark goes back. Read and write through the same grammar, so an invalid body cannot
|
|
273
|
-
// invent a mark.
|
|
373
|
+
// invent a mark.
|
|
274
374
|
export function inkOpenBody(opener) {
|
|
275
375
|
if (typeof opener !== 'string') return null;
|
|
276
376
|
const match = new RegExp('^' + INK_OPEN_PATTERN + '$').exec(opener);
|
|
@@ -342,7 +442,7 @@ export function pairInkSpans(source, markers = null) {
|
|
|
342
442
|
return pairSpanMarkers(source, 'ink', markers);
|
|
343
443
|
}
|
|
344
444
|
|
|
345
|
-
// Colour
|
|
445
|
+
// Colour keeps its first opener; ink pairs each closer with the innermost opener. The shared range
|
|
346
446
|
// shape lets custody owners keep every independent stroke, including strokes on identical words.
|
|
347
447
|
export function pairMarkers(source, kind, markers = null) {
|
|
348
448
|
const paired = pairSpanMarkers(source, kind, markers);
|
|
@@ -370,8 +470,8 @@ export function pairMarkers(source, kind, markers = null) {
|
|
|
370
470
|
return { runs, strays, arrows };
|
|
371
471
|
}
|
|
372
472
|
|
|
373
|
-
// Where a Will marker may stand (docs/will.md, Conversion). A pair inside a fence, a raw block
|
|
374
|
-
// (
|
|
473
|
+
// Where a Will marker may stand (docs/will.md, Conversion). A pair inside a fence, a raw HTML block
|
|
474
|
+
// (until its blank line) or the opening front matter is text: the line is quoted,
|
|
375
475
|
// and a Word or PDF writer that met it as a marker would govern the wrong region. The same frame
|
|
376
476
|
// as spec/frontmatter.mjs, copied here so this grammar stays inside the MIT kit's closure.
|
|
377
477
|
const WILL_BLOCK_TAGS = new Set('address article aside base basefont blockquote body caption center col colgroup dd details dialog dir div dl dt fieldset figcaption figure footer form frame frameset h1 h2 h3 h4 h5 h6 head header hr html iframe legend li link main menu menuitem nav noframes ol optgroup option p param search section summary table tbody td tfoot th thead title tr track ul'.split(' '));
|
|
@@ -509,7 +609,7 @@ function willQuotedWhere(stack) {
|
|
|
509
609
|
if (item.tags.includes('pre')) return 'raw-pre';
|
|
510
610
|
if (item.tags.includes('td') || item.tags.includes('th')) return 'raw-table';
|
|
511
611
|
if (item.tags.includes('div')) return 'raw-div';
|
|
512
|
-
return
|
|
612
|
+
return 'raw-html';
|
|
513
613
|
}
|
|
514
614
|
}
|
|
515
615
|
return null;
|
|
@@ -522,7 +622,7 @@ function willLeaf(stack) {
|
|
|
522
622
|
|
|
523
623
|
// `spans` are [start, end) of marker lines in `source` (the Will reader's own offsets).
|
|
524
624
|
// Returns the first quoted span, {start, end, where}, or null when every span may stand.
|
|
525
|
-
// `where` is `fence`, `raw-pre`, `raw-div`, `raw-table` or `frontmatter`.
|
|
625
|
+
// `where` is `fence`, `raw-pre`, `raw-div`, `raw-table`, `raw-html` (any other raw HTML block) or `frontmatter`.
|
|
526
626
|
export function quotedWillSpan(source, spans) {
|
|
527
627
|
const text = String(source ?? '');
|
|
528
628
|
const marks = Array.isArray(spans) ? spans : [];
|
|
@@ -589,19 +689,13 @@ export function quotedWillSpan(source, spans) {
|
|
|
589
689
|
}
|
|
590
690
|
|
|
591
691
|
function pairSpanMarkers(source, kind, markers = null) {
|
|
592
|
-
if (kind !== 'ink'
|
|
593
|
-
const color = kind === 'color', font = kind === 'font';
|
|
692
|
+
if (kind !== 'ink') throw new TypeError('Unknown paired marker kind');
|
|
594
693
|
const runs = [], strays = [], stack = [];
|
|
595
|
-
for (const marker of markers ||
|
|
596
|
-
if (
|
|
597
|
-
if ((color || font) && stack.length) { strays.push(marker); continue; }
|
|
598
|
-
stack.push(marker);
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
694
|
+
for (const marker of markers || scanInkMarkers(source)) {
|
|
695
|
+
if (!marker.close) { stack.push(marker); continue; }
|
|
601
696
|
const open = stack.pop();
|
|
602
697
|
if (!open) { strays.push(marker); continue; }
|
|
603
|
-
runs.push({ start: open.start, end: marker.end, innerStart: open.end, innerEnd: marker.start,
|
|
604
|
-
...(color ? { hex: open.hex } : font ? {family: open.family} : { mark: open.mark }) });
|
|
698
|
+
runs.push({ start: open.start, end: marker.end, innerStart: open.end, innerEnd: marker.start, mark: open.mark });
|
|
605
699
|
}
|
|
606
700
|
strays.push(...stack);
|
|
607
701
|
runs.sort((a, b) => a.start - b.start);
|
|
@@ -682,7 +776,6 @@ export function isPageBreakLine(text) {
|
|
|
682
776
|
return typeof text === 'string' && /^<!--md-break:v1 page-->\r?$/.test(text);
|
|
683
777
|
}
|
|
684
778
|
|
|
685
|
-
// Also relocated by .toString(); reads only its own regex.
|
|
686
779
|
export function isPageBreakBlock(text) {
|
|
687
780
|
return typeof text === 'string' && /^<!--md-break:v1 page-->\r?\n?$/.test(text);
|
|
688
781
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import {splitOpeningFrontmatter} from './frontmatter.mjs';
|
|
3
|
+
|
|
4
|
+
// The standard parser owns literal regions and comment boundaries. Offsets are original UTF-16,
|
|
5
|
+
// including BOM/CRLF and list/quote prefixes. A private parser view observes rules; it changes no
|
|
6
|
+
// live rule or token. Call once per source, then ask isText(position) or isComment(start, end).
|
|
7
|
+
export function markdownSourcePositions(source, parser) {
|
|
8
|
+
if (typeof source !== 'string' || !parser) throw new TypeError('Source positions require text and its Markdown parser');
|
|
9
|
+
const {body, bodyOffset} = splitOpeningFrontmatter(source), lines = [bodyOffset];
|
|
10
|
+
for (const row of body.matchAll(/\r\n?|\n/g)) lines.push(bodyOffset + row.index + row[0].length);
|
|
11
|
+
const lineAt = line => lines[line] ?? source.length;
|
|
12
|
+
const literal = [], comments = new Map(), observed = new WeakMap(), typography = [], openedStyles = new Map();
|
|
13
|
+
const add = (start, end) => { if (end > start) literal.push({start, end}); };
|
|
14
|
+
if (bodyOffset) add(0, bodyOffset);
|
|
15
|
+
const owner = Object.create(parser), inline = Object.create(parser.inline), ruler = Object.create(parser.inline.ruler);
|
|
16
|
+
owner.inline = inline; inline.ruler = ruler;
|
|
17
|
+
ruler.getRules = chain => parser.inline.ruler.getRules(chain).map(rule => (state, silent) => {
|
|
18
|
+
const start = state.pos, first = state.tokens.length;
|
|
19
|
+
const matched = rule(state, silent), token = state.tokens.at(-1);
|
|
20
|
+
// A pending text token may flush before this token. Never overwrite a nested rule's span
|
|
21
|
+
// with its enclosing link's range.
|
|
22
|
+
if (matched && !silent && state.tokens.length > first && !observed.has(token) &&
|
|
23
|
+
['code_inline', 'html_inline', 'image', 'footnote_ref'].includes(token?.type)) {
|
|
24
|
+
observed.set(token, {start, end: state.pos, source: state.src,
|
|
25
|
+
comment: token.type === 'html_inline' && token.content.startsWith('<!--'),
|
|
26
|
+
footnote: token.type === 'footnote_ref' && state.src.startsWith('^[', start)
|
|
27
|
+
? state.env.footnotes?.list?.[token.meta.id] : null});
|
|
28
|
+
}
|
|
29
|
+
return matched;
|
|
30
|
+
});
|
|
31
|
+
const {tokens, cells} = markdownTableSources(body, owner, {}, lines);
|
|
32
|
+
const readInline = (children, content, rawOffset) => {
|
|
33
|
+
for (const child of children || []) {
|
|
34
|
+
const span = observed.get(child);
|
|
35
|
+
if (!span || span.source !== content) continue;
|
|
36
|
+
// Inline footnotes are moved to a generated footer without line maps. Their parser-owned
|
|
37
|
+
// child tokens still stand inside the original ^[...] carrier, not in that footer.
|
|
38
|
+
if (span.footnote?.tokens) {
|
|
39
|
+
readInline(span.footnote.tokens, span.footnote.content, pos => rawOffset(span.start + 2 + pos));
|
|
40
|
+
} else if (child.type !== 'footnote_ref') {
|
|
41
|
+
const start = rawOffset(span.start), end = rawOffset(span.end);
|
|
42
|
+
const style = child.meta?.typography;
|
|
43
|
+
if (style && child.nesting === 1) openedStyles.set(style, {start, innerStart: end, style: style.style});
|
|
44
|
+
else if (style && child.nesting === -1 && openedStyles.has(style)) {
|
|
45
|
+
typography.push({...openedStyles.get(style), innerEnd: start, end});
|
|
46
|
+
openedStyles.delete(style);
|
|
47
|
+
}
|
|
48
|
+
if (span.comment) comments.set(start, end);
|
|
49
|
+
else add(start, end);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
for (const token of tokens) {
|
|
54
|
+
if (token.map && ['fence', 'code_block', 'html_block', 'reference_definition'].includes(token.type)) {
|
|
55
|
+
const start = lineAt(token.map[0]), end = lineAt(token.map[1]);
|
|
56
|
+
add(start, end);
|
|
57
|
+
// A comment-carrier block can contain several adjacent comments. Keep each carrier,
|
|
58
|
+
// including a closer surviving damage to its opener. Never promote nested comments or
|
|
59
|
+
// comment-shaped text in a raw tagged HTML block (pre, script, div, ...).
|
|
60
|
+
if (token.type === 'html_block' && /^\s*<!--/.test(token.content)) {
|
|
61
|
+
let at = source.indexOf('<!--', start);
|
|
62
|
+
while (at >= start && at < end) {
|
|
63
|
+
const close = source.indexOf('-->', at + 4);
|
|
64
|
+
const stop = close >= 0 && close + 3 <= end ? close + 3 : end;
|
|
65
|
+
comments.set(at, stop);
|
|
66
|
+
at = source.indexOf('<!--', stop);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (token.type !== 'inline' || !token.content) continue;
|
|
72
|
+
let rawOffset;
|
|
73
|
+
if (token.map) {
|
|
74
|
+
const offsets = [], starts = [], rows = token.content.split('\n');
|
|
75
|
+
let local = 0;
|
|
76
|
+
for (let i = 0; i < rows.length; i++) {
|
|
77
|
+
const start = lineAt(token.map[0] + i), end = lineAt(token.map[0] + i + 1);
|
|
78
|
+
const at = source.slice(start, end).replace(/\0/g, '\ufffd').indexOf(rows[i]);
|
|
79
|
+
if (at < 0) throw new Error('Markdown inline source mapping failed');
|
|
80
|
+
starts.push(local); offsets.push(start + at); local += rows[i].length + 1;
|
|
81
|
+
}
|
|
82
|
+
rawOffset = pos => {
|
|
83
|
+
let row = starts.length - 1;
|
|
84
|
+
while (row > 0 && starts[row] > pos) row--;
|
|
85
|
+
return offsets[row] + pos - starts[row];
|
|
86
|
+
};
|
|
87
|
+
} else if (cells.has(token)) {
|
|
88
|
+
rawOffset = cells.get(token);
|
|
89
|
+
} else continue;
|
|
90
|
+
readInline(token.children, token.content, rawOffset);
|
|
91
|
+
}
|
|
92
|
+
literal.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
93
|
+
const merged = [];
|
|
94
|
+
for (const range of literal) {
|
|
95
|
+
const last = merged.at(-1);
|
|
96
|
+
if (last && range.start <= last.end) last.end = Math.max(last.end, range.end);
|
|
97
|
+
else merged.push({...range});
|
|
98
|
+
}
|
|
99
|
+
const isText = position => {
|
|
100
|
+
if (comments.has(position)) return false;
|
|
101
|
+
let low = 0, high = merged.length;
|
|
102
|
+
while (low < high) { const mid = (low + high) >> 1; if (merged[mid].end <= position) low = mid + 1; else high = mid; }
|
|
103
|
+
return low < merged.length && merged[low].start <= position;
|
|
104
|
+
};
|
|
105
|
+
return {isText, isComment: (start, end) => comments.get(start) === end, typography: typography.sort((a, b) => a.start - b.start)};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Capture only table-cell provenance while the parser still owns container-adjusted row
|
|
109
|
+
// bounds. Looking for cell text in a whole row aliases literal pipes and quote/list markers.
|
|
110
|
+
export function markdownTableSources(source, parser, environment, lineStarts = null) {
|
|
111
|
+
const cells = new WeakMap();
|
|
112
|
+
if (!source.includes('|')) return {tokens: parser.parse(source, environment), cells};
|
|
113
|
+
const lines = lineStarts || [0];
|
|
114
|
+
if (!lineStarts) for (const row of source.matchAll(/\r\n?|\n/g)) lines.push(row.index + row[0].length);
|
|
115
|
+
const owner = Object.create(parser), block = Object.create(parser.block), ruler = Object.create(parser.block.ruler);
|
|
116
|
+
owner.block = block; block.ruler = ruler;
|
|
117
|
+
ruler.getRules = chain => parser.block.ruler.getRules(chain).map(rule => (state, start, end, silent) => {
|
|
118
|
+
const first = state.tokens.length, matched = rule(state, start, end, silent);
|
|
119
|
+
if (matched && !silent && state.tokens[first]?.type === 'table_open') {
|
|
120
|
+
let cell;
|
|
121
|
+
for (let at = first; at < state.tokens.length; at++) {
|
|
122
|
+
const token = state.tokens[at];
|
|
123
|
+
if (token.type === 'tr_open') {
|
|
124
|
+
const row = token.map[0], from = state.bMarks[row] + state.tShift[row];
|
|
125
|
+
const line = state.src.lastIndexOf('\n', from - 1) + 1;
|
|
126
|
+
cell = tableSource(state.src.slice(from, state.eMarks[row]), lines[row] + from - line);
|
|
127
|
+
} else if (token.type === 'inline' && cell) cells.set(token, cell(token.content));
|
|
128
|
+
else if (token.type === 'tr_close') cell = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return matched;
|
|
132
|
+
});
|
|
133
|
+
return {tokens: owner.parse(source, environment), cells};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function tableSource(row, base) {
|
|
137
|
+
const trim = row.trimStart();
|
|
138
|
+
base += row.length - trim.length; row = trim.trimEnd();
|
|
139
|
+
const offsets = [], stops = [];
|
|
140
|
+
let text = '', cursor = 0;
|
|
141
|
+
for (let at = 0; at < row.length; at++) {
|
|
142
|
+
if (row[at] === '\\' && row[at + 1] === '|') at++;
|
|
143
|
+
else if (row[at] === '|') stops.push(text.length);
|
|
144
|
+
offsets.push(base + at); text += row[at];
|
|
145
|
+
}
|
|
146
|
+
offsets.push(base + row.length);
|
|
147
|
+
let next = stops[0] === 0 ? 1 : 0;
|
|
148
|
+
if (next) cursor = 1;
|
|
149
|
+
if (stops.at(-1) !== text.length - 1) stops.push(text.length);
|
|
150
|
+
return content => {
|
|
151
|
+
const stop = stops[next++] ?? text.length, raw = text.slice(cursor, stop);
|
|
152
|
+
const start = Math.min(text.length, cursor + raw.length - raw.trimStart().length);
|
|
153
|
+
if (raw.trim() !== content) throw new Error('Markdown table source mapping failed');
|
|
154
|
+
cursor = stop + 1;
|
|
155
|
+
return pos => offsets[start + pos];
|
|
156
|
+
};
|
|
157
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rapier-markdown-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.3",
|
|
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",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"./layout": "./dist/kit/layout.mjs",
|
|
44
44
|
"./marks": "./dist/kit/marks.mjs",
|
|
45
45
|
"./model": "./dist/kit/model.mjs",
|
|
46
|
+
"./typography": "./dist/kit/typography.mjs",
|
|
46
47
|
"./will": "./dist/kit/will.mjs",
|
|
47
48
|
"./assets": "./dist/kit/assets.mjs",
|
|
48
49
|
"./style.css": "./style.css",
|