@illusions-lab/mdi 2.0.25 → 2.0.26
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 +29 -0
- package/dist/{chunk-GMS5SFZO.js → chunk-SC6NZVRY.js} +277 -8
- package/dist/index.cjs +281 -7
- package/dist/index.d.cts +69 -1
- package/dist/index.d.ts +69 -1
- package/dist/index.js +11 -1
- package/dist/internal/mdast.js +1 -1
- package/dist/node.cjs +2 -2
- package/dist/node.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -217,3 +217,32 @@ executable syntax authority is `mdi-core`.
|
|
|
217
217
|
- [Document IR and diagnostics](https://mdi.illusions.app/core/document-ir/)
|
|
218
218
|
- [Rendering model](https://mdi.illusions.app/core/rendering/)
|
|
219
219
|
- [JavaScript documentation](https://mdi.illusions.app/bindings/javascript/)
|
|
220
|
+
|
|
221
|
+
### Automatic two-line notes
|
|
222
|
+
|
|
223
|
+
`layoutMdiWarichu(children, capacity)` remains available. To account for the
|
|
224
|
+
space before a note, pass `{ firstCapacity, continuationCapacity }` instead.
|
|
225
|
+
Both capacities use half-em units at the note's 50% font size. Rust returns
|
|
226
|
+
`lines`, `widths`, `overflow`, `hardBreakAfter`, `html`, and `sources`. Source
|
|
227
|
+
paths address the original inline children; byte offsets are UTF-8 leaf
|
|
228
|
+
boundaries, and `group` identifies indivisible units. Repeated text must be
|
|
229
|
+
mapped by these positions, never by searching its value.
|
|
230
|
+
|
|
231
|
+
For read-only rendered HTML, initialize MDI and call
|
|
232
|
+
`attachMdiWarichuLayout(container)`. The controller exposes `configure()`,
|
|
233
|
+
`settled({ timeoutMs, signal })`, and `dispose()`. Resize, font, and ancestor
|
|
234
|
+
style changes trigger presentation updates. Editors should use the layout
|
|
235
|
+
result with their own selection-preserving presentation layer.
|
|
236
|
+
|
|
237
|
+
`settleMdiPrintLayout(evaluate, { timeoutMs, signal, page: prepared.page })` works with a host's
|
|
238
|
+
existing hidden print document. Pass the resolved `page` from `prepareChromiumPrintProfile` so layout uses the paper's printable dimensions instead of the host window's viewport. Supply `code => page.evaluate(code)` for
|
|
239
|
+
Playwright or `code => webContents.executeJavaScript(code)` for Electron.
|
|
240
|
+
The helper waits for fonts and a stable Rust layout; timeout, cancellation,
|
|
241
|
+
and nonconvergence reject the promise and must prevent printing.
|
|
242
|
+
|
|
243
|
+
The adapter does not change canonical MDI or insert generated `[[br]]`.
|
|
244
|
+
No-script HTML/EPUB retain precomputed two-line spans. Their reading system
|
|
245
|
+
may reflow differently; proportional-font balance is an estimate, and this
|
|
246
|
+
API does not imply testing in Word or every EPUB reader.
|
|
247
|
+
|
|
248
|
+
Browser measurement accounts for actual rendered advances and inherited text insets; Rust still owns every split. This retains configured tracking while avoiding clipped notes and Chromium shrink-to-fit.
|
|
@@ -1,6 +1,261 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import * as mdiCore from "@illusions-lab/mdi-core";
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
|
+
|
|
5
|
+
// src/warichu-browser.ts
|
|
6
|
+
function measureMdiWarichu(container = document.body, affected, minimumUnits) {
|
|
7
|
+
return Array.from(container.querySelectorAll("[data-mdi-warichu-source]")).flatMap((note, index) => {
|
|
8
|
+
if (note.parentElement?.closest("[data-mdi-warichu-source]")) return [];
|
|
9
|
+
if (affected && !affected.some((element) => element.contains(note))) return [];
|
|
10
|
+
const paragraph = note.closest("p,li,td,th,h1,h2,h3,h4,h5,h6") ?? note.parentElement ?? container;
|
|
11
|
+
const view = container.ownerDocument.defaultView;
|
|
12
|
+
const style = view.getComputedStyle(paragraph);
|
|
13
|
+
const vertical = style.writingMode.startsWith("vertical");
|
|
14
|
+
const box = paragraph.getBoundingClientRect();
|
|
15
|
+
const range = container.ownerDocument.createRange();
|
|
16
|
+
range.selectNodeContents(paragraph);
|
|
17
|
+
range.setEndBefore(note);
|
|
18
|
+
const rects = Array.from(range.getClientRects());
|
|
19
|
+
const previous = rects.at(-1);
|
|
20
|
+
const size = parseFloat(view.getComputedStyle(note).fontSize) || parseFloat(style.fontSize) / 2;
|
|
21
|
+
const paddingStart = parseFloat(style.paddingInlineStart) || 0;
|
|
22
|
+
const paddingEnd = parseFloat(style.paddingInlineEnd) || 0;
|
|
23
|
+
const full = (vertical ? paragraph.clientHeight : paragraph.clientWidth) - paddingStart - paddingEnd;
|
|
24
|
+
const scale = (vertical ? box.height / paragraph.offsetHeight : box.width / paragraph.offsetWidth) || 1;
|
|
25
|
+
let unit = Math.max(size / 2, minimumUnits?.[index] ?? 0);
|
|
26
|
+
let inset = 0;
|
|
27
|
+
for (const line of note.querySelectorAll(":scope > .mdi-warichu-fragment > .mdi-warichu-line[data-mdi-width]")) {
|
|
28
|
+
const width = Number(line.dataset.mdiWidth);
|
|
29
|
+
if (!(width > 0)) continue;
|
|
30
|
+
const contents = container.ownerDocument.createRange();
|
|
31
|
+
contents.selectNodeContents(line);
|
|
32
|
+
const bounds = contents.getBoundingClientRect();
|
|
33
|
+
const lineBox = line.getBoundingClientRect();
|
|
34
|
+
inset = Math.max(inset, (vertical ? bounds.top - lineBox.top : style.direction === "rtl" ? lineBox.right - bounds.right : bounds.left - lineBox.left) / scale);
|
|
35
|
+
unit = Math.max(unit, (vertical ? bounds.height : bounds.width) / scale / width);
|
|
36
|
+
}
|
|
37
|
+
const remaining = !previous ? full : vertical ? (box.bottom - previous.bottom) / scale - paddingEnd : style.direction === "rtl" ? (previous.left - box.left) / scale - paddingStart : (box.right - previous.right) / scale - paddingEnd;
|
|
38
|
+
return [{ index, unit, children: JSON.parse(note.dataset.mdiWarichuSource), options: { firstCapacity: Math.max(1, Math.floor(((remaining <= 0 ? full : Math.min(full, remaining)) - inset) / unit)), continuationCapacity: Math.max(1, Math.floor((full - inset) / unit)) } }];
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function layoutMeasuredMdiWarichu(measurement) {
|
|
42
|
+
const { children, options } = measurement;
|
|
43
|
+
const fragments = layoutMdiWarichu(children, options);
|
|
44
|
+
const first = fragments[0];
|
|
45
|
+
return first?.overflow && options.firstCapacity < options.continuationCapacity && Math.max(...first.widths) <= options.continuationCapacity ? layoutMdiWarichu(children, { ...options, firstCapacity: options.continuationCapacity }) : fragments;
|
|
46
|
+
}
|
|
47
|
+
function applyMdiWarichu(updates, container = document.body) {
|
|
48
|
+
const notes = container.querySelectorAll("[data-mdi-warichu-source]");
|
|
49
|
+
let changed = false;
|
|
50
|
+
for (const { index, fragments } of updates) {
|
|
51
|
+
const note = notes[index];
|
|
52
|
+
if (!note) continue;
|
|
53
|
+
const html = fragments.map((fragment) => `<span class="mdi-warichu-fragment" style="display:inline-flex;flex-direction:column;vertical-align:middle;text-align:start"${fragment.overflow ? ' data-mdi-overflow="true"' : ""}>${fragment.html.map((line, row) => `<span class="mdi-warichu-line" data-mdi-width="${fragment.widths[row]}" style="display:block;white-space:nowrap;min-block-size:1em">${line}</span>`).join("")}</span>${fragment.hardBreakAfter ? "<br>" : ""}`).join("");
|
|
54
|
+
if (note.dataset.mdiWarichuLayout === html) continue;
|
|
55
|
+
note.innerHTML = html;
|
|
56
|
+
note.dataset.mdiWarichuLayout = html;
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
return changed;
|
|
60
|
+
}
|
|
61
|
+
function attachMdiWarichuLayout(container) {
|
|
62
|
+
const win = container.ownerDocument.defaultView;
|
|
63
|
+
const realm = win;
|
|
64
|
+
let disposed = false, scheduled = false, pending = false;
|
|
65
|
+
let affected = null;
|
|
66
|
+
let rejectDisposal;
|
|
67
|
+
const disposal = new Promise((_, reject) => {
|
|
68
|
+
rejectDisposal = reject;
|
|
69
|
+
});
|
|
70
|
+
void disposal.catch(() => {
|
|
71
|
+
});
|
|
72
|
+
let ready = Promise.resolve();
|
|
73
|
+
const publish = () => {
|
|
74
|
+
win.__mdiWarichuLayoutReady = ready;
|
|
75
|
+
void ready.catch(() => {
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const frame = async () => {
|
|
79
|
+
let id = 0;
|
|
80
|
+
try {
|
|
81
|
+
await Promise.race([new Promise((resolve) => {
|
|
82
|
+
id = win.requestAnimationFrame(() => resolve());
|
|
83
|
+
}), disposal]);
|
|
84
|
+
} finally {
|
|
85
|
+
win.cancelAnimationFrame?.(id);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const observe = () => mutation.observe(container.ownerDocument.documentElement, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["style", "class", "dir", "data-mdi-warichu-source"] });
|
|
89
|
+
const mutation = new realm.MutationObserver((records) => {
|
|
90
|
+
for (const record of records) {
|
|
91
|
+
if (!container.contains(record.target) && !record.target.contains(container)) continue;
|
|
92
|
+
const element = record.target.nodeType === 1 ? record.target : record.target.parentElement;
|
|
93
|
+
const paragraph = element?.closest("p,li,td,th,h1,h2,h3,h4,h5,h6");
|
|
94
|
+
invalidate(paragraph && container.contains(paragraph) ? paragraph : void 0);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
const configure = () => invalidate();
|
|
98
|
+
const invalidate = (paragraph) => {
|
|
99
|
+
if (paragraph) affected?.add(paragraph);
|
|
100
|
+
else affected = null;
|
|
101
|
+
if (disposed) return;
|
|
102
|
+
pending = true;
|
|
103
|
+
if (scheduled) return;
|
|
104
|
+
scheduled = true;
|
|
105
|
+
ready = (async () => {
|
|
106
|
+
try {
|
|
107
|
+
do {
|
|
108
|
+
syncObservedParagraphs();
|
|
109
|
+
pending = false;
|
|
110
|
+
const batch = affected === null ? void 0 : Array.from(affected);
|
|
111
|
+
affected = /* @__PURE__ */ new Set();
|
|
112
|
+
await Promise.race([container.ownerDocument.fonts.ready, disposal]);
|
|
113
|
+
await frame();
|
|
114
|
+
let stable = false;
|
|
115
|
+
const minimumUnits = {};
|
|
116
|
+
for (let pass = 0; pass < 12; pass++) {
|
|
117
|
+
if (disposed) return;
|
|
118
|
+
const updates = measureMdiWarichu(container, batch, minimumUnits).map((m) => {
|
|
119
|
+
if (m.unit) minimumUnits[m.index] = m.unit;
|
|
120
|
+
return { index: m.index, fragments: layoutMeasuredMdiWarichu(m) };
|
|
121
|
+
});
|
|
122
|
+
mutation.disconnect();
|
|
123
|
+
let changed;
|
|
124
|
+
try {
|
|
125
|
+
changed = applyMdiWarichu(updates, container);
|
|
126
|
+
} finally {
|
|
127
|
+
observe();
|
|
128
|
+
}
|
|
129
|
+
await frame();
|
|
130
|
+
if (!changed) {
|
|
131
|
+
stable = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!stable) throw new Error("Warichu layout did not stabilize");
|
|
136
|
+
} while (pending && !disposed);
|
|
137
|
+
} finally {
|
|
138
|
+
scheduled = false;
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
publish();
|
|
142
|
+
};
|
|
143
|
+
const resize = new realm.ResizeObserver((entries) => {
|
|
144
|
+
for (const entry of entries) invalidate(entry.target === container ? void 0 : entry.target);
|
|
145
|
+
});
|
|
146
|
+
resize.observe(container);
|
|
147
|
+
const observed = /* @__PURE__ */ new Set();
|
|
148
|
+
const syncObservedParagraphs = () => {
|
|
149
|
+
const paragraphs = new Set(container.querySelectorAll("p,li,td,th,h1,h2,h3,h4,h5,h6"));
|
|
150
|
+
for (const paragraph of observed) if (!paragraphs.has(paragraph)) {
|
|
151
|
+
resize.unobserve(paragraph);
|
|
152
|
+
observed.delete(paragraph);
|
|
153
|
+
}
|
|
154
|
+
for (const paragraph of paragraphs) if (!observed.has(paragraph)) {
|
|
155
|
+
resize.observe(paragraph);
|
|
156
|
+
observed.add(paragraph);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
syncObservedParagraphs();
|
|
160
|
+
observe();
|
|
161
|
+
container.ownerDocument.fonts.addEventListener("loadingdone", configure);
|
|
162
|
+
win.addEventListener("resize", configure);
|
|
163
|
+
win.visualViewport?.addEventListener("resize", configure);
|
|
164
|
+
configure();
|
|
165
|
+
return { configure, settled: ({ timeoutMs = 1e4, signal } = {}) => new Promise((resolve, reject) => {
|
|
166
|
+
if (signal?.aborted) {
|
|
167
|
+
reject(signal.reason ?? new Error("Warichu layout cancelled"));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const finish = (error) => {
|
|
171
|
+
clearTimeout(timer);
|
|
172
|
+
signal?.removeEventListener("abort", abort);
|
|
173
|
+
error ? reject(error) : resolve();
|
|
174
|
+
};
|
|
175
|
+
const abort = () => finish(signal?.reason ?? new Error("Warichu layout cancelled"));
|
|
176
|
+
const timer = setTimeout(() => finish(new Error("Warichu layout timed out")), timeoutMs);
|
|
177
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
178
|
+
const latest = async () => {
|
|
179
|
+
let current;
|
|
180
|
+
do {
|
|
181
|
+
current = ready;
|
|
182
|
+
await Promise.race([current, disposal]);
|
|
183
|
+
} while (current !== ready);
|
|
184
|
+
};
|
|
185
|
+
latest().then(() => finish(), finish);
|
|
186
|
+
}), dispose: () => {
|
|
187
|
+
disposed = true;
|
|
188
|
+
rejectDisposal(new Error("Warichu layout disposed"));
|
|
189
|
+
resize.disconnect();
|
|
190
|
+
mutation.disconnect();
|
|
191
|
+
container.ownerDocument.fonts.removeEventListener("loadingdone", configure);
|
|
192
|
+
win.removeEventListener("resize", configure);
|
|
193
|
+
win.visualViewport?.removeEventListener("resize", configure);
|
|
194
|
+
} };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/warichu-print.ts
|
|
198
|
+
async function settleMdiPrintLayout(evaluate, { timeoutMs = 1e4, signal, page } = {}) {
|
|
199
|
+
let contentWidthMm, contentHeightMm;
|
|
200
|
+
if (page) {
|
|
201
|
+
contentWidthMm = page.widthMm - page.marginsMm.left - page.marginsMm.right;
|
|
202
|
+
contentHeightMm = page.heightMm - page.marginsMm.top - page.marginsMm.bottom;
|
|
203
|
+
if (!Number.isFinite(contentWidthMm) || !Number.isFinite(contentHeightMm) || contentWidthMm <= 0 || contentHeightMm <= 0) {
|
|
204
|
+
throw new RangeError("MDI print page must have positive finite printable dimensions");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let timer;
|
|
208
|
+
let abort = () => {
|
|
209
|
+
};
|
|
210
|
+
let stopped = false;
|
|
211
|
+
const failure = new Promise((_, reject) => {
|
|
212
|
+
abort = () => {
|
|
213
|
+
stopped = true;
|
|
214
|
+
reject(signal?.reason ?? new Error("MDI print layout cancelled"));
|
|
215
|
+
};
|
|
216
|
+
if (signal?.aborted) {
|
|
217
|
+
abort();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
221
|
+
timer = setTimeout(() => {
|
|
222
|
+
stopped = true;
|
|
223
|
+
reject(new Error("MDI print layout timed out"));
|
|
224
|
+
}, timeoutMs);
|
|
225
|
+
});
|
|
226
|
+
const run = async () => {
|
|
227
|
+
if (stopped) return;
|
|
228
|
+
if (page) {
|
|
229
|
+
await evaluate(`document.body.style.setProperty("inline-size", (getComputedStyle(document.body).writingMode.startsWith("vertical") ? ${contentHeightMm} : ${contentWidthMm}) + "mm")`);
|
|
230
|
+
if (stopped) return;
|
|
231
|
+
}
|
|
232
|
+
await evaluate("document.fonts.ready.then(() => undefined)");
|
|
233
|
+
const minimumUnits = {};
|
|
234
|
+
for (let pass = 0; pass < 12; pass++) {
|
|
235
|
+
if (stopped) return;
|
|
236
|
+
const measurements = await evaluate(`(${measureMdiWarichu.toString()})(document.body,undefined,${JSON.stringify(minimumUnits)})`);
|
|
237
|
+
if (stopped) return;
|
|
238
|
+
const updates = measurements.map((m) => {
|
|
239
|
+
if (m.unit) minimumUnits[m.index] = m.unit;
|
|
240
|
+
return { index: m.index, fragments: layoutMeasuredMdiWarichu(m) };
|
|
241
|
+
});
|
|
242
|
+
const changed = await evaluate(`(${applyMdiWarichu.toString()})(${JSON.stringify(updates)})`);
|
|
243
|
+
if (stopped) return;
|
|
244
|
+
await evaluate("new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))");
|
|
245
|
+
if (!changed) return;
|
|
246
|
+
}
|
|
247
|
+
throw new Error("MDI print layout did not stabilize");
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
await Promise.race([failure, run()]);
|
|
251
|
+
} finally {
|
|
252
|
+
stopped = true;
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
signal?.removeEventListener("abort", abort);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/index.ts
|
|
4
259
|
var {
|
|
5
260
|
getMdiTextBlocksJson,
|
|
6
261
|
resolveMdiSourceSpansJson,
|
|
@@ -229,12 +484,12 @@ async function renderDocxWithProfile(source, profile = {}) {
|
|
|
229
484
|
requireLayoutSystem(normalized);
|
|
230
485
|
return renderDocxWithProfileFromRust(source, JSON.stringify(normalized));
|
|
231
486
|
}
|
|
232
|
-
function toPublicationMdast(
|
|
233
|
-
const children =
|
|
487
|
+
function toPublicationMdast(document2) {
|
|
488
|
+
const children = document2.children.map(toPublicationMdastNode);
|
|
234
489
|
const tree = { type: "root", children };
|
|
235
|
-
if (
|
|
236
|
-
children.unshift({ type: "yaml", value:
|
|
237
|
-
const frontmatter = publicationFrontmatter(
|
|
490
|
+
if (document2.frontmatter) {
|
|
491
|
+
children.unshift({ type: "yaml", value: document2.frontmatter.raw });
|
|
492
|
+
const frontmatter = publicationFrontmatter(document2.frontmatter.raw);
|
|
238
493
|
tree.data ??= {};
|
|
239
494
|
tree.data.frontmatter = frontmatter;
|
|
240
495
|
}
|
|
@@ -338,9 +593,9 @@ function htmlBody(html) {
|
|
|
338
593
|
if (!match) throw new Error("Rust HTML renderer returned a document without a body");
|
|
339
594
|
return match[1];
|
|
340
595
|
}
|
|
341
|
-
function headingsFromDocument(
|
|
596
|
+
function headingsFromDocument(document2) {
|
|
342
597
|
const headings = [];
|
|
343
|
-
visitNodes(
|
|
598
|
+
visitNodes(document2.children, (node) => {
|
|
344
599
|
if (node.type !== "heading" || !isHeadingDepth(node.depth)) return;
|
|
345
600
|
headings.push({
|
|
346
601
|
depth: node.depth,
|
|
@@ -502,8 +757,21 @@ function renderTextFormatWithDiagnostics(source, format, indentPrefix = "") {
|
|
|
502
757
|
return renderWithDiagnostics(source, () => renderTextFormat(source, format, indentPrefix));
|
|
503
758
|
}
|
|
504
759
|
var parseMdiSyntax = parse;
|
|
760
|
+
function layoutMdiWarichu(children, capacity = 40) {
|
|
761
|
+
const options = typeof capacity === "number" ? { firstCapacity: capacity, continuationCapacity: capacity } : capacity;
|
|
762
|
+
for (const value of [options.firstCapacity, options.continuationCapacity]) {
|
|
763
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 4294967295) {
|
|
764
|
+
throw new RangeError("Warichu capacity must be a positive u32 integer");
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return JSON.parse(mdiCore.layoutWarichuOptionsJson(JSON.stringify(children), JSON.stringify(options)));
|
|
768
|
+
}
|
|
505
769
|
|
|
506
770
|
export {
|
|
771
|
+
measureMdiWarichu,
|
|
772
|
+
applyMdiWarichu,
|
|
773
|
+
attachMdiWarichuLayout,
|
|
774
|
+
settleMdiPrintLayout,
|
|
507
775
|
initializeMdi,
|
|
508
776
|
MDI_SPEC_VERSION,
|
|
509
777
|
MDI_IR_VERSION,
|
|
@@ -531,5 +799,6 @@ export {
|
|
|
531
799
|
renderTextWithDiagnostics,
|
|
532
800
|
renderTextFormat,
|
|
533
801
|
renderTextFormatWithDiagnostics,
|
|
534
|
-
parseMdiSyntax
|
|
802
|
+
parseMdiSyntax,
|
|
803
|
+
layoutMdiWarichu
|
|
535
804
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -33,10 +33,14 @@ __export(index_exports, {
|
|
|
33
33
|
MDI_IR_VERSION: () => MDI_IR_VERSION,
|
|
34
34
|
MDI_SPEC_VERSION: () => MDI_SPEC_VERSION,
|
|
35
35
|
MDI_TEXT_PROJECTION_VERSION: () => MDI_TEXT_PROJECTION_VERSION,
|
|
36
|
+
applyMdiWarichu: () => applyMdiWarichu,
|
|
37
|
+
attachMdiWarichuLayout: () => attachMdiWarichuLayout,
|
|
36
38
|
formatMdiTextPosition: () => formatMdiTextPosition,
|
|
37
39
|
formatMdiTextRange: () => formatMdiTextRange,
|
|
38
40
|
getMdiTextBlocks: () => getMdiTextBlocks,
|
|
39
41
|
initializeMdi: () => initializeMdi,
|
|
42
|
+
layoutMdiWarichu: () => layoutMdiWarichu,
|
|
43
|
+
measureMdiWarichu: () => measureMdiWarichu,
|
|
40
44
|
parse: () => parse,
|
|
41
45
|
parseMdiSyntax: () => parseMdiSyntax,
|
|
42
46
|
parseMdiTextPosition: () => parseMdiTextPosition,
|
|
@@ -56,12 +60,268 @@ __export(index_exports, {
|
|
|
56
60
|
resolveMdiSourceSpan: () => resolveMdiSourceSpan,
|
|
57
61
|
resolveMdiSourceSpans: () => resolveMdiSourceSpans,
|
|
58
62
|
serializeMdi: () => serializeMdi,
|
|
63
|
+
settleMdiPrintLayout: () => settleMdiPrintLayout,
|
|
59
64
|
sourceSpansForTextRange: () => sourceSpansForTextRange,
|
|
60
65
|
toPublicationMdast: () => toPublicationMdast
|
|
61
66
|
});
|
|
62
67
|
module.exports = __toCommonJS(index_exports);
|
|
63
68
|
var mdiCore = __toESM(require("@illusions-lab/mdi-core"), 1);
|
|
64
69
|
var import_yaml = require("yaml");
|
|
70
|
+
|
|
71
|
+
// src/warichu-browser.ts
|
|
72
|
+
function measureMdiWarichu(container = document.body, affected, minimumUnits) {
|
|
73
|
+
return Array.from(container.querySelectorAll("[data-mdi-warichu-source]")).flatMap((note, index) => {
|
|
74
|
+
if (note.parentElement?.closest("[data-mdi-warichu-source]")) return [];
|
|
75
|
+
if (affected && !affected.some((element) => element.contains(note))) return [];
|
|
76
|
+
const paragraph = note.closest("p,li,td,th,h1,h2,h3,h4,h5,h6") ?? note.parentElement ?? container;
|
|
77
|
+
const view = container.ownerDocument.defaultView;
|
|
78
|
+
const style = view.getComputedStyle(paragraph);
|
|
79
|
+
const vertical = style.writingMode.startsWith("vertical");
|
|
80
|
+
const box = paragraph.getBoundingClientRect();
|
|
81
|
+
const range = container.ownerDocument.createRange();
|
|
82
|
+
range.selectNodeContents(paragraph);
|
|
83
|
+
range.setEndBefore(note);
|
|
84
|
+
const rects = Array.from(range.getClientRects());
|
|
85
|
+
const previous = rects.at(-1);
|
|
86
|
+
const size = parseFloat(view.getComputedStyle(note).fontSize) || parseFloat(style.fontSize) / 2;
|
|
87
|
+
const paddingStart = parseFloat(style.paddingInlineStart) || 0;
|
|
88
|
+
const paddingEnd = parseFloat(style.paddingInlineEnd) || 0;
|
|
89
|
+
const full = (vertical ? paragraph.clientHeight : paragraph.clientWidth) - paddingStart - paddingEnd;
|
|
90
|
+
const scale = (vertical ? box.height / paragraph.offsetHeight : box.width / paragraph.offsetWidth) || 1;
|
|
91
|
+
let unit = Math.max(size / 2, minimumUnits?.[index] ?? 0);
|
|
92
|
+
let inset = 0;
|
|
93
|
+
for (const line of note.querySelectorAll(":scope > .mdi-warichu-fragment > .mdi-warichu-line[data-mdi-width]")) {
|
|
94
|
+
const width = Number(line.dataset.mdiWidth);
|
|
95
|
+
if (!(width > 0)) continue;
|
|
96
|
+
const contents = container.ownerDocument.createRange();
|
|
97
|
+
contents.selectNodeContents(line);
|
|
98
|
+
const bounds = contents.getBoundingClientRect();
|
|
99
|
+
const lineBox = line.getBoundingClientRect();
|
|
100
|
+
inset = Math.max(inset, (vertical ? bounds.top - lineBox.top : style.direction === "rtl" ? lineBox.right - bounds.right : bounds.left - lineBox.left) / scale);
|
|
101
|
+
unit = Math.max(unit, (vertical ? bounds.height : bounds.width) / scale / width);
|
|
102
|
+
}
|
|
103
|
+
const remaining = !previous ? full : vertical ? (box.bottom - previous.bottom) / scale - paddingEnd : style.direction === "rtl" ? (previous.left - box.left) / scale - paddingStart : (box.right - previous.right) / scale - paddingEnd;
|
|
104
|
+
return [{ index, unit, children: JSON.parse(note.dataset.mdiWarichuSource), options: { firstCapacity: Math.max(1, Math.floor(((remaining <= 0 ? full : Math.min(full, remaining)) - inset) / unit)), continuationCapacity: Math.max(1, Math.floor((full - inset) / unit)) } }];
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function layoutMeasuredMdiWarichu(measurement) {
|
|
108
|
+
const { children, options } = measurement;
|
|
109
|
+
const fragments = layoutMdiWarichu(children, options);
|
|
110
|
+
const first = fragments[0];
|
|
111
|
+
return first?.overflow && options.firstCapacity < options.continuationCapacity && Math.max(...first.widths) <= options.continuationCapacity ? layoutMdiWarichu(children, { ...options, firstCapacity: options.continuationCapacity }) : fragments;
|
|
112
|
+
}
|
|
113
|
+
function applyMdiWarichu(updates, container = document.body) {
|
|
114
|
+
const notes = container.querySelectorAll("[data-mdi-warichu-source]");
|
|
115
|
+
let changed = false;
|
|
116
|
+
for (const { index, fragments } of updates) {
|
|
117
|
+
const note = notes[index];
|
|
118
|
+
if (!note) continue;
|
|
119
|
+
const html = fragments.map((fragment) => `<span class="mdi-warichu-fragment" style="display:inline-flex;flex-direction:column;vertical-align:middle;text-align:start"${fragment.overflow ? ' data-mdi-overflow="true"' : ""}>${fragment.html.map((line, row) => `<span class="mdi-warichu-line" data-mdi-width="${fragment.widths[row]}" style="display:block;white-space:nowrap;min-block-size:1em">${line}</span>`).join("")}</span>${fragment.hardBreakAfter ? "<br>" : ""}`).join("");
|
|
120
|
+
if (note.dataset.mdiWarichuLayout === html) continue;
|
|
121
|
+
note.innerHTML = html;
|
|
122
|
+
note.dataset.mdiWarichuLayout = html;
|
|
123
|
+
changed = true;
|
|
124
|
+
}
|
|
125
|
+
return changed;
|
|
126
|
+
}
|
|
127
|
+
function attachMdiWarichuLayout(container) {
|
|
128
|
+
const win = container.ownerDocument.defaultView;
|
|
129
|
+
const realm = win;
|
|
130
|
+
let disposed = false, scheduled = false, pending = false;
|
|
131
|
+
let affected = null;
|
|
132
|
+
let rejectDisposal;
|
|
133
|
+
const disposal = new Promise((_, reject) => {
|
|
134
|
+
rejectDisposal = reject;
|
|
135
|
+
});
|
|
136
|
+
void disposal.catch(() => {
|
|
137
|
+
});
|
|
138
|
+
let ready = Promise.resolve();
|
|
139
|
+
const publish = () => {
|
|
140
|
+
win.__mdiWarichuLayoutReady = ready;
|
|
141
|
+
void ready.catch(() => {
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const frame = async () => {
|
|
145
|
+
let id = 0;
|
|
146
|
+
try {
|
|
147
|
+
await Promise.race([new Promise((resolve) => {
|
|
148
|
+
id = win.requestAnimationFrame(() => resolve());
|
|
149
|
+
}), disposal]);
|
|
150
|
+
} finally {
|
|
151
|
+
win.cancelAnimationFrame?.(id);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const observe = () => mutation.observe(container.ownerDocument.documentElement, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["style", "class", "dir", "data-mdi-warichu-source"] });
|
|
155
|
+
const mutation = new realm.MutationObserver((records) => {
|
|
156
|
+
for (const record of records) {
|
|
157
|
+
if (!container.contains(record.target) && !record.target.contains(container)) continue;
|
|
158
|
+
const element = record.target.nodeType === 1 ? record.target : record.target.parentElement;
|
|
159
|
+
const paragraph = element?.closest("p,li,td,th,h1,h2,h3,h4,h5,h6");
|
|
160
|
+
invalidate(paragraph && container.contains(paragraph) ? paragraph : void 0);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
const configure = () => invalidate();
|
|
164
|
+
const invalidate = (paragraph) => {
|
|
165
|
+
if (paragraph) affected?.add(paragraph);
|
|
166
|
+
else affected = null;
|
|
167
|
+
if (disposed) return;
|
|
168
|
+
pending = true;
|
|
169
|
+
if (scheduled) return;
|
|
170
|
+
scheduled = true;
|
|
171
|
+
ready = (async () => {
|
|
172
|
+
try {
|
|
173
|
+
do {
|
|
174
|
+
syncObservedParagraphs();
|
|
175
|
+
pending = false;
|
|
176
|
+
const batch = affected === null ? void 0 : Array.from(affected);
|
|
177
|
+
affected = /* @__PURE__ */ new Set();
|
|
178
|
+
await Promise.race([container.ownerDocument.fonts.ready, disposal]);
|
|
179
|
+
await frame();
|
|
180
|
+
let stable = false;
|
|
181
|
+
const minimumUnits = {};
|
|
182
|
+
for (let pass = 0; pass < 12; pass++) {
|
|
183
|
+
if (disposed) return;
|
|
184
|
+
const updates = measureMdiWarichu(container, batch, minimumUnits).map((m) => {
|
|
185
|
+
if (m.unit) minimumUnits[m.index] = m.unit;
|
|
186
|
+
return { index: m.index, fragments: layoutMeasuredMdiWarichu(m) };
|
|
187
|
+
});
|
|
188
|
+
mutation.disconnect();
|
|
189
|
+
let changed;
|
|
190
|
+
try {
|
|
191
|
+
changed = applyMdiWarichu(updates, container);
|
|
192
|
+
} finally {
|
|
193
|
+
observe();
|
|
194
|
+
}
|
|
195
|
+
await frame();
|
|
196
|
+
if (!changed) {
|
|
197
|
+
stable = true;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!stable) throw new Error("Warichu layout did not stabilize");
|
|
202
|
+
} while (pending && !disposed);
|
|
203
|
+
} finally {
|
|
204
|
+
scheduled = false;
|
|
205
|
+
}
|
|
206
|
+
})();
|
|
207
|
+
publish();
|
|
208
|
+
};
|
|
209
|
+
const resize = new realm.ResizeObserver((entries) => {
|
|
210
|
+
for (const entry of entries) invalidate(entry.target === container ? void 0 : entry.target);
|
|
211
|
+
});
|
|
212
|
+
resize.observe(container);
|
|
213
|
+
const observed = /* @__PURE__ */ new Set();
|
|
214
|
+
const syncObservedParagraphs = () => {
|
|
215
|
+
const paragraphs = new Set(container.querySelectorAll("p,li,td,th,h1,h2,h3,h4,h5,h6"));
|
|
216
|
+
for (const paragraph of observed) if (!paragraphs.has(paragraph)) {
|
|
217
|
+
resize.unobserve(paragraph);
|
|
218
|
+
observed.delete(paragraph);
|
|
219
|
+
}
|
|
220
|
+
for (const paragraph of paragraphs) if (!observed.has(paragraph)) {
|
|
221
|
+
resize.observe(paragraph);
|
|
222
|
+
observed.add(paragraph);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
syncObservedParagraphs();
|
|
226
|
+
observe();
|
|
227
|
+
container.ownerDocument.fonts.addEventListener("loadingdone", configure);
|
|
228
|
+
win.addEventListener("resize", configure);
|
|
229
|
+
win.visualViewport?.addEventListener("resize", configure);
|
|
230
|
+
configure();
|
|
231
|
+
return { configure, settled: ({ timeoutMs = 1e4, signal } = {}) => new Promise((resolve, reject) => {
|
|
232
|
+
if (signal?.aborted) {
|
|
233
|
+
reject(signal.reason ?? new Error("Warichu layout cancelled"));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const finish = (error) => {
|
|
237
|
+
clearTimeout(timer);
|
|
238
|
+
signal?.removeEventListener("abort", abort);
|
|
239
|
+
error ? reject(error) : resolve();
|
|
240
|
+
};
|
|
241
|
+
const abort = () => finish(signal?.reason ?? new Error("Warichu layout cancelled"));
|
|
242
|
+
const timer = setTimeout(() => finish(new Error("Warichu layout timed out")), timeoutMs);
|
|
243
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
244
|
+
const latest = async () => {
|
|
245
|
+
let current;
|
|
246
|
+
do {
|
|
247
|
+
current = ready;
|
|
248
|
+
await Promise.race([current, disposal]);
|
|
249
|
+
} while (current !== ready);
|
|
250
|
+
};
|
|
251
|
+
latest().then(() => finish(), finish);
|
|
252
|
+
}), dispose: () => {
|
|
253
|
+
disposed = true;
|
|
254
|
+
rejectDisposal(new Error("Warichu layout disposed"));
|
|
255
|
+
resize.disconnect();
|
|
256
|
+
mutation.disconnect();
|
|
257
|
+
container.ownerDocument.fonts.removeEventListener("loadingdone", configure);
|
|
258
|
+
win.removeEventListener("resize", configure);
|
|
259
|
+
win.visualViewport?.removeEventListener("resize", configure);
|
|
260
|
+
} };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/warichu-print.ts
|
|
264
|
+
async function settleMdiPrintLayout(evaluate, { timeoutMs = 1e4, signal, page } = {}) {
|
|
265
|
+
let contentWidthMm, contentHeightMm;
|
|
266
|
+
if (page) {
|
|
267
|
+
contentWidthMm = page.widthMm - page.marginsMm.left - page.marginsMm.right;
|
|
268
|
+
contentHeightMm = page.heightMm - page.marginsMm.top - page.marginsMm.bottom;
|
|
269
|
+
if (!Number.isFinite(contentWidthMm) || !Number.isFinite(contentHeightMm) || contentWidthMm <= 0 || contentHeightMm <= 0) {
|
|
270
|
+
throw new RangeError("MDI print page must have positive finite printable dimensions");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
let timer;
|
|
274
|
+
let abort = () => {
|
|
275
|
+
};
|
|
276
|
+
let stopped = false;
|
|
277
|
+
const failure = new Promise((_, reject) => {
|
|
278
|
+
abort = () => {
|
|
279
|
+
stopped = true;
|
|
280
|
+
reject(signal?.reason ?? new Error("MDI print layout cancelled"));
|
|
281
|
+
};
|
|
282
|
+
if (signal?.aborted) {
|
|
283
|
+
abort();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
287
|
+
timer = setTimeout(() => {
|
|
288
|
+
stopped = true;
|
|
289
|
+
reject(new Error("MDI print layout timed out"));
|
|
290
|
+
}, timeoutMs);
|
|
291
|
+
});
|
|
292
|
+
const run = async () => {
|
|
293
|
+
if (stopped) return;
|
|
294
|
+
if (page) {
|
|
295
|
+
await evaluate(`document.body.style.setProperty("inline-size", (getComputedStyle(document.body).writingMode.startsWith("vertical") ? ${contentHeightMm} : ${contentWidthMm}) + "mm")`);
|
|
296
|
+
if (stopped) return;
|
|
297
|
+
}
|
|
298
|
+
await evaluate("document.fonts.ready.then(() => undefined)");
|
|
299
|
+
const minimumUnits = {};
|
|
300
|
+
for (let pass = 0; pass < 12; pass++) {
|
|
301
|
+
if (stopped) return;
|
|
302
|
+
const measurements = await evaluate(`(${measureMdiWarichu.toString()})(document.body,undefined,${JSON.stringify(minimumUnits)})`);
|
|
303
|
+
if (stopped) return;
|
|
304
|
+
const updates = measurements.map((m) => {
|
|
305
|
+
if (m.unit) minimumUnits[m.index] = m.unit;
|
|
306
|
+
return { index: m.index, fragments: layoutMeasuredMdiWarichu(m) };
|
|
307
|
+
});
|
|
308
|
+
const changed = await evaluate(`(${applyMdiWarichu.toString()})(${JSON.stringify(updates)})`);
|
|
309
|
+
if (stopped) return;
|
|
310
|
+
await evaluate("new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve())))");
|
|
311
|
+
if (!changed) return;
|
|
312
|
+
}
|
|
313
|
+
throw new Error("MDI print layout did not stabilize");
|
|
314
|
+
};
|
|
315
|
+
try {
|
|
316
|
+
await Promise.race([failure, run()]);
|
|
317
|
+
} finally {
|
|
318
|
+
stopped = true;
|
|
319
|
+
clearTimeout(timer);
|
|
320
|
+
signal?.removeEventListener("abort", abort);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/index.ts
|
|
65
325
|
var {
|
|
66
326
|
getMdiTextBlocksJson,
|
|
67
327
|
resolveMdiSourceSpansJson,
|
|
@@ -290,12 +550,12 @@ async function renderDocxWithProfile(source, profile = {}) {
|
|
|
290
550
|
requireLayoutSystem(normalized);
|
|
291
551
|
return renderDocxWithProfileFromRust(source, JSON.stringify(normalized));
|
|
292
552
|
}
|
|
293
|
-
function toPublicationMdast(
|
|
294
|
-
const children =
|
|
553
|
+
function toPublicationMdast(document2) {
|
|
554
|
+
const children = document2.children.map(toPublicationMdastNode);
|
|
295
555
|
const tree = { type: "root", children };
|
|
296
|
-
if (
|
|
297
|
-
children.unshift({ type: "yaml", value:
|
|
298
|
-
const frontmatter = publicationFrontmatter(
|
|
556
|
+
if (document2.frontmatter) {
|
|
557
|
+
children.unshift({ type: "yaml", value: document2.frontmatter.raw });
|
|
558
|
+
const frontmatter = publicationFrontmatter(document2.frontmatter.raw);
|
|
299
559
|
tree.data ??= {};
|
|
300
560
|
tree.data.frontmatter = frontmatter;
|
|
301
561
|
}
|
|
@@ -399,9 +659,9 @@ function htmlBody(html) {
|
|
|
399
659
|
if (!match) throw new Error("Rust HTML renderer returned a document without a body");
|
|
400
660
|
return match[1];
|
|
401
661
|
}
|
|
402
|
-
function headingsFromDocument(
|
|
662
|
+
function headingsFromDocument(document2) {
|
|
403
663
|
const headings = [];
|
|
404
|
-
visitNodes(
|
|
664
|
+
visitNodes(document2.children, (node) => {
|
|
405
665
|
if (node.type !== "heading" || !isHeadingDepth(node.depth)) return;
|
|
406
666
|
headings.push({
|
|
407
667
|
depth: node.depth,
|
|
@@ -563,15 +823,28 @@ function renderTextFormatWithDiagnostics(source, format, indentPrefix = "") {
|
|
|
563
823
|
return renderWithDiagnostics(source, () => renderTextFormat(source, format, indentPrefix));
|
|
564
824
|
}
|
|
565
825
|
var parseMdiSyntax = parse;
|
|
826
|
+
function layoutMdiWarichu(children, capacity = 40) {
|
|
827
|
+
const options = typeof capacity === "number" ? { firstCapacity: capacity, continuationCapacity: capacity } : capacity;
|
|
828
|
+
for (const value of [options.firstCapacity, options.continuationCapacity]) {
|
|
829
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 4294967295) {
|
|
830
|
+
throw new RangeError("Warichu capacity must be a positive u32 integer");
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return JSON.parse(mdiCore.layoutWarichuOptionsJson(JSON.stringify(children), JSON.stringify(options)));
|
|
834
|
+
}
|
|
566
835
|
// Annotate the CommonJS export names for ESM import in node:
|
|
567
836
|
0 && (module.exports = {
|
|
568
837
|
MDI_IR_VERSION,
|
|
569
838
|
MDI_SPEC_VERSION,
|
|
570
839
|
MDI_TEXT_PROJECTION_VERSION,
|
|
840
|
+
applyMdiWarichu,
|
|
841
|
+
attachMdiWarichuLayout,
|
|
571
842
|
formatMdiTextPosition,
|
|
572
843
|
formatMdiTextRange,
|
|
573
844
|
getMdiTextBlocks,
|
|
574
845
|
initializeMdi,
|
|
846
|
+
layoutMdiWarichu,
|
|
847
|
+
measureMdiWarichu,
|
|
575
848
|
parse,
|
|
576
849
|
parseMdiSyntax,
|
|
577
850
|
parseMdiTextPosition,
|
|
@@ -591,6 +864,7 @@ var parseMdiSyntax = parse;
|
|
|
591
864
|
resolveMdiSourceSpan,
|
|
592
865
|
resolveMdiSourceSpans,
|
|
593
866
|
serializeMdi,
|
|
867
|
+
settleMdiPrintLayout,
|
|
594
868
|
sourceSpansForTextRange,
|
|
595
869
|
toPublicationMdast
|
|
596
870
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,52 @@ import { ExportProfile } from '@illusions-lab/mdi-export-profile';
|
|
|
4
4
|
export { ExportProfile } from '@illusions-lab/mdi-export-profile';
|
|
5
5
|
import { Root } from 'mdast';
|
|
6
6
|
|
|
7
|
+
interface MdiWarichuMeasurement {
|
|
8
|
+
index: number;
|
|
9
|
+
children: Record<string, unknown>[];
|
|
10
|
+
options: MdiWarichuOptions;
|
|
11
|
+
/** Measured CSS pixels per Rust half-em unit for this layout pass. */
|
|
12
|
+
unit?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Self-contained for execution in a browser hosted by Electron or Playwright. */
|
|
15
|
+
declare function measureMdiWarichu(container?: HTMLElement, affected?: readonly HTMLElement[], minimumUnits?: Readonly<Record<number, number>>): MdiWarichuMeasurement[];
|
|
16
|
+
/** Applies Rust-rendered presentation only; never modifies canonical source. */
|
|
17
|
+
declare function applyMdiWarichu(updates: {
|
|
18
|
+
index: number;
|
|
19
|
+
fragments: MdiWarichuFragment[];
|
|
20
|
+
}[], container?: HTMLElement): boolean;
|
|
21
|
+
interface MdiWarichuSettleOptions {
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
interface MdiWarichuLayoutController {
|
|
26
|
+
configure(): void;
|
|
27
|
+
settled(options?: MdiWarichuSettleOptions): Promise<void>;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|
|
30
|
+
/** Attach to read-only Rust-rendered HTML. Editable editors consume layoutMdiWarichu directly. */
|
|
31
|
+
declare function attachMdiWarichuLayout(container: HTMLElement): MdiWarichuLayoutController;
|
|
32
|
+
|
|
33
|
+
/** Execute JavaScript in the print document. Electron and Playwright can supply their existing page. */
|
|
34
|
+
type MdiPrintEvaluate = (javascript: string) => Promise<unknown>;
|
|
35
|
+
/** Physical page dimensions resolved by the upstream Chromium print profile. */
|
|
36
|
+
interface MdiPrintPage {
|
|
37
|
+
widthMm: number;
|
|
38
|
+
heightMm: number;
|
|
39
|
+
marginsMm: {
|
|
40
|
+
top: number;
|
|
41
|
+
right: number;
|
|
42
|
+
bottom: number;
|
|
43
|
+
left: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
interface MdiPrintLayoutOptions extends MdiWarichuSettleOptions {
|
|
47
|
+
/** Pass prepared.page so measurement uses paper space rather than the host window viewport. */
|
|
48
|
+
page?: MdiPrintPage;
|
|
49
|
+
}
|
|
50
|
+
/** Wait for fonts, then measure and apply the shared Rust layout before permitting printing. */
|
|
51
|
+
declare function settleMdiPrintLayout(evaluate: MdiPrintEvaluate, { timeoutMs, signal, page }?: MdiPrintLayoutOptions): Promise<void>;
|
|
52
|
+
|
|
7
53
|
/**
|
|
8
54
|
* Initialize the shared MDI core runtime exactly once.
|
|
9
55
|
*
|
|
@@ -349,5 +395,27 @@ declare function renderTextFormat(source: string, format: MdiTextFormat, indentP
|
|
|
349
395
|
declare function renderTextFormatWithDiagnostics(source: string, format: MdiTextFormat, indentPrefix?: string): MdiRenderResult<string>;
|
|
350
396
|
/** @deprecated Use {@link parse}; it now parses the complete document. */
|
|
351
397
|
declare const parseMdiSyntax: typeof parse;
|
|
398
|
+
/** UTF-8 boundaries in an original inline leaf; paths are relative to children. */
|
|
399
|
+
interface MdiWarichuSource {
|
|
400
|
+
path: number[];
|
|
401
|
+
startUtf8: number;
|
|
402
|
+
endUtf8: number;
|
|
403
|
+
group: number;
|
|
404
|
+
}
|
|
405
|
+
interface MdiWarichuOptions {
|
|
406
|
+
firstCapacity: number;
|
|
407
|
+
continuationCapacity: number;
|
|
408
|
+
}
|
|
409
|
+
/** Presentation-only two-line fragment; generated boundaries never belong in MDI. */
|
|
410
|
+
interface MdiWarichuFragment {
|
|
411
|
+
lines: [Record<string, unknown>[], Record<string, unknown>[]];
|
|
412
|
+
widths: [number, number];
|
|
413
|
+
overflow: boolean;
|
|
414
|
+
hardBreakAfter: boolean;
|
|
415
|
+
sources: [MdiWarichuSource[], MdiWarichuSource[]];
|
|
416
|
+
html: [string, string];
|
|
417
|
+
}
|
|
418
|
+
/** Rust owns splitting. Capacities are half-em units at the note's 50% font size. */
|
|
419
|
+
declare function layoutMdiWarichu(children: readonly Record<string, unknown>[], capacity?: number | MdiWarichuOptions): MdiWarichuFragment[];
|
|
352
420
|
|
|
353
|
-
export { MDI_IR_VERSION, MDI_SPEC_VERSION, MDI_TEXT_PROJECTION_VERSION, type MdiAnnotationSourceMap, type MdiDiagnostic, type MdiDocument, type MdiDocxExportProfile, type MdiEpubExportOptions, type MdiFrontmatter, type MdiHeading, type MdiHtmlRenderOptions, type MdiNode, type MdiParserCapabilities, type MdiPublicationFrontmatter, type MdiPublicationRoot, type MdiRenderResult, type MdiRubyReading, type MdiSourceSpan, type MdiSourceSpanAnnotationMatch, type MdiSourceSpanBlockTextMatch, type MdiSourceSpanCoverage, type MdiSourceSpanRelation, type MdiSourceSpanTextMatch, type MdiSourceSpanTextResolution, type MdiSyntaxDocument, type MdiSyntaxParseResult, type MdiTextAnnotation, type MdiTextBlock, type MdiTextBlocksResult, type MdiTextFormat, type MdiTextPosition, type MdiTextPositionValue, type MdiTextRange, type MdiTextSourceMap, type MdiTextSourceRun, formatMdiTextPosition, formatMdiTextRange, getMdiTextBlocks, initializeMdi, parse, parseMdiSyntax, parseMdiTextPosition, prepareRender, renderDocx, renderDocxWithDiagnostics, renderDocxWithProfile, renderEpub, renderEpubWithDiagnostics, renderEpubWithProfile, renderHtml, renderHtmlWithDiagnostics, renderText, renderTextFormat, renderTextFormatWithDiagnostics, renderTextWithDiagnostics, resolveMdiSourceSpan, resolveMdiSourceSpans, serializeMdi, sourceSpansForTextRange, toPublicationMdast };
|
|
421
|
+
export { MDI_IR_VERSION, MDI_SPEC_VERSION, MDI_TEXT_PROJECTION_VERSION, type MdiAnnotationSourceMap, type MdiDiagnostic, type MdiDocument, type MdiDocxExportProfile, type MdiEpubExportOptions, type MdiFrontmatter, type MdiHeading, type MdiHtmlRenderOptions, type MdiNode, type MdiParserCapabilities, type MdiPrintEvaluate, type MdiPrintLayoutOptions, type MdiPrintPage, type MdiPublicationFrontmatter, type MdiPublicationRoot, type MdiRenderResult, type MdiRubyReading, type MdiSourceSpan, type MdiSourceSpanAnnotationMatch, type MdiSourceSpanBlockTextMatch, type MdiSourceSpanCoverage, type MdiSourceSpanRelation, type MdiSourceSpanTextMatch, type MdiSourceSpanTextResolution, type MdiSyntaxDocument, type MdiSyntaxParseResult, type MdiTextAnnotation, type MdiTextBlock, type MdiTextBlocksResult, type MdiTextFormat, type MdiTextPosition, type MdiTextPositionValue, type MdiTextRange, type MdiTextSourceMap, type MdiTextSourceRun, type MdiWarichuFragment, type MdiWarichuLayoutController, type MdiWarichuOptions, type MdiWarichuSettleOptions, type MdiWarichuSource, applyMdiWarichu, attachMdiWarichuLayout, formatMdiTextPosition, formatMdiTextRange, getMdiTextBlocks, initializeMdi, layoutMdiWarichu, measureMdiWarichu, parse, parseMdiSyntax, parseMdiTextPosition, prepareRender, renderDocx, renderDocxWithDiagnostics, renderDocxWithProfile, renderEpub, renderEpubWithDiagnostics, renderEpubWithProfile, renderHtml, renderHtmlWithDiagnostics, renderText, renderTextFormat, renderTextFormatWithDiagnostics, renderTextWithDiagnostics, resolveMdiSourceSpan, resolveMdiSourceSpans, serializeMdi, settleMdiPrintLayout, sourceSpansForTextRange, toPublicationMdast };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,52 @@ import { ExportProfile } from '@illusions-lab/mdi-export-profile';
|
|
|
4
4
|
export { ExportProfile } from '@illusions-lab/mdi-export-profile';
|
|
5
5
|
import { Root } from 'mdast';
|
|
6
6
|
|
|
7
|
+
interface MdiWarichuMeasurement {
|
|
8
|
+
index: number;
|
|
9
|
+
children: Record<string, unknown>[];
|
|
10
|
+
options: MdiWarichuOptions;
|
|
11
|
+
/** Measured CSS pixels per Rust half-em unit for this layout pass. */
|
|
12
|
+
unit?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Self-contained for execution in a browser hosted by Electron or Playwright. */
|
|
15
|
+
declare function measureMdiWarichu(container?: HTMLElement, affected?: readonly HTMLElement[], minimumUnits?: Readonly<Record<number, number>>): MdiWarichuMeasurement[];
|
|
16
|
+
/** Applies Rust-rendered presentation only; never modifies canonical source. */
|
|
17
|
+
declare function applyMdiWarichu(updates: {
|
|
18
|
+
index: number;
|
|
19
|
+
fragments: MdiWarichuFragment[];
|
|
20
|
+
}[], container?: HTMLElement): boolean;
|
|
21
|
+
interface MdiWarichuSettleOptions {
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
interface MdiWarichuLayoutController {
|
|
26
|
+
configure(): void;
|
|
27
|
+
settled(options?: MdiWarichuSettleOptions): Promise<void>;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|
|
30
|
+
/** Attach to read-only Rust-rendered HTML. Editable editors consume layoutMdiWarichu directly. */
|
|
31
|
+
declare function attachMdiWarichuLayout(container: HTMLElement): MdiWarichuLayoutController;
|
|
32
|
+
|
|
33
|
+
/** Execute JavaScript in the print document. Electron and Playwright can supply their existing page. */
|
|
34
|
+
type MdiPrintEvaluate = (javascript: string) => Promise<unknown>;
|
|
35
|
+
/** Physical page dimensions resolved by the upstream Chromium print profile. */
|
|
36
|
+
interface MdiPrintPage {
|
|
37
|
+
widthMm: number;
|
|
38
|
+
heightMm: number;
|
|
39
|
+
marginsMm: {
|
|
40
|
+
top: number;
|
|
41
|
+
right: number;
|
|
42
|
+
bottom: number;
|
|
43
|
+
left: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
interface MdiPrintLayoutOptions extends MdiWarichuSettleOptions {
|
|
47
|
+
/** Pass prepared.page so measurement uses paper space rather than the host window viewport. */
|
|
48
|
+
page?: MdiPrintPage;
|
|
49
|
+
}
|
|
50
|
+
/** Wait for fonts, then measure and apply the shared Rust layout before permitting printing. */
|
|
51
|
+
declare function settleMdiPrintLayout(evaluate: MdiPrintEvaluate, { timeoutMs, signal, page }?: MdiPrintLayoutOptions): Promise<void>;
|
|
52
|
+
|
|
7
53
|
/**
|
|
8
54
|
* Initialize the shared MDI core runtime exactly once.
|
|
9
55
|
*
|
|
@@ -349,5 +395,27 @@ declare function renderTextFormat(source: string, format: MdiTextFormat, indentP
|
|
|
349
395
|
declare function renderTextFormatWithDiagnostics(source: string, format: MdiTextFormat, indentPrefix?: string): MdiRenderResult<string>;
|
|
350
396
|
/** @deprecated Use {@link parse}; it now parses the complete document. */
|
|
351
397
|
declare const parseMdiSyntax: typeof parse;
|
|
398
|
+
/** UTF-8 boundaries in an original inline leaf; paths are relative to children. */
|
|
399
|
+
interface MdiWarichuSource {
|
|
400
|
+
path: number[];
|
|
401
|
+
startUtf8: number;
|
|
402
|
+
endUtf8: number;
|
|
403
|
+
group: number;
|
|
404
|
+
}
|
|
405
|
+
interface MdiWarichuOptions {
|
|
406
|
+
firstCapacity: number;
|
|
407
|
+
continuationCapacity: number;
|
|
408
|
+
}
|
|
409
|
+
/** Presentation-only two-line fragment; generated boundaries never belong in MDI. */
|
|
410
|
+
interface MdiWarichuFragment {
|
|
411
|
+
lines: [Record<string, unknown>[], Record<string, unknown>[]];
|
|
412
|
+
widths: [number, number];
|
|
413
|
+
overflow: boolean;
|
|
414
|
+
hardBreakAfter: boolean;
|
|
415
|
+
sources: [MdiWarichuSource[], MdiWarichuSource[]];
|
|
416
|
+
html: [string, string];
|
|
417
|
+
}
|
|
418
|
+
/** Rust owns splitting. Capacities are half-em units at the note's 50% font size. */
|
|
419
|
+
declare function layoutMdiWarichu(children: readonly Record<string, unknown>[], capacity?: number | MdiWarichuOptions): MdiWarichuFragment[];
|
|
352
420
|
|
|
353
|
-
export { MDI_IR_VERSION, MDI_SPEC_VERSION, MDI_TEXT_PROJECTION_VERSION, type MdiAnnotationSourceMap, type MdiDiagnostic, type MdiDocument, type MdiDocxExportProfile, type MdiEpubExportOptions, type MdiFrontmatter, type MdiHeading, type MdiHtmlRenderOptions, type MdiNode, type MdiParserCapabilities, type MdiPublicationFrontmatter, type MdiPublicationRoot, type MdiRenderResult, type MdiRubyReading, type MdiSourceSpan, type MdiSourceSpanAnnotationMatch, type MdiSourceSpanBlockTextMatch, type MdiSourceSpanCoverage, type MdiSourceSpanRelation, type MdiSourceSpanTextMatch, type MdiSourceSpanTextResolution, type MdiSyntaxDocument, type MdiSyntaxParseResult, type MdiTextAnnotation, type MdiTextBlock, type MdiTextBlocksResult, type MdiTextFormat, type MdiTextPosition, type MdiTextPositionValue, type MdiTextRange, type MdiTextSourceMap, type MdiTextSourceRun, formatMdiTextPosition, formatMdiTextRange, getMdiTextBlocks, initializeMdi, parse, parseMdiSyntax, parseMdiTextPosition, prepareRender, renderDocx, renderDocxWithDiagnostics, renderDocxWithProfile, renderEpub, renderEpubWithDiagnostics, renderEpubWithProfile, renderHtml, renderHtmlWithDiagnostics, renderText, renderTextFormat, renderTextFormatWithDiagnostics, renderTextWithDiagnostics, resolveMdiSourceSpan, resolveMdiSourceSpans, serializeMdi, sourceSpansForTextRange, toPublicationMdast };
|
|
421
|
+
export { MDI_IR_VERSION, MDI_SPEC_VERSION, MDI_TEXT_PROJECTION_VERSION, type MdiAnnotationSourceMap, type MdiDiagnostic, type MdiDocument, type MdiDocxExportProfile, type MdiEpubExportOptions, type MdiFrontmatter, type MdiHeading, type MdiHtmlRenderOptions, type MdiNode, type MdiParserCapabilities, type MdiPrintEvaluate, type MdiPrintLayoutOptions, type MdiPrintPage, type MdiPublicationFrontmatter, type MdiPublicationRoot, type MdiRenderResult, type MdiRubyReading, type MdiSourceSpan, type MdiSourceSpanAnnotationMatch, type MdiSourceSpanBlockTextMatch, type MdiSourceSpanCoverage, type MdiSourceSpanRelation, type MdiSourceSpanTextMatch, type MdiSourceSpanTextResolution, type MdiSyntaxDocument, type MdiSyntaxParseResult, type MdiTextAnnotation, type MdiTextBlock, type MdiTextBlocksResult, type MdiTextFormat, type MdiTextPosition, type MdiTextPositionValue, type MdiTextRange, type MdiTextSourceMap, type MdiTextSourceRun, type MdiWarichuFragment, type MdiWarichuLayoutController, type MdiWarichuOptions, type MdiWarichuSettleOptions, type MdiWarichuSource, applyMdiWarichu, attachMdiWarichuLayout, formatMdiTextPosition, formatMdiTextRange, getMdiTextBlocks, initializeMdi, layoutMdiWarichu, measureMdiWarichu, parse, parseMdiSyntax, parseMdiTextPosition, prepareRender, renderDocx, renderDocxWithDiagnostics, renderDocxWithProfile, renderEpub, renderEpubWithDiagnostics, renderEpubWithProfile, renderHtml, renderHtmlWithDiagnostics, renderText, renderTextFormat, renderTextFormatWithDiagnostics, renderTextWithDiagnostics, resolveMdiSourceSpan, resolveMdiSourceSpans, serializeMdi, settleMdiPrintLayout, sourceSpansForTextRange, toPublicationMdast };
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,14 @@ import {
|
|
|
2
2
|
MDI_IR_VERSION,
|
|
3
3
|
MDI_SPEC_VERSION,
|
|
4
4
|
MDI_TEXT_PROJECTION_VERSION,
|
|
5
|
+
applyMdiWarichu,
|
|
6
|
+
attachMdiWarichuLayout,
|
|
5
7
|
formatMdiTextPosition,
|
|
6
8
|
formatMdiTextRange,
|
|
7
9
|
getMdiTextBlocks,
|
|
8
10
|
initializeMdi,
|
|
11
|
+
layoutMdiWarichu,
|
|
12
|
+
measureMdiWarichu,
|
|
9
13
|
parse,
|
|
10
14
|
parseMdiSyntax,
|
|
11
15
|
parseMdiTextPosition,
|
|
@@ -25,17 +29,22 @@ import {
|
|
|
25
29
|
resolveMdiSourceSpan,
|
|
26
30
|
resolveMdiSourceSpans,
|
|
27
31
|
serializeMdi,
|
|
32
|
+
settleMdiPrintLayout,
|
|
28
33
|
sourceSpansForTextRange,
|
|
29
34
|
toPublicationMdast
|
|
30
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-SC6NZVRY.js";
|
|
31
36
|
export {
|
|
32
37
|
MDI_IR_VERSION,
|
|
33
38
|
MDI_SPEC_VERSION,
|
|
34
39
|
MDI_TEXT_PROJECTION_VERSION,
|
|
40
|
+
applyMdiWarichu,
|
|
41
|
+
attachMdiWarichuLayout,
|
|
35
42
|
formatMdiTextPosition,
|
|
36
43
|
formatMdiTextRange,
|
|
37
44
|
getMdiTextBlocks,
|
|
38
45
|
initializeMdi,
|
|
46
|
+
layoutMdiWarichu,
|
|
47
|
+
measureMdiWarichu,
|
|
39
48
|
parse,
|
|
40
49
|
parseMdiSyntax,
|
|
41
50
|
parseMdiTextPosition,
|
|
@@ -55,6 +64,7 @@ export {
|
|
|
55
64
|
resolveMdiSourceSpan,
|
|
56
65
|
resolveMdiSourceSpans,
|
|
57
66
|
serializeMdi,
|
|
67
|
+
settleMdiPrintLayout,
|
|
58
68
|
sourceSpansForTextRange,
|
|
59
69
|
toPublicationMdast
|
|
60
70
|
};
|
package/dist/internal/mdast.js
CHANGED
package/dist/node.cjs
CHANGED
|
@@ -143,7 +143,7 @@ async function renderPdfWithChromiumWithDiagnostics(source, profile, adapter) {
|
|
|
143
143
|
)
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
function headingsFromDocument(
|
|
146
|
+
function headingsFromDocument(document2) {
|
|
147
147
|
const headings = [];
|
|
148
148
|
const visit = (nodes) => {
|
|
149
149
|
for (const node of nodes) {
|
|
@@ -153,7 +153,7 @@ function headingsFromDocument(document) {
|
|
|
153
153
|
if (node.children) visit(node.children);
|
|
154
154
|
}
|
|
155
155
|
};
|
|
156
|
-
visit(
|
|
156
|
+
visit(document2.children);
|
|
157
157
|
return headings;
|
|
158
158
|
}
|
|
159
159
|
function isHeadingDepth(value) {
|
package/dist/node.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@illusions-lab/mdi",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.26",
|
|
4
4
|
"description": "Thin JavaScript binding for the Rust-authoritative MDI parser",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"typecheck": "tsc --noEmit"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@illusions-lab/mdi-core": "^2.0.
|
|
40
|
-
"@illusions-lab/mdi-export-profile": "^2.0.
|
|
41
|
-
"@illusions-lab/mdi-to-epub": "^2.0.
|
|
39
|
+
"@illusions-lab/mdi-core": "^2.0.26",
|
|
40
|
+
"@illusions-lab/mdi-export-profile": "^2.0.31",
|
|
41
|
+
"@illusions-lab/mdi-to-epub": "^2.0.41",
|
|
42
42
|
"@types/mdast": "^4.0.0",
|
|
43
43
|
"yaml": "^2.0.0"
|
|
44
44
|
},
|