@particle-academy/last-word 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,2419 @@
1
+ // src/exceptions.ts
2
+ var SchemaException = class _SchemaException extends Error {
3
+ constructor(message, errors) {
4
+ super(message);
5
+ this.name = "SchemaException";
6
+ this.errors = errors;
7
+ Object.setPrototypeOf(this, _SchemaException.prototype);
8
+ }
9
+ };
10
+
11
+ // src/helpers/image-size.ts
12
+ function base64Decode(b64) {
13
+ if (typeof Buffer !== "undefined") {
14
+ const buf = Buffer.from(b64, "base64");
15
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
16
+ }
17
+ const bin = atob(b64);
18
+ const out = new Uint8Array(bin.length);
19
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
20
+ return out;
21
+ }
22
+ function base64Encode(bytes) {
23
+ if (typeof Buffer !== "undefined") {
24
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
25
+ }
26
+ let bin = "";
27
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
28
+ return btoa(bin);
29
+ }
30
+ function parseDataUrl(src) {
31
+ const m = /^data:([^;,]+);base64,([\s\S]*)$/.exec(src);
32
+ if (!m) return null;
33
+ try {
34
+ return { mime: m[1].toLowerCase(), bytes: base64Decode(m[2].replace(/\s+/g, "")) };
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ function pngSize(bytes) {
40
+ if (bytes.length < 24) return null;
41
+ const sig = [137, 80, 78, 71, 13, 10, 26, 10];
42
+ for (let i = 0; i < 8; i++) {
43
+ if (bytes[i] !== sig[i]) return null;
44
+ }
45
+ if (bytes[12] !== 73 || bytes[13] !== 72 || bytes[14] !== 68 || bytes[15] !== 82) return null;
46
+ const width = bytes[16] << 24 | bytes[17] << 16 | bytes[18] << 8 | bytes[19];
47
+ const height = bytes[20] << 24 | bytes[21] << 16 | bytes[22] << 8 | bytes[23];
48
+ if (width <= 0 || height <= 0) return null;
49
+ return { width, height };
50
+ }
51
+ function jpegSize(bytes) {
52
+ if (bytes.length < 4 || bytes[0] !== 255 || bytes[1] !== 216) return null;
53
+ let i = 2;
54
+ while (i + 9 < bytes.length) {
55
+ if (bytes[i] !== 255) {
56
+ i++;
57
+ continue;
58
+ }
59
+ const marker = bytes[i + 1];
60
+ if (marker === 216 || marker >= 208 && marker <= 215 || marker === 1 || marker === 255) {
61
+ i += 2;
62
+ continue;
63
+ }
64
+ const length = bytes[i + 2] << 8 | bytes[i + 3];
65
+ const isSof = marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204;
66
+ if (isSof) {
67
+ const height = bytes[i + 5] << 8 | bytes[i + 6];
68
+ const width = bytes[i + 7] << 8 | bytes[i + 8];
69
+ if (width <= 0 || height <= 0) return null;
70
+ return { width, height };
71
+ }
72
+ if (marker === 217 || marker === 218) break;
73
+ i += 2 + length;
74
+ }
75
+ return null;
76
+ }
77
+ function sniffImageSize(bytes) {
78
+ return pngSize(bytes) ?? jpegSize(bytes);
79
+ }
80
+
81
+ // src/zip/crc32.ts
82
+ var TABLE = /* @__PURE__ */ (() => {
83
+ const t = new Uint32Array(256);
84
+ for (let n = 0; n < 256; n++) {
85
+ let c = n;
86
+ for (let k = 0; k < 8; k++) {
87
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
88
+ }
89
+ t[n] = c >>> 0;
90
+ }
91
+ return t;
92
+ })();
93
+ function crc32(bytes) {
94
+ let crc = 4294967295;
95
+ for (let i = 0; i < bytes.length; i++) {
96
+ crc = TABLE[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
97
+ }
98
+ return (crc ^ 4294967295) >>> 0;
99
+ }
100
+
101
+ // src/zip/zip-writer.ts
102
+ var encoder = new TextEncoder();
103
+ var DOS_TIME = 0;
104
+ var DOS_DATE = 33;
105
+ function zipSync(files) {
106
+ const localParts = [];
107
+ const centralParts = [];
108
+ let offset = 0;
109
+ for (const f of files) {
110
+ const nameBytes = encoder.encode(f.name);
111
+ const crc = crc32(f.data);
112
+ const size = f.data.length;
113
+ const lh = new Uint8Array(30 + nameBytes.length);
114
+ const ldv = new DataView(lh.buffer);
115
+ ldv.setUint32(0, 67324752, true);
116
+ ldv.setUint16(4, 20, true);
117
+ ldv.setUint16(6, 2048, true);
118
+ ldv.setUint16(8, 0, true);
119
+ ldv.setUint16(10, DOS_TIME, true);
120
+ ldv.setUint16(12, DOS_DATE, true);
121
+ ldv.setUint32(14, crc, true);
122
+ ldv.setUint32(18, size, true);
123
+ ldv.setUint32(22, size, true);
124
+ ldv.setUint16(26, nameBytes.length, true);
125
+ ldv.setUint16(28, 0, true);
126
+ lh.set(nameBytes, 30);
127
+ localParts.push(lh, f.data);
128
+ const cd = new Uint8Array(46 + nameBytes.length);
129
+ const cdv = new DataView(cd.buffer);
130
+ cdv.setUint32(0, 33639248, true);
131
+ cdv.setUint16(4, 20, true);
132
+ cdv.setUint16(6, 20, true);
133
+ cdv.setUint16(8, 2048, true);
134
+ cdv.setUint16(10, 0, true);
135
+ cdv.setUint16(12, DOS_TIME, true);
136
+ cdv.setUint16(14, DOS_DATE, true);
137
+ cdv.setUint32(16, crc, true);
138
+ cdv.setUint32(20, size, true);
139
+ cdv.setUint32(24, size, true);
140
+ cdv.setUint16(28, nameBytes.length, true);
141
+ cdv.setUint16(30, 0, true);
142
+ cdv.setUint16(32, 0, true);
143
+ cdv.setUint16(34, 0, true);
144
+ cdv.setUint16(36, 0, true);
145
+ cdv.setUint32(38, 0, true);
146
+ cdv.setUint32(42, offset, true);
147
+ cd.set(nameBytes, 46);
148
+ centralParts.push(cd);
149
+ offset += lh.length + f.data.length;
150
+ }
151
+ const localSize = offset;
152
+ const centralSize = centralParts.reduce((s, c) => s + c.length, 0);
153
+ const eocd = new Uint8Array(22);
154
+ const edv = new DataView(eocd.buffer);
155
+ edv.setUint32(0, 101010256, true);
156
+ edv.setUint16(4, 0, true);
157
+ edv.setUint16(6, 0, true);
158
+ edv.setUint16(8, files.length, true);
159
+ edv.setUint16(10, files.length, true);
160
+ edv.setUint32(12, centralSize, true);
161
+ edv.setUint32(16, localSize, true);
162
+ edv.setUint16(20, 0, true);
163
+ const out = new Uint8Array(localSize + centralSize + 22);
164
+ let p = 0;
165
+ for (const part of localParts) {
166
+ out.set(part, p);
167
+ p += part.length;
168
+ }
169
+ for (const part of centralParts) {
170
+ out.set(part, p);
171
+ p += part.length;
172
+ }
173
+ out.set(eocd, p);
174
+ return out;
175
+ }
176
+
177
+ // src/zip/inflate.ts
178
+ var Tree = class {
179
+ constructor() {
180
+ // number of codes with a given bit length
181
+ this.table = new Uint16Array(16);
182
+ // symbols sorted by code
183
+ this.trans = new Uint16Array(288);
184
+ }
185
+ };
186
+ var LENGTH_BITS = new Uint8Array([
187
+ 0,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ 0,
192
+ 0,
193
+ 0,
194
+ 0,
195
+ 1,
196
+ 1,
197
+ 1,
198
+ 1,
199
+ 2,
200
+ 2,
201
+ 2,
202
+ 2,
203
+ 3,
204
+ 3,
205
+ 3,
206
+ 3,
207
+ 4,
208
+ 4,
209
+ 4,
210
+ 4,
211
+ 5,
212
+ 5,
213
+ 5,
214
+ 5,
215
+ 0
216
+ ]);
217
+ var LENGTH_BASE = new Uint16Array([
218
+ 3,
219
+ 4,
220
+ 5,
221
+ 6,
222
+ 7,
223
+ 8,
224
+ 9,
225
+ 10,
226
+ 11,
227
+ 13,
228
+ 15,
229
+ 17,
230
+ 19,
231
+ 23,
232
+ 27,
233
+ 31,
234
+ 35,
235
+ 43,
236
+ 51,
237
+ 59,
238
+ 67,
239
+ 83,
240
+ 99,
241
+ 115,
242
+ 131,
243
+ 163,
244
+ 195,
245
+ 227,
246
+ 258
247
+ ]);
248
+ var DIST_BITS = new Uint8Array([
249
+ 0,
250
+ 0,
251
+ 0,
252
+ 0,
253
+ 1,
254
+ 1,
255
+ 2,
256
+ 2,
257
+ 3,
258
+ 3,
259
+ 4,
260
+ 4,
261
+ 5,
262
+ 5,
263
+ 6,
264
+ 6,
265
+ 7,
266
+ 7,
267
+ 8,
268
+ 8,
269
+ 9,
270
+ 9,
271
+ 10,
272
+ 10,
273
+ 11,
274
+ 11,
275
+ 12,
276
+ 12,
277
+ 13,
278
+ 13
279
+ ]);
280
+ var DIST_BASE = new Uint16Array([
281
+ 1,
282
+ 2,
283
+ 3,
284
+ 4,
285
+ 5,
286
+ 7,
287
+ 9,
288
+ 13,
289
+ 17,
290
+ 25,
291
+ 33,
292
+ 49,
293
+ 65,
294
+ 97,
295
+ 129,
296
+ 193,
297
+ 257,
298
+ 385,
299
+ 513,
300
+ 769,
301
+ 1025,
302
+ 1537,
303
+ 2049,
304
+ 3073,
305
+ 4097,
306
+ 6145,
307
+ 8193,
308
+ 12289,
309
+ 16385,
310
+ 24577
311
+ ]);
312
+ var CLC_INDEX = new Uint8Array([
313
+ 16,
314
+ 17,
315
+ 18,
316
+ 0,
317
+ 8,
318
+ 7,
319
+ 9,
320
+ 6,
321
+ 10,
322
+ 5,
323
+ 11,
324
+ 4,
325
+ 12,
326
+ 3,
327
+ 13,
328
+ 2,
329
+ 14,
330
+ 1,
331
+ 15
332
+ ]);
333
+ function buildFixedTrees(lt, dt) {
334
+ let i;
335
+ for (i = 0; i < 7; i++) lt.table[i] = 0;
336
+ lt.table[7] = 24;
337
+ lt.table[8] = 152;
338
+ lt.table[9] = 112;
339
+ for (i = 0; i < 24; i++) lt.trans[i] = 256 + i;
340
+ for (i = 0; i < 144; i++) lt.trans[24 + i] = i;
341
+ for (i = 0; i < 8; i++) lt.trans[24 + 144 + i] = 280 + i;
342
+ for (i = 0; i < 112; i++) lt.trans[24 + 144 + 8 + i] = 144 + i;
343
+ for (i = 0; i < 5; i++) dt.table[i] = 0;
344
+ dt.table[5] = 32;
345
+ for (i = 0; i < 32; i++) dt.trans[i] = i;
346
+ }
347
+ var OFFS = new Uint16Array(16);
348
+ function buildTree(t, lengths, off, num) {
349
+ let i;
350
+ let sum = 0;
351
+ for (i = 0; i < 16; i++) t.table[i] = 0;
352
+ for (i = 0; i < num; i++) t.table[lengths[off + i]]++;
353
+ t.table[0] = 0;
354
+ for (i = 0; i < 16; i++) {
355
+ OFFS[i] = sum;
356
+ sum += t.table[i];
357
+ }
358
+ for (i = 0; i < num; i++) {
359
+ const len = lengths[off + i];
360
+ if (len) t.trans[OFFS[len]++] = i;
361
+ }
362
+ }
363
+ var Reader = class {
364
+ constructor(source) {
365
+ this.index = 0;
366
+ this.tag = 0;
367
+ this.bitcount = 0;
368
+ this.source = source;
369
+ }
370
+ };
371
+ function getBit(d) {
372
+ if (d.bitcount-- === 0) {
373
+ d.tag = d.source[d.index++];
374
+ d.bitcount = 7;
375
+ }
376
+ const bit = d.tag & 1;
377
+ d.tag >>>= 1;
378
+ return bit;
379
+ }
380
+ function readBits(d, num, base) {
381
+ if (!num) return base;
382
+ let val = 0;
383
+ for (let i = 0; i < num; i++) val |= getBit(d) << i;
384
+ return val + base;
385
+ }
386
+ function decodeSymbol(d, t) {
387
+ let sum = 0;
388
+ let cur = 0;
389
+ let len = 0;
390
+ do {
391
+ cur = 2 * cur + getBit(d);
392
+ len++;
393
+ sum += t.table[len];
394
+ cur -= t.table[len];
395
+ } while (cur >= 0);
396
+ return t.trans[sum + cur];
397
+ }
398
+ function decodeTrees(d, lt, dt) {
399
+ const lengths = new Uint8Array(288 + 32);
400
+ const hlit = readBits(d, 5, 257);
401
+ const hdist = readBits(d, 5, 1);
402
+ const hclen = readBits(d, 4, 4);
403
+ let i;
404
+ for (i = 0; i < 19; i++) lengths[i] = 0;
405
+ for (i = 0; i < hclen; i++) {
406
+ const clen = readBits(d, 3, 0);
407
+ lengths[CLC_INDEX[i]] = clen;
408
+ }
409
+ const codeTree = new Tree();
410
+ buildTree(codeTree, lengths, 0, 19);
411
+ for (let num = 0; num < hlit + hdist; ) {
412
+ const sym = decodeSymbol(d, codeTree);
413
+ switch (sym) {
414
+ case 16: {
415
+ const prev = lengths[num - 1];
416
+ for (let length = readBits(d, 2, 3); length; length--) lengths[num++] = prev;
417
+ break;
418
+ }
419
+ case 17:
420
+ for (let length = readBits(d, 3, 3); length; length--) lengths[num++] = 0;
421
+ break;
422
+ case 18:
423
+ for (let length = readBits(d, 7, 11); length; length--) lengths[num++] = 0;
424
+ break;
425
+ default:
426
+ lengths[num++] = sym;
427
+ break;
428
+ }
429
+ }
430
+ buildTree(lt, lengths, 0, hlit);
431
+ buildTree(dt, lengths, hlit, hdist);
432
+ }
433
+ var Out = class {
434
+ constructor() {
435
+ this.buf = new Uint8Array(1024);
436
+ this.len = 0;
437
+ }
438
+ push(b) {
439
+ if (this.len >= this.buf.length) {
440
+ const next = new Uint8Array(this.buf.length * 2);
441
+ next.set(this.buf);
442
+ this.buf = next;
443
+ }
444
+ this.buf[this.len++] = b;
445
+ }
446
+ };
447
+ function inflateBlockData(d, out, lt, dt) {
448
+ for (; ; ) {
449
+ const sym = decodeSymbol(d, lt);
450
+ if (sym === 256) return;
451
+ if (sym < 256) {
452
+ out.push(sym);
453
+ } else {
454
+ const lengthSym = sym - 257;
455
+ const length = readBits(d, LENGTH_BITS[lengthSym], LENGTH_BASE[lengthSym]);
456
+ const distSym = decodeSymbol(d, dt);
457
+ const dist = readBits(d, DIST_BITS[distSym], DIST_BASE[distSym]);
458
+ const offs = out.len - dist;
459
+ for (let i = 0; i < length; i++) out.push(out.buf[offs + i]);
460
+ }
461
+ }
462
+ }
463
+ function inflateUncompressedBlock(d, out) {
464
+ d.bitcount = 0;
465
+ let length = d.source[d.index + 1] * 256 + d.source[d.index];
466
+ d.index += 4;
467
+ for (; length; length--) out.push(d.source[d.index++]);
468
+ }
469
+ function inflateRaw(source) {
470
+ const d = new Reader(source);
471
+ const out = new Out();
472
+ const lt = new Tree();
473
+ const dt = new Tree();
474
+ let bfinal;
475
+ do {
476
+ bfinal = getBit(d);
477
+ const btype = readBits(d, 2, 0);
478
+ if (btype === 0) {
479
+ inflateUncompressedBlock(d, out);
480
+ } else if (btype === 1) {
481
+ buildFixedTrees(lt, dt);
482
+ inflateBlockData(d, out, lt, dt);
483
+ } else if (btype === 2) {
484
+ decodeTrees(d, lt, dt);
485
+ inflateBlockData(d, out, lt, dt);
486
+ } else {
487
+ throw new Error("dark-slide: invalid DEFLATE block type");
488
+ }
489
+ } while (!bfinal);
490
+ return out.buf.subarray(0, out.len);
491
+ }
492
+
493
+ // src/zip/zip-reader.ts
494
+ var decoder = new TextDecoder();
495
+ function unzipSync(data) {
496
+ const dv = new DataView(data.buffer, data.byteOffset, data.byteLength);
497
+ let eocd = -1;
498
+ for (let i = data.length - 22; i >= 0; i--) {
499
+ if (dv.getUint32(i, true) === 101010256) {
500
+ eocd = i;
501
+ break;
502
+ }
503
+ }
504
+ if (eocd < 0) throw new Error("dark-slide: not a zip archive (no EOCD record)");
505
+ const count = dv.getUint16(eocd + 10, true);
506
+ let cd = dv.getUint32(eocd + 16, true);
507
+ const result = {};
508
+ for (let n = 0; n < count; n++) {
509
+ if (dv.getUint32(cd, true) !== 33639248) break;
510
+ const method = dv.getUint16(cd + 10, true);
511
+ const compSize = dv.getUint32(cd + 20, true);
512
+ const nameLen = dv.getUint16(cd + 28, true);
513
+ const extraLen = dv.getUint16(cd + 30, true);
514
+ const commentLen = dv.getUint16(cd + 32, true);
515
+ const localOffset = dv.getUint32(cd + 42, true);
516
+ const name = decoder.decode(data.subarray(cd + 46, cd + 46 + nameLen));
517
+ const lhNameLen = dv.getUint16(localOffset + 26, true);
518
+ const lhExtraLen = dv.getUint16(localOffset + 28, true);
519
+ const dataStart = localOffset + 30 + lhNameLen + lhExtraLen;
520
+ const raw = data.subarray(dataStart, dataStart + compSize);
521
+ let content;
522
+ if (method === 0) content = raw.slice();
523
+ else if (method === 8) content = inflateRaw(raw);
524
+ else throw new Error(`dark-slide: unsupported zip method ${method} for "${name}"`);
525
+ result[name] = content;
526
+ cd += 46 + nameLen + extraLen + commentLen;
527
+ }
528
+ return result;
529
+ }
530
+
531
+ // src/reader/xml.ts
532
+ function localName(name) {
533
+ const idx = name.indexOf(":");
534
+ return idx >= 0 ? name.slice(idx + 1) : name;
535
+ }
536
+ function unescapeXml(s) {
537
+ if (s.indexOf("&") < 0) return s;
538
+ return s.replace(/&(#x?[0-9a-fA-F]+|\w+);/g, (m, ent) => {
539
+ switch (ent) {
540
+ case "amp":
541
+ return "&";
542
+ case "lt":
543
+ return "<";
544
+ case "gt":
545
+ return ">";
546
+ case "quot":
547
+ return '"';
548
+ case "apos":
549
+ return "'";
550
+ default:
551
+ if (ent[0] === "#") {
552
+ const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
553
+ return Number.isFinite(code) ? String.fromCodePoint(code) : m;
554
+ }
555
+ return m;
556
+ }
557
+ });
558
+ }
559
+ function findTagEnd(src, from) {
560
+ let quote = "";
561
+ for (let i = from; i < src.length; i++) {
562
+ const ch = src[i];
563
+ if (quote) {
564
+ if (ch === quote) quote = "";
565
+ } else if (ch === '"' || ch === "'") {
566
+ quote = ch;
567
+ } else if (ch === ">") {
568
+ return i;
569
+ }
570
+ }
571
+ return src.length;
572
+ }
573
+ var ATTR_RE = /([^\s=/]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
574
+ function parseTag(inner) {
575
+ const trimmed = inner.trim();
576
+ let i = 0;
577
+ while (i < trimmed.length && !/\s/.test(trimmed[i])) i++;
578
+ const name = trimmed.slice(0, i);
579
+ const attrs = {};
580
+ const rest = trimmed.slice(i);
581
+ for (const m of rest.matchAll(ATTR_RE)) {
582
+ const key = localName(m[1]);
583
+ const value = unescapeXml(m[3] ?? m[4] ?? "");
584
+ attrs[key] = value;
585
+ }
586
+ return { name, attrs };
587
+ }
588
+ function parseXml(src) {
589
+ let i = 0;
590
+ const n = src.length;
591
+ const root = { name: "#root", attrs: {}, children: [], text: "" };
592
+ const stack = [root];
593
+ while (i < n) {
594
+ if (src[i] === "<") {
595
+ if (src.startsWith("<!--", i)) {
596
+ const end2 = src.indexOf("-->", i);
597
+ i = end2 < 0 ? n : end2 + 3;
598
+ continue;
599
+ }
600
+ if (src.startsWith("<![CDATA[", i)) {
601
+ const end2 = src.indexOf("]]>", i);
602
+ stack[stack.length - 1].text += src.slice(i + 9, end2 < 0 ? n : end2);
603
+ i = end2 < 0 ? n : end2 + 3;
604
+ continue;
605
+ }
606
+ if (src.startsWith("<?", i)) {
607
+ const end2 = src.indexOf("?>", i);
608
+ i = end2 < 0 ? n : end2 + 2;
609
+ continue;
610
+ }
611
+ if (src.startsWith("<!", i)) {
612
+ const end2 = src.indexOf(">", i);
613
+ i = end2 < 0 ? n : end2 + 1;
614
+ continue;
615
+ }
616
+ if (src[i + 1] === "/") {
617
+ const end2 = src.indexOf(">", i);
618
+ if (stack.length > 1) stack.pop();
619
+ i = end2 < 0 ? n : end2 + 1;
620
+ continue;
621
+ }
622
+ const end = findTagEnd(src, i + 1);
623
+ const tagContent = src.slice(i + 1, end);
624
+ const selfClosing = tagContent.endsWith("/");
625
+ const { name, attrs } = parseTag(selfClosing ? tagContent.slice(0, -1) : tagContent);
626
+ const node = { name: localName(name), attrs, children: [], text: "" };
627
+ stack[stack.length - 1].children.push(node);
628
+ if (!selfClosing) stack.push(node);
629
+ i = end + 1;
630
+ } else {
631
+ const next = src.indexOf("<", i);
632
+ const stop = next < 0 ? n : next;
633
+ stack[stack.length - 1].text += unescapeXml(src.slice(i, stop));
634
+ i = stop;
635
+ }
636
+ }
637
+ return root.children[0] ?? null;
638
+ }
639
+ function el(node, name) {
640
+ return node?.children.find((c) => c.name === name);
641
+ }
642
+ function els(node, name) {
643
+ return node ? node.children.filter((c) => c.name === name) : [];
644
+ }
645
+ function at(node, name) {
646
+ return node?.attrs[name];
647
+ }
648
+
649
+ // src/helpers/xml.ts
650
+ var Xml = {
651
+ text(s) {
652
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
653
+ },
654
+ attr(s) {
655
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
656
+ },
657
+ declaration(standalone = true) {
658
+ const sa = standalone ? ' standalone="yes"' : "";
659
+ return `<?xml version="1.0" encoding="UTF-8"${sa}?>
660
+ `;
661
+ }
662
+ };
663
+
664
+ // src/writer/docx-writer.ts
665
+ var EMU_PER_PX = 9525;
666
+ var MAX_WIDTH_PX = 624;
667
+ var NS_W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
668
+ var NS_R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
669
+ var NS_WP = "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
670
+ var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
671
+ var NS_PIC = "http://schemas.openxmlformats.org/drawingml/2006/picture";
672
+ var REL_STYLES = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
673
+ var REL_NUMBERING = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
674
+ var REL_HYPERLINK = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
675
+ var REL_IMAGE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
676
+ var REL_DOCUMENT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
677
+ var REL_CORE = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
678
+ var NUM_ID_BULLET = 1;
679
+ var NUM_ID_DECIMAL = 2;
680
+ var MAX_ILVL = 5;
681
+ var SDT_TAG_CODE = "lastword:code";
682
+ var SDT_TAG_QUOTE = "lastword:quote";
683
+ function normalizeHex(hex) {
684
+ return hex.replace(/^#/, "").toUpperCase();
685
+ }
686
+ var DocxWriter = class {
687
+ constructor() {
688
+ this.rels = [];
689
+ this.hyperlinkIds = /* @__PURE__ */ new Map();
690
+ this.media = [];
691
+ this.drawingId = 0;
692
+ }
693
+ toBytes(doc) {
694
+ this.rels = [
695
+ { id: "rId1", type: REL_STYLES, target: "styles.xml", external: false },
696
+ { id: "rId2", type: REL_NUMBERING, target: "numbering.xml", external: false }
697
+ ];
698
+ this.hyperlinkIds = /* @__PURE__ */ new Map();
699
+ this.media = [];
700
+ this.drawingId = 0;
701
+ const body = this.renderBlocks(doc.blocks ?? [], {});
702
+ const documentXml = Xml.declaration() + `<w:document xmlns:w="${NS_W}" xmlns:r="${NS_R}" xmlns:wp="${NS_WP}" xmlns:a="${NS_A}" xmlns:pic="${NS_PIC}"><w:body>${body}${this.sectPr()}</w:body></w:document>`;
703
+ const files = [
704
+ { name: "[Content_Types].xml", data: encode(this.contentTypesXml(doc)) },
705
+ { name: "_rels/.rels", data: encode(this.packageRelsXml(doc)) }
706
+ ];
707
+ if (doc.title !== void 0) {
708
+ files.push({ name: "docProps/core.xml", data: encode(this.coreXml(String(doc.title))) });
709
+ }
710
+ files.push(
711
+ { name: "word/document.xml", data: encode(documentXml) },
712
+ { name: "word/styles.xml", data: encode(this.stylesXml()) },
713
+ { name: "word/numbering.xml", data: encode(this.numberingXml()) },
714
+ { name: "word/_rels/document.xml.rels", data: encode(this.documentRelsXml()) }
715
+ );
716
+ for (const m of this.media) {
717
+ files.push({ name: `word/media/${m.name}`, data: m.bytes });
718
+ }
719
+ return zipSync(files);
720
+ }
721
+ // ── Blocks ────────────────────────────────────────────────────────────────
722
+ renderBlocks(blocks, ctx) {
723
+ let out = "";
724
+ for (const block of blocks) {
725
+ out += this.renderBlock(block, ctx);
726
+ }
727
+ return out;
728
+ }
729
+ renderBlock(block, ctx) {
730
+ switch (block.type) {
731
+ case "heading":
732
+ return this.paragraph(this.pPr(`Heading${clampLevel(block.level)}`), this.renderRuns(block.runs ?? []));
733
+ case "paragraph": {
734
+ const jc = block.align && block.align !== "left" ? block.align === "justify" ? "both" : block.align : null;
735
+ return this.paragraph(this.pPr(ctx.paragraphStyle ?? null, null, jc), this.renderRuns(block.runs ?? []));
736
+ }
737
+ case "list":
738
+ return this.renderList(block.items ?? [], block.ordered === true, 0);
739
+ case "table":
740
+ return this.renderTable(block);
741
+ case "code":
742
+ return this.renderCode(block);
743
+ case "quote":
744
+ return this.renderQuote(block);
745
+ case "image":
746
+ return this.renderImage(block);
747
+ case "pageBreak":
748
+ return `<w:p><w:r><w:br w:type="page"/></w:r></w:p>`;
749
+ case "hr":
750
+ return `<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="auto"/></w:pBdr></w:pPr></w:p>`;
751
+ default:
752
+ return "";
753
+ }
754
+ }
755
+ paragraph(pPr, runs) {
756
+ return `<w:p>${pPr}${runs}</w:p>`;
757
+ }
758
+ pPr(style, numPr = null, jc = null) {
759
+ let inner = "";
760
+ if (style) inner += `<w:pStyle w:val="${Xml.attr(style)}"/>`;
761
+ if (numPr) inner += numPr;
762
+ if (jc) inner += `<w:jc w:val="${jc}"/>`;
763
+ return inner === "" ? "" : `<w:pPr>${inner}</w:pPr>`;
764
+ }
765
+ // ── Runs ──────────────────────────────────────────────────────────────────
766
+ renderRuns(runs) {
767
+ let out = "";
768
+ for (const run of runs) {
769
+ if (typeof run?.text !== "string") continue;
770
+ if (run.link) {
771
+ const rid = this.hyperlinkRel(run.link);
772
+ out += `<w:hyperlink r:id="${rid}" w:history="1">${this.renderRun(run, true)}</w:hyperlink>`;
773
+ } else {
774
+ out += this.renderRun(run, false);
775
+ }
776
+ }
777
+ return out;
778
+ }
779
+ renderRun(run, linked) {
780
+ let rPr = "";
781
+ if (run.code) rPr += `<w:rStyle w:val="InlineCode"/>`;
782
+ else if (linked) rPr += `<w:rStyle w:val="Hyperlink"/>`;
783
+ if (run.code) rPr += `<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>`;
784
+ if (run.bold) rPr += `<w:b/>`;
785
+ if (run.italic) rPr += `<w:i/>`;
786
+ if (run.strike) rPr += `<w:strike/>`;
787
+ if (run.color) rPr += `<w:color w:val="${normalizeHex(run.color)}"/>`;
788
+ if (run.underline) rPr += `<w:u w:val="single"/>`;
789
+ if (run.highlight) {
790
+ rPr += `<w:shd w:val="clear" w:color="auto" w:fill="${normalizeHex(run.highlight)}"/>`;
791
+ }
792
+ const pr = rPr === "" ? "" : `<w:rPr>${rPr}</w:rPr>`;
793
+ const parts = run.text.split("\n");
794
+ let content = "";
795
+ parts.forEach((part, i) => {
796
+ if (i > 0) content += `<w:br/>`;
797
+ if (part !== "") content += `<w:t xml:space="preserve">${Xml.text(part)}</w:t>`;
798
+ });
799
+ if (content === "") content = `<w:t xml:space="preserve"></w:t>`;
800
+ return `<w:r>${pr}${content}</w:r>`;
801
+ }
802
+ hyperlinkRel(url) {
803
+ const existing = this.hyperlinkIds.get(url);
804
+ if (existing) return existing;
805
+ const id = `rId${this.rels.length + 1}`;
806
+ this.rels.push({ id, type: REL_HYPERLINK, target: url, external: true });
807
+ this.hyperlinkIds.set(url, id);
808
+ return id;
809
+ }
810
+ // ── Lists ─────────────────────────────────────────────────────────────────
811
+ renderList(items, ordered, depth) {
812
+ const numId = ordered ? NUM_ID_DECIMAL : NUM_ID_BULLET;
813
+ const ilvl = Math.min(depth, MAX_ILVL);
814
+ let out = "";
815
+ for (const item of items) {
816
+ const numPr = `<w:numPr><w:ilvl w:val="${ilvl}"/><w:numId w:val="${numId}"/></w:numPr>`;
817
+ out += this.paragraph(this.pPr(null, numPr), this.renderRuns(item.runs ?? []));
818
+ if (item.children && item.children.length > 0) {
819
+ out += this.renderList(item.children, ordered, depth + 1);
820
+ }
821
+ }
822
+ return out;
823
+ }
824
+ // ── Tables ────────────────────────────────────────────────────────────────
825
+ renderTable(block) {
826
+ const rows = block.rows ?? [];
827
+ const cols = Math.max(1, ...rows.map((r) => Array.isArray(r?.cells) ? r.cells.length : 0));
828
+ const colWidth = Math.floor(9360 / cols);
829
+ let out = `<w:tbl><w:tblPr><w:tblStyle w:val="LastWordTable"/><w:tblW w:w="0" w:type="auto"/>`;
830
+ out += `<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="0" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>`;
831
+ out += `</w:tblPr><w:tblGrid>`;
832
+ for (let c = 0; c < cols; c++) out += `<w:gridCol w:w="${colWidth}"/>`;
833
+ out += `</w:tblGrid>`;
834
+ for (const row of rows) {
835
+ const header = row?.header === true;
836
+ out += `<w:tr>`;
837
+ if (header) out += `<w:trPr><w:tblHeader/></w:trPr>`;
838
+ const cells = Array.isArray(row?.cells) ? row.cells : [];
839
+ for (const cell of cells) {
840
+ out += `<w:tc><w:tcPr><w:tcW w:w="${colWidth}" w:type="dxa"/>`;
841
+ if (header) out += `<w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/>`;
842
+ out += `</w:tcPr>`;
843
+ let inner = this.renderBlocks(cell?.blocks ?? [], {});
844
+ if (inner === "" || !inner.endsWith("</w:p>")) inner += `<w:p/>`;
845
+ out += inner + `</w:tc>`;
846
+ }
847
+ out += `</w:tr>`;
848
+ }
849
+ out += `</w:tbl>`;
850
+ return out;
851
+ }
852
+ // ── Code / quote (SDT-wrapped for lossless round-trip) ──────────────────
853
+ renderCode(block) {
854
+ const language = typeof block.language === "string" && block.language !== "" ? block.language : null;
855
+ const tag = language ? `${SDT_TAG_CODE}:${language}` : SDT_TAG_CODE;
856
+ const lines = String(block.text ?? "").split("\n");
857
+ let body = "";
858
+ for (const line of lines) {
859
+ const runs = line === "" ? "" : `<w:r><w:t xml:space="preserve">${Xml.text(line)}</w:t></w:r>`;
860
+ body += this.paragraph(this.pPr("CodeBlock"), runs);
861
+ }
862
+ return `<w:sdt><w:sdtPr><w:alias w:val="Code"/><w:tag w:val="${Xml.attr(tag)}"/></w:sdtPr><w:sdtContent>${body}</w:sdtContent></w:sdt>`;
863
+ }
864
+ renderQuote(block) {
865
+ const body = this.renderBlocks(block.blocks ?? [], { paragraphStyle: "Quote" });
866
+ return `<w:sdt><w:sdtPr><w:alias w:val="Quote"/><w:tag w:val="${SDT_TAG_QUOTE}"/></w:sdtPr><w:sdtContent>${body === "" ? "<w:p/>" : body}</w:sdtContent></w:sdt>`;
867
+ }
868
+ // ── Images ────────────────────────────────────────────────────────────────
869
+ renderImage(block) {
870
+ const decoded = parseDataUrl(String(block.src ?? ""));
871
+ if (!decoded) {
872
+ throw new Error("last-word: image `src` must be a base64 data URL (data:image/png;base64,\u2026 or data:image/jpeg;base64,\u2026)");
873
+ }
874
+ const ext = extForMime(decoded.mime);
875
+ const name = `image${this.media.length + 1}.${ext}`;
876
+ this.media.push({ name, bytes: decoded.bytes, ext });
877
+ const rid = `rId${this.rels.length + 1}`;
878
+ this.rels.push({ id: rid, type: REL_IMAGE, target: `media/${name}`, external: false });
879
+ const { width, height } = resolveImageSize(block, decoded.bytes);
880
+ const cx = Math.max(1, Math.round(width * EMU_PER_PX));
881
+ const cy = Math.max(1, Math.round(height * EMU_PER_PX));
882
+ const id = ++this.drawingId;
883
+ const alt = typeof block.alt === "string" ? block.alt : "";
884
+ const descr = alt === "" ? "" : ` descr="${Xml.attr(alt)}"`;
885
+ return `<w:p><w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:effectExtent l="0" t="0" r="0" b="0"/><wp:docPr id="${id}" name="Picture ${id}"${descr}/><wp:cNvGraphicFramePr><a:graphicFrameLocks noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic><a:graphicData uri="${NS_PIC}"><pic:pic><pic:nvPicPr><pic:cNvPr id="${id}" name="Picture ${id}"${descr}/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${rid}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r></w:p>`;
886
+ }
887
+ // ── Parts ─────────────────────────────────────────────────────────────────
888
+ sectPr() {
889
+ return `<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/></w:sectPr>`;
890
+ }
891
+ contentTypesXml(doc) {
892
+ const exts = [...new Set(this.media.map((m) => m.ext))].sort();
893
+ let defaults = `<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>`;
894
+ for (const ext of exts) {
895
+ defaults += `<Default Extension="${ext}" ContentType="${mimeForExt(ext)}"/>`;
896
+ }
897
+ let overrides = `<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/><Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>`;
898
+ if (doc.title !== void 0) {
899
+ overrides += `<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>`;
900
+ }
901
+ return Xml.declaration() + `<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">${defaults}${overrides}</Types>`;
902
+ }
903
+ packageRelsXml(doc) {
904
+ let rels = `<Relationship Id="rId1" Type="${REL_DOCUMENT}" Target="word/document.xml"/>`;
905
+ if (doc.title !== void 0) {
906
+ rels += `<Relationship Id="rId2" Type="${REL_CORE}" Target="docProps/core.xml"/>`;
907
+ }
908
+ return Xml.declaration() + `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${rels}</Relationships>`;
909
+ }
910
+ coreXml(title) {
911
+ return Xml.declaration() + `<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>${Xml.text(title)}</dc:title></cp:coreProperties>`;
912
+ }
913
+ documentRelsXml() {
914
+ let out = Xml.declaration() + `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">`;
915
+ for (const rel of this.rels) {
916
+ const mode = rel.external ? ` TargetMode="External"` : "";
917
+ out += `<Relationship Id="${rel.id}" Type="${rel.type}" Target="${Xml.attr(rel.target)}"${mode}/>`;
918
+ }
919
+ return out + `</Relationships>`;
920
+ }
921
+ stylesXml() {
922
+ const headingSizes = [40, 32, 28, 26, 24, 22];
923
+ let styles = `<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Calibri" w:cs="Calibri"/><w:sz w:val="22"/><w:szCs w:val="22"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr><w:spacing w:after="160" w:line="259" w:lineRule="auto"/></w:pPr></w:pPrDefault></w:docDefaults><w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style>`;
924
+ for (let level = 1; level <= 6; level++) {
925
+ const sz = headingSizes[level - 1];
926
+ styles += `<w:style w:type="paragraph" w:styleId="Heading${level}"><w:name w:val="heading ${level}"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="240" w:after="120"/><w:outlineLvl w:val="${level - 1}"/></w:pPr><w:rPr><w:b/><w:sz w:val="${sz}"/><w:szCs w:val="${sz}"/></w:rPr></w:style>`;
927
+ }
928
+ styles += `<w:style w:type="paragraph" w:styleId="Quote"><w:name w:val="Quote"/><w:basedOn w:val="Normal"/><w:qFormat/><w:pPr><w:ind w:left="720"/></w:pPr><w:rPr><w:i/><w:color w:val="595959"/></w:rPr></w:style><w:style w:type="paragraph" w:styleId="CodeBlock"><w:name w:val="Code Block"/><w:basedOn w:val="Normal"/><w:pPr><w:spacing w:after="0" w:line="240" w:lineRule="auto"/><w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/></w:pPr><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/><w:sz w:val="20"/><w:szCs w:val="20"/></w:rPr></w:style><w:style w:type="character" w:styleId="InlineCode"><w:name w:val="Inline Code"/><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/><w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/></w:rPr></w:style><w:style w:type="character" w:styleId="Hyperlink"><w:name w:val="Hyperlink"/><w:rPr><w:color w:val="0563C1"/><w:u w:val="single"/></w:rPr></w:style><w:style w:type="table" w:styleId="LastWordTable"><w:name w:val="LastWord Table"/><w:tblPr><w:tblBorders><w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideH w:val="single" w:sz="4" w:space="0" w:color="auto"/><w:insideV w:val="single" w:sz="4" w:space="0" w:color="auto"/></w:tblBorders><w:tblCellMar><w:top w:w="60" w:type="dxa"/><w:left w:w="108" w:type="dxa"/><w:bottom w:w="60" w:type="dxa"/><w:right w:w="108" w:type="dxa"/></w:tblCellMar></w:tblPr><w:tblStylePr w:type="firstRow"><w:rPr><w:b/></w:rPr><w:tcPr><w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/></w:tcPr></w:tblStylePr></w:style>`;
929
+ return Xml.declaration() + `<w:styles xmlns:w="${NS_W}">${styles}</w:styles>`;
930
+ }
931
+ numberingXml() {
932
+ let out = Xml.declaration() + `<w:numbering xmlns:w="${NS_W}">`;
933
+ out += `<w:abstractNum w:abstractNumId="0"><w:multiLevelType w:val="hybridMultilevel"/>`;
934
+ for (let i = 0; i <= MAX_ILVL; i++) {
935
+ out += `<w:lvl w:ilvl="${i}"><w:start w:val="1"/><w:numFmt w:val="bullet"/><w:lvlText w:val="\u2022"/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="${720 * (i + 1)}" w:hanging="360"/></w:pPr></w:lvl>`;
936
+ }
937
+ out += `</w:abstractNum>`;
938
+ out += `<w:abstractNum w:abstractNumId="1"><w:multiLevelType w:val="hybridMultilevel"/>`;
939
+ for (let i = 0; i <= MAX_ILVL; i++) {
940
+ out += `<w:lvl w:ilvl="${i}"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%${i + 1}."/><w:lvlJc w:val="left"/><w:pPr><w:ind w:left="${720 * (i + 1)}" w:hanging="360"/></w:pPr></w:lvl>`;
941
+ }
942
+ out += `</w:abstractNum>`;
943
+ out += `<w:num w:numId="${NUM_ID_BULLET}"><w:abstractNumId w:val="0"/></w:num>`;
944
+ out += `<w:num w:numId="${NUM_ID_DECIMAL}"><w:abstractNumId w:val="1"/></w:num>`;
945
+ return out + `</w:numbering>`;
946
+ }
947
+ };
948
+ var encoder2 = new TextEncoder();
949
+ function encode(xml) {
950
+ return encoder2.encode(xml);
951
+ }
952
+ function clampLevel(level) {
953
+ const n = Number.isFinite(Number(level)) ? Math.trunc(Number(level)) : 1;
954
+ return Math.min(6, Math.max(1, n));
955
+ }
956
+ function extForMime(mime) {
957
+ switch (mime) {
958
+ case "image/png":
959
+ return "png";
960
+ case "image/jpeg":
961
+ case "image/jpg":
962
+ return "jpeg";
963
+ case "image/gif":
964
+ return "gif";
965
+ default:
966
+ return mime.split("/")[1] ?? "bin";
967
+ }
968
+ }
969
+ function mimeForExt(ext) {
970
+ switch (ext) {
971
+ case "png":
972
+ return "image/png";
973
+ case "jpeg":
974
+ return "image/jpeg";
975
+ case "gif":
976
+ return "image/gif";
977
+ default:
978
+ return "application/octet-stream";
979
+ }
980
+ }
981
+ function resolveImageSize(block, bytes) {
982
+ const intrinsic = sniffImageSize(bytes);
983
+ let width = typeof block.widthPx === "number" && block.widthPx > 0 ? block.widthPx : void 0;
984
+ let height = typeof block.heightPx === "number" && block.heightPx > 0 ? block.heightPx : void 0;
985
+ if (width === void 0 && height === void 0) {
986
+ width = intrinsic?.width ?? 300;
987
+ height = intrinsic?.height ?? 200;
988
+ } else if (width === void 0) {
989
+ const aspect = intrinsic ? intrinsic.width / intrinsic.height : 1.5;
990
+ width = height * aspect;
991
+ } else if (height === void 0) {
992
+ const aspect = intrinsic ? intrinsic.width / intrinsic.height : 1.5;
993
+ height = width / aspect;
994
+ }
995
+ if (width > MAX_WIDTH_PX) {
996
+ height = height * MAX_WIDTH_PX / width;
997
+ width = MAX_WIDTH_PX;
998
+ }
999
+ return { width: Math.max(1, Math.round(width)), height: Math.max(1, Math.round(height)) };
1000
+ }
1001
+
1002
+ // src/reader/docx-reader.ts
1003
+ var DECODER = new TextDecoder();
1004
+ var HIGHLIGHT_NAMES = {
1005
+ yellow: "#FFFF00",
1006
+ green: "#00FF00",
1007
+ cyan: "#00FFFF",
1008
+ magenta: "#FF00FF",
1009
+ blue: "#0000FF",
1010
+ red: "#FF0000",
1011
+ darkBlue: "#00008B",
1012
+ darkCyan: "#008B8B",
1013
+ darkGreen: "#006400",
1014
+ darkMagenta: "#8B008B",
1015
+ darkRed: "#8B0000",
1016
+ darkYellow: "#808000",
1017
+ darkGray: "#A9A9A9",
1018
+ lightGray: "#D3D3D3",
1019
+ black: "#000000",
1020
+ white: "#FFFFFF"
1021
+ };
1022
+ var MONO_FONTS = ["consolas", "courier new", "courier", "menlo", "monaco", "source code pro"];
1023
+ var ORDERED_NUM_FMTS = [
1024
+ "decimal",
1025
+ "decimalZero",
1026
+ "upperRoman",
1027
+ "lowerRoman",
1028
+ "upperLetter",
1029
+ "lowerLetter"
1030
+ ];
1031
+ var DocxReader = class {
1032
+ constructor() {
1033
+ this.parts = {};
1034
+ this.rels = {};
1035
+ this.numOrdered = {};
1036
+ }
1037
+ read(bytes) {
1038
+ this.parts = unzipSync(bytes);
1039
+ this.rels = this.loadRels("word/_rels/document.xml.rels");
1040
+ this.numOrdered = this.loadNumbering();
1041
+ const docXml = this.partText("word/document.xml");
1042
+ const root = docXml ? parseXml(docXml) : null;
1043
+ const body = el(root, "body");
1044
+ const doc = { blocks: body ? this.walkBody(body.children, {}) : [] };
1045
+ const title = this.readTitle();
1046
+ if (title !== null) doc.title = title;
1047
+ return doc;
1048
+ }
1049
+ // ── Parts / metadata ─────────────────────────────────────────────────────
1050
+ partText(name) {
1051
+ const part = this.parts[name];
1052
+ return part ? DECODER.decode(part) : null;
1053
+ }
1054
+ readTitle() {
1055
+ const xml = this.partText("docProps/core.xml");
1056
+ if (!xml) return null;
1057
+ const root = parseXml(xml);
1058
+ const title = el(root, "title");
1059
+ return title ? title.text : null;
1060
+ }
1061
+ loadRels(name) {
1062
+ const xml = this.partText(name);
1063
+ if (!xml) return {};
1064
+ const root = parseXml(xml);
1065
+ const out = {};
1066
+ for (const rel of els(root, "Relationship")) {
1067
+ const id = at(rel, "Id");
1068
+ const target = at(rel, "Target");
1069
+ if (!id || !target) continue;
1070
+ out[id] = { target, external: at(rel, "TargetMode") === "External" };
1071
+ }
1072
+ return out;
1073
+ }
1074
+ loadNumbering() {
1075
+ const xml = this.partText("word/numbering.xml");
1076
+ if (!xml) return {};
1077
+ const root = parseXml(xml);
1078
+ const abstractOrdered = {};
1079
+ for (const abs of els(root, "abstractNum")) {
1080
+ const id = at(abs, "abstractNumId") ?? "";
1081
+ const lvl0 = els(abs, "lvl").find((l) => at(l, "ilvl") === "0") ?? el(abs, "lvl");
1082
+ const fmt = at(el(lvl0, "numFmt"), "val") ?? "bullet";
1083
+ abstractOrdered[id] = ORDERED_NUM_FMTS.includes(fmt);
1084
+ }
1085
+ const out = {};
1086
+ for (const num of els(root, "num")) {
1087
+ const numId = at(num, "numId") ?? "";
1088
+ const absId = at(el(num, "abstractNumId"), "val") ?? "";
1089
+ out[numId] = abstractOrdered[absId] ?? false;
1090
+ }
1091
+ return out;
1092
+ }
1093
+ // ── Body walking ─────────────────────────────────────────────────────────
1094
+ walkBody(nodes, ctx) {
1095
+ const blocks = [];
1096
+ let listBuf = [];
1097
+ let codeBuf = null;
1098
+ let quoteBuf = null;
1099
+ const flushList = () => {
1100
+ if (listBuf.length > 0) {
1101
+ blocks.push(...this.buildLists(listBuf));
1102
+ listBuf = [];
1103
+ }
1104
+ };
1105
+ const flushCode = () => {
1106
+ if (codeBuf !== null) {
1107
+ blocks.push({ type: "code", text: codeBuf.join("\n") });
1108
+ codeBuf = null;
1109
+ }
1110
+ };
1111
+ const flushQuote = () => {
1112
+ if (quoteBuf !== null) {
1113
+ blocks.push({ type: "quote", blocks: quoteBuf });
1114
+ quoteBuf = null;
1115
+ }
1116
+ };
1117
+ const flushAll = () => {
1118
+ flushList();
1119
+ flushCode();
1120
+ flushQuote();
1121
+ };
1122
+ for (const node of nodes) {
1123
+ switch (node.name) {
1124
+ case "p": {
1125
+ const kind = this.classifyParagraph(node);
1126
+ if (kind.kind === "listItem") {
1127
+ flushCode();
1128
+ flushQuote();
1129
+ listBuf.push(kind.item);
1130
+ } else if (kind.kind === "codeLine") {
1131
+ flushList();
1132
+ flushQuote();
1133
+ if (codeBuf === null) codeBuf = [];
1134
+ codeBuf.push(kind.text);
1135
+ } else if (kind.kind === "quoteParagraph" && !ctx.insideQuote) {
1136
+ flushList();
1137
+ flushCode();
1138
+ if (quoteBuf === null) quoteBuf = [];
1139
+ quoteBuf.push(...kind.blocks);
1140
+ } else {
1141
+ flushAll();
1142
+ blocks.push(...kind.blocks);
1143
+ }
1144
+ break;
1145
+ }
1146
+ case "tbl":
1147
+ flushAll();
1148
+ blocks.push(this.parseTable(node, ctx));
1149
+ break;
1150
+ case "sdt": {
1151
+ flushAll();
1152
+ blocks.push(...this.parseSdt(node, ctx));
1153
+ break;
1154
+ }
1155
+ case "sectPr":
1156
+ case "bookmarkStart":
1157
+ case "bookmarkEnd":
1158
+ case "proofErr":
1159
+ break;
1160
+ default:
1161
+ if (node.children.length > 0) {
1162
+ const inner = this.walkBody(node.children, ctx);
1163
+ if (inner.length > 0) {
1164
+ flushAll();
1165
+ blocks.push(...inner);
1166
+ }
1167
+ }
1168
+ break;
1169
+ }
1170
+ }
1171
+ flushAll();
1172
+ return blocks;
1173
+ }
1174
+ parseSdt(sdt, ctx) {
1175
+ const tag = at(el(el(sdt, "sdtPr"), "tag"), "val") ?? "";
1176
+ const content = el(sdt, "sdtContent");
1177
+ if (!content) return [];
1178
+ if (tag === SDT_TAG_CODE || tag.startsWith(SDT_TAG_CODE + ":")) {
1179
+ const lines = els(content, "p").map((p) => this.plainText(p));
1180
+ const block = { type: "code", text: lines.join("\n") };
1181
+ if (tag.length > SDT_TAG_CODE.length + 1) {
1182
+ block.language = tag.slice(SDT_TAG_CODE.length + 1);
1183
+ }
1184
+ return [orderCodeKeys(block)];
1185
+ }
1186
+ if (tag === SDT_TAG_QUOTE) {
1187
+ return [{ type: "quote", blocks: this.walkBody(content.children, { ...ctx, insideQuote: true }) }];
1188
+ }
1189
+ return this.walkBody(content.children, ctx);
1190
+ }
1191
+ // ── Paragraph classification ─────────────────────────────────────────────
1192
+ classifyParagraph(p) {
1193
+ const pPr = el(p, "pPr");
1194
+ const styleId = at(el(pPr, "pStyle"), "val") ?? "";
1195
+ const numPr = el(pPr, "numPr");
1196
+ if (numPr) {
1197
+ const numId = at(el(numPr, "numId"), "val") ?? "";
1198
+ const ilvl = parseInt(at(el(numPr, "ilvl"), "val") ?? "0", 10) || 0;
1199
+ return { kind: "listItem", item: { numId, ilvl, runs: this.parseRuns(p, void 0) } };
1200
+ }
1201
+ if (/^(CodeBlock|SourceCode|HTMLPreformatted|Code)$/i.test(styleId)) {
1202
+ return { kind: "codeLine", text: this.plainText(p) };
1203
+ }
1204
+ const blocks = [];
1205
+ const drawings = findAll(p, "drawing");
1206
+ const runs = this.parseRuns(p, void 0);
1207
+ const headingMatch = /^Heading([1-9])$/i.exec(styleId);
1208
+ const outlineLvl = at(el(pPr, "outlineLvl"), "val");
1209
+ if (headingMatch || outlineLvl !== void 0) {
1210
+ const raw = headingMatch ? parseInt(headingMatch[1], 10) : parseInt(outlineLvl ?? "0", 10) + 1;
1211
+ const level = Math.min(6, Math.max(1, raw));
1212
+ blocks.push({ type: "heading", level, runs });
1213
+ for (const d of drawings) blocks.push(...this.parseDrawing(d));
1214
+ return { kind: "blocks", blocks };
1215
+ }
1216
+ const hasPageBreak = findAll(p, "br").some((br) => at(br, "type") === "page");
1217
+ const text = runs.map((r) => r.text).join("");
1218
+ if (hasPageBreak && text === "" && drawings.length === 0) {
1219
+ return { kind: "blocks", blocks: [{ type: "pageBreak" }] };
1220
+ }
1221
+ const pBdr = el(pPr, "pBdr");
1222
+ if (pBdr && el(pBdr, "bottom") && text === "" && drawings.length === 0) {
1223
+ return { kind: "blocks", blocks: [{ type: "hr" }] };
1224
+ }
1225
+ if (text !== "" || drawings.length === 0) {
1226
+ const para = { type: "paragraph", runs };
1227
+ const jc = at(el(pPr, "jc"), "val");
1228
+ if (jc === "center" || jc === "right") para.align = jc;
1229
+ else if (jc === "both" || jc === "distribute" || jc === "justify") para.align = "justify";
1230
+ blocks.push(para);
1231
+ }
1232
+ for (const d of drawings) blocks.push(...this.parseDrawing(d));
1233
+ if (/^(Quote|IntenseQuote|BlockQuote|Blockquote)$/i.test(styleId)) {
1234
+ return { kind: "quoteParagraph", blocks };
1235
+ }
1236
+ return { kind: "blocks", blocks };
1237
+ }
1238
+ /** Concatenated visible text of a paragraph (tabs and soft breaks included). */
1239
+ plainText(p) {
1240
+ return this.parseRuns(p, void 0).map((r) => r.text).join("");
1241
+ }
1242
+ // ── Runs ─────────────────────────────────────────────────────────────────
1243
+ parseRuns(container, link) {
1244
+ const runs = [];
1245
+ for (const child of container.children) {
1246
+ switch (child.name) {
1247
+ case "r":
1248
+ runs.push(...this.runFrom(child, link));
1249
+ break;
1250
+ case "hyperlink": {
1251
+ const rid = at(child, "id");
1252
+ const rel = rid ? this.rels[rid] : void 0;
1253
+ const anchor = at(child, "anchor");
1254
+ const url = rel?.external ? rel.target : anchor ? `#${anchor}` : void 0;
1255
+ runs.push(...this.parseRuns(child, url ?? link));
1256
+ break;
1257
+ }
1258
+ case "sdt": {
1259
+ const content = el(child, "sdtContent");
1260
+ if (content) runs.push(...this.parseRuns(content, link));
1261
+ break;
1262
+ }
1263
+ case "ins":
1264
+ case "smartTag":
1265
+ runs.push(...this.parseRuns(child, link));
1266
+ break;
1267
+ }
1268
+ }
1269
+ return mergeRuns(runs);
1270
+ }
1271
+ runFrom(r, link) {
1272
+ const rPr = el(r, "rPr");
1273
+ let text = "";
1274
+ for (const child of r.children) {
1275
+ if (child.name === "t") text += child.text;
1276
+ else if (child.name === "br" && at(child, "type") !== "page") text += "\n";
1277
+ else if (child.name === "cr") text += "\n";
1278
+ else if (child.name === "tab") text += " ";
1279
+ }
1280
+ if (text === "") return [];
1281
+ const run = { text };
1282
+ if (onFlag(el(rPr, "b"))) run.bold = true;
1283
+ if (onFlag(el(rPr, "i"))) run.italic = true;
1284
+ if (onFlag(el(rPr, "strike"))) run.strike = true;
1285
+ const u = el(rPr, "u");
1286
+ if (u && (at(u, "val") ?? "single") !== "none") run.underline = true;
1287
+ const rStyle = at(el(rPr, "rStyle"), "val") ?? "";
1288
+ const asciiFont = (at(el(rPr, "rFonts"), "ascii") ?? "").toLowerCase();
1289
+ if (/^InlineCode$/i.test(rStyle) || MONO_FONTS.includes(asciiFont)) run.code = true;
1290
+ if (link) run.link = link;
1291
+ const color = at(el(rPr, "color"), "val");
1292
+ if (color && color !== "auto") run.color = `#${color.toUpperCase()}`;
1293
+ const shdFill = at(el(rPr, "shd"), "fill");
1294
+ if (shdFill && shdFill !== "auto") {
1295
+ run.highlight = `#${shdFill.toUpperCase()}`;
1296
+ } else {
1297
+ const named = at(el(rPr, "highlight"), "val");
1298
+ if (named && named !== "none" && HIGHLIGHT_NAMES[named]) run.highlight = HIGHLIGHT_NAMES[named];
1299
+ }
1300
+ return [run];
1301
+ }
1302
+ // ── Lists ────────────────────────────────────────────────────────────────
1303
+ buildLists(flat) {
1304
+ const blocks = [];
1305
+ let i = 0;
1306
+ while (i < flat.length) {
1307
+ const numId = flat[i].numId;
1308
+ const group = [];
1309
+ while (i < flat.length && flat[i].numId === numId) {
1310
+ group.push(flat[i]);
1311
+ i++;
1312
+ }
1313
+ const ordered = this.numOrdered[numId] ?? false;
1314
+ const items = buildTree2(group);
1315
+ const block = ordered ? { type: "list", ordered: true, items } : { type: "list", items };
1316
+ blocks.push(block);
1317
+ }
1318
+ return blocks;
1319
+ }
1320
+ // ── Tables ───────────────────────────────────────────────────────────────
1321
+ parseTable(tbl, ctx) {
1322
+ const rows = els(tbl, "tr").map((tr) => {
1323
+ const header = el(el(tr, "trPr"), "tblHeader") !== void 0;
1324
+ const cells = els(tr, "tc").map((tc) => ({ blocks: this.walkBody(tc.children, ctx) }));
1325
+ return header ? { header: true, cells } : { cells };
1326
+ });
1327
+ return { type: "table", rows };
1328
+ }
1329
+ // ── Images ───────────────────────────────────────────────────────────────
1330
+ parseDrawing(drawing) {
1331
+ const frame = el(drawing, "inline") ?? el(drawing, "anchor");
1332
+ if (!frame) return [];
1333
+ const blip = findFirst(frame, "blip");
1334
+ const rid = at(blip, "embed") ?? at(blip, "link");
1335
+ const rel = rid ? this.rels[rid] : void 0;
1336
+ if (!rel) return [];
1337
+ const target = rel.target.replace(/^\.\//, "");
1338
+ const partName = target.startsWith("/") ? target.slice(1) : `word/${target}`;
1339
+ const media = this.parts[partName];
1340
+ if (!media) return [];
1341
+ const ext = (partName.split(".").pop() ?? "").toLowerCase();
1342
+ const mime = ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : "image/png";
1343
+ const block = { type: "image", src: `data:${mime};base64,${base64Encode(media)}` };
1344
+ const extent = el(frame, "extent");
1345
+ const cx = parseInt(at(extent, "cx") ?? "0", 10);
1346
+ const cy = parseInt(at(extent, "cy") ?? "0", 10);
1347
+ if (cx > 0) block.widthPx = Math.max(1, Math.round(cx / EMU_PER_PX));
1348
+ if (cy > 0) block.heightPx = Math.max(1, Math.round(cy / EMU_PER_PX));
1349
+ const descr = at(el(frame, "docPr"), "descr");
1350
+ if (descr) block.alt = descr;
1351
+ return [block];
1352
+ }
1353
+ };
1354
+ function onFlag(node) {
1355
+ if (!node) return false;
1356
+ const val = node.attrs["val"];
1357
+ return val === void 0 || !["0", "false", "none", "off"].includes(val);
1358
+ }
1359
+ function findAll(node, name) {
1360
+ const out = [];
1361
+ for (const child of node.children) {
1362
+ if (child.name === name) out.push(child);
1363
+ out.push(...findAll(child, name));
1364
+ }
1365
+ return out;
1366
+ }
1367
+ function findFirst(node, name) {
1368
+ if (!node) return void 0;
1369
+ for (const child of node.children) {
1370
+ if (child.name === name) return child;
1371
+ const nested = findFirst(child, name);
1372
+ if (nested) return nested;
1373
+ }
1374
+ return void 0;
1375
+ }
1376
+ function mergeRuns(runs) {
1377
+ const out = [];
1378
+ for (const run of runs) {
1379
+ if (run.text === "") continue;
1380
+ const prev = out[out.length - 1];
1381
+ if (prev && sameProps(prev, run)) {
1382
+ prev.text += run.text;
1383
+ } else {
1384
+ out.push({ ...run });
1385
+ }
1386
+ }
1387
+ return out;
1388
+ }
1389
+ function sameProps(a, b) {
1390
+ const keys = ["bold", "italic", "underline", "strike", "code", "link", "color", "highlight"];
1391
+ return keys.every((k) => (a[k] ?? void 0) === (b[k] ?? void 0));
1392
+ }
1393
+ function buildTree2(flat) {
1394
+ const root = [];
1395
+ const lastAtDepth = [];
1396
+ for (const entry of flat) {
1397
+ const item = { runs: entry.runs };
1398
+ let depth = Math.max(0, entry.ilvl);
1399
+ while (depth > 0 && lastAtDepth[depth - 1] === void 0) depth--;
1400
+ if (depth === 0) {
1401
+ root.push(item);
1402
+ } else {
1403
+ const parent = lastAtDepth[depth - 1];
1404
+ if (!parent.children) parent.children = [];
1405
+ parent.children.push(item);
1406
+ }
1407
+ lastAtDepth[depth] = item;
1408
+ lastAtDepth.length = depth + 1;
1409
+ }
1410
+ return root;
1411
+ }
1412
+ function orderCodeKeys(block) {
1413
+ const b = block;
1414
+ if (b.language === void 0) return block;
1415
+ return { type: "code", language: b.language, text: b.text };
1416
+ }
1417
+
1418
+ // src/markdown/from-markdown.ts
1419
+ var HEADING_RE = /^(#{1,6})\s+(.*)$/;
1420
+ var HR_RE = /^(-{3,}|\*{3,}|_{3,})\s*$/;
1421
+ var FENCE_RE = /^```(.*)$/;
1422
+ var LIST_RE = /^(\s*)([-*+]|\d{1,9}\.)\s+(.*)$/;
1423
+ var IMAGE_LINE_RE = /^!\[((?:\\.|[^\]\\])*)\]\(([^)]*)\)\s*$/;
1424
+ var TABLE_SEP_RE = /^\s*\|?(\s*:?-{3,}:?\s*\|)*\s*:?-{3,}:?\s*\|?\s*$/;
1425
+ function fromMarkdown(markdown) {
1426
+ const lines = String(markdown ?? "").replace(/\r\n?/g, "\n").split("\n");
1427
+ return { blocks: parseBlocks(lines) };
1428
+ }
1429
+ function parseBlocks(lines) {
1430
+ const blocks = [];
1431
+ let i = 0;
1432
+ while (i < lines.length) {
1433
+ const line = lines[i];
1434
+ if (line.trim() === "") {
1435
+ i++;
1436
+ continue;
1437
+ }
1438
+ const fence = FENCE_RE.exec(line);
1439
+ if (fence) {
1440
+ const language = fence[1].trim();
1441
+ const body = [];
1442
+ i++;
1443
+ while (i < lines.length && !/^```\s*$/.test(lines[i])) {
1444
+ body.push(lines[i]);
1445
+ i++;
1446
+ }
1447
+ i++;
1448
+ const block = { type: "code", text: body.join("\n") };
1449
+ if (language !== "") block.language = language;
1450
+ blocks.push(reorderCode(block));
1451
+ continue;
1452
+ }
1453
+ const heading = HEADING_RE.exec(line);
1454
+ if (heading) {
1455
+ blocks.push({
1456
+ type: "heading",
1457
+ level: heading[1].length,
1458
+ runs: parseInline(heading[2])
1459
+ });
1460
+ i++;
1461
+ continue;
1462
+ }
1463
+ if (HR_RE.test(line)) {
1464
+ blocks.push({ type: "hr" });
1465
+ i++;
1466
+ continue;
1467
+ }
1468
+ if (line.startsWith(">")) {
1469
+ const inner = [];
1470
+ while (i < lines.length && lines[i].startsWith(">")) {
1471
+ inner.push(lines[i].replace(/^> ?/, ""));
1472
+ i++;
1473
+ }
1474
+ blocks.push({ type: "quote", blocks: parseBlocks(inner) });
1475
+ continue;
1476
+ }
1477
+ if (line.trimStart().startsWith("|") && i + 1 < lines.length && TABLE_SEP_RE.test(lines[i + 1])) {
1478
+ const rows = [];
1479
+ const headerCells = splitTableRow(line);
1480
+ rows.push({ header: true, cells: headerCells });
1481
+ i += 2;
1482
+ while (i < lines.length && lines[i].trimStart().startsWith("|")) {
1483
+ rows.push({ cells: splitTableRow(lines[i]) });
1484
+ i++;
1485
+ }
1486
+ blocks.push({ type: "table", rows });
1487
+ continue;
1488
+ }
1489
+ if (LIST_RE.test(line)) {
1490
+ const entries = [];
1491
+ while (i < lines.length) {
1492
+ const m = LIST_RE.exec(lines[i]);
1493
+ if (!m) break;
1494
+ entries.push({
1495
+ indent: m[1].length,
1496
+ ordered: /\d/.test(m[2][0]),
1497
+ content: m[3]
1498
+ });
1499
+ i++;
1500
+ }
1501
+ blocks.push(buildListBlock(entries));
1502
+ continue;
1503
+ }
1504
+ const image = IMAGE_LINE_RE.exec(line);
1505
+ if (image) {
1506
+ const block = { type: "image", src: image[2] };
1507
+ const alt = image[1].replace(/\\([[\]\\])/g, "$1");
1508
+ if (alt !== "") block.alt = alt;
1509
+ blocks.push(block);
1510
+ i++;
1511
+ continue;
1512
+ }
1513
+ const para = [line];
1514
+ i++;
1515
+ while (i < lines.length && lines[i].trim() !== "" && !isBlockStart(lines[i])) {
1516
+ para.push(lines[i]);
1517
+ i++;
1518
+ }
1519
+ blocks.push({ type: "paragraph", runs: parseInline(para.join(" ")) });
1520
+ }
1521
+ return blocks;
1522
+ }
1523
+ function isBlockStart(line) {
1524
+ return HEADING_RE.test(line) || HR_RE.test(line) || FENCE_RE.test(line) || LIST_RE.test(line) || IMAGE_LINE_RE.test(line) || line.startsWith(">") || line.trimStart().startsWith("|");
1525
+ }
1526
+ function buildListBlock(entries) {
1527
+ const root = [];
1528
+ const stack = [
1529
+ { indent: entries[0]?.indent ?? 0, items: root, last: null }
1530
+ ];
1531
+ for (const entry of entries) {
1532
+ while (stack.length > 1 && entry.indent < stack[stack.length - 1].indent) {
1533
+ stack.pop();
1534
+ }
1535
+ let top = stack[stack.length - 1];
1536
+ if (entry.indent > top.indent && top.last) {
1537
+ if (!top.last.children) top.last.children = [];
1538
+ stack.push({ indent: entry.indent, items: top.last.children, last: null });
1539
+ top = stack[stack.length - 1];
1540
+ }
1541
+ const item = { runs: parseInline(entry.content) };
1542
+ top.items.push(item);
1543
+ top.last = item;
1544
+ }
1545
+ const ordered = entries[0]?.ordered === true;
1546
+ return ordered ? { type: "list", ordered: true, items: root } : { type: "list", items: root };
1547
+ }
1548
+ function splitTableRow(line) {
1549
+ let s = line.trim();
1550
+ if (s.startsWith("|")) s = s.slice(1);
1551
+ if (s.endsWith("|") && !s.endsWith("\\|")) s = s.slice(0, -1);
1552
+ const cells = [];
1553
+ let buf = "";
1554
+ for (let i = 0; i < s.length; i++) {
1555
+ const ch = s[i];
1556
+ if (ch === "\\" && i + 1 < s.length) {
1557
+ buf += ch + s[i + 1];
1558
+ i++;
1559
+ continue;
1560
+ }
1561
+ if (ch === "|") {
1562
+ cells.push(buf);
1563
+ buf = "";
1564
+ continue;
1565
+ }
1566
+ buf += ch;
1567
+ }
1568
+ cells.push(buf);
1569
+ return cells.map((cell) => ({ blocks: [{ type: "paragraph", runs: parseInline(cell.trim()) }] }));
1570
+ }
1571
+ var PUNCT_RE = /[!-/:-@[-`{-~]/;
1572
+ var WORD_RE = /[A-Za-z0-9_]/;
1573
+ function parseInline(text) {
1574
+ const runs = [];
1575
+ let buf = "";
1576
+ let bold = false;
1577
+ let italic = false;
1578
+ let strike = false;
1579
+ let i = 0;
1580
+ const len = text.length;
1581
+ const flush = () => {
1582
+ if (buf !== "") {
1583
+ runs.push(makeRun(buf, bold, italic, strike, false));
1584
+ buf = "";
1585
+ }
1586
+ };
1587
+ while (i < len) {
1588
+ const ch = text[i];
1589
+ const two = text.slice(i, i + 2);
1590
+ if (ch === "\\" && i + 1 < len && PUNCT_RE.test(text[i + 1])) {
1591
+ buf += text[i + 1];
1592
+ i += 2;
1593
+ continue;
1594
+ }
1595
+ if (ch === "`") {
1596
+ const fence = /^`+/.exec(text.slice(i))[0];
1597
+ const end = text.indexOf(fence, i + fence.length);
1598
+ if (end >= 0) {
1599
+ flush();
1600
+ let content = text.slice(i + fence.length, end);
1601
+ if (fence.length > 1) content = content.replace(/^ /, "").replace(/ $/, "");
1602
+ runs.push(makeRun(content, bold, italic, strike, true));
1603
+ i = end + fence.length;
1604
+ continue;
1605
+ }
1606
+ buf += ch;
1607
+ i++;
1608
+ continue;
1609
+ }
1610
+ if (two === "**" || two === "__") {
1611
+ flush();
1612
+ bold = !bold;
1613
+ i += 2;
1614
+ continue;
1615
+ }
1616
+ if (two === "~~") {
1617
+ flush();
1618
+ strike = !strike;
1619
+ i += 2;
1620
+ continue;
1621
+ }
1622
+ if (ch === "*") {
1623
+ flush();
1624
+ italic = !italic;
1625
+ i++;
1626
+ continue;
1627
+ }
1628
+ if (ch === "_") {
1629
+ const prev = i > 0 ? text[i - 1] : " ";
1630
+ const next = i + 1 < len ? text[i + 1] : " ";
1631
+ if (!italic && !WORD_RE.test(prev) || italic && !WORD_RE.test(next)) {
1632
+ flush();
1633
+ italic = !italic;
1634
+ i++;
1635
+ continue;
1636
+ }
1637
+ buf += ch;
1638
+ i++;
1639
+ continue;
1640
+ }
1641
+ if (ch === "[") {
1642
+ const match = matchLink(text, i);
1643
+ if (match) {
1644
+ flush();
1645
+ const inner = parseInline(match.label);
1646
+ for (const run of inner) {
1647
+ run.link = match.url;
1648
+ if (bold) run.bold = true;
1649
+ if (italic) run.italic = true;
1650
+ if (strike) run.strike = true;
1651
+ }
1652
+ runs.push(...inner);
1653
+ i = match.end;
1654
+ continue;
1655
+ }
1656
+ buf += ch;
1657
+ i++;
1658
+ continue;
1659
+ }
1660
+ if (ch === "!" && text[i + 1] === "[") {
1661
+ const match = matchLink(text, i + 1);
1662
+ if (match) {
1663
+ buf += match.label.replace(/\\([[\]\\])/g, "$1");
1664
+ i = match.end;
1665
+ continue;
1666
+ }
1667
+ }
1668
+ buf += ch;
1669
+ i++;
1670
+ }
1671
+ flush();
1672
+ return mergeRuns(runs);
1673
+ }
1674
+ function makeRun(text, bold, italic, strike, code, link) {
1675
+ const run = { text };
1676
+ if (bold) run.bold = true;
1677
+ if (italic) run.italic = true;
1678
+ if (strike) run.strike = true;
1679
+ if (code) run.code = true;
1680
+ return run;
1681
+ }
1682
+ function matchLink(text, start) {
1683
+ let depth = 0;
1684
+ let labelEnd = -1;
1685
+ for (let j = start; j < text.length; j++) {
1686
+ const ch = text[j];
1687
+ if (ch === "\\") {
1688
+ j++;
1689
+ continue;
1690
+ }
1691
+ if (ch === "[") depth++;
1692
+ else if (ch === "]") {
1693
+ depth--;
1694
+ if (depth === 0) {
1695
+ labelEnd = j;
1696
+ break;
1697
+ }
1698
+ }
1699
+ }
1700
+ if (labelEnd < 0 || text[labelEnd + 1] !== "(") return null;
1701
+ for (let k = labelEnd + 2; k < text.length; k++) {
1702
+ const ch = text[k];
1703
+ if (ch === "\\") {
1704
+ k++;
1705
+ continue;
1706
+ }
1707
+ if (ch === ")") {
1708
+ return { label: text.slice(start + 1, labelEnd), url: text.slice(labelEnd + 2, k), end: k + 1 };
1709
+ }
1710
+ }
1711
+ return null;
1712
+ }
1713
+ function reorderCode(block) {
1714
+ if (block.language === void 0) return block;
1715
+ return { type: "code", language: block.language, text: block.text };
1716
+ }
1717
+
1718
+ // src/markdown/to-markdown.ts
1719
+ function toMarkdown(doc) {
1720
+ const chunks = [];
1721
+ for (const block of doc.blocks ?? []) {
1722
+ const md = blockToMarkdown(block);
1723
+ if (md !== null) chunks.push(md);
1724
+ }
1725
+ return chunks.join("\n\n") + (chunks.length > 0 ? "\n" : "");
1726
+ }
1727
+ function blockToMarkdown(block) {
1728
+ switch (block?.type) {
1729
+ case "heading":
1730
+ return "#".repeat(clamp(block.level)) + " " + inline(block.runs ?? []);
1731
+ case "paragraph":
1732
+ return guardLineStart(inline(block.runs ?? []).replace(/\n/g, " "));
1733
+ case "list":
1734
+ return listToMarkdown(block.items ?? [], block.ordered === true, 0).join("\n");
1735
+ case "table":
1736
+ return tableToMarkdown(block);
1737
+ case "code": {
1738
+ const lang = typeof block.language === "string" ? block.language : "";
1739
+ return "```" + lang + "\n" + String(block.text ?? "") + "\n```";
1740
+ }
1741
+ case "quote": {
1742
+ const inner = (block.blocks ?? []).map((b) => blockToMarkdown(b)).filter((s) => s !== null).join("\n\n");
1743
+ return inner.split("\n").map((line) => line === "" ? ">" : "> " + line).join("\n");
1744
+ }
1745
+ case "image": {
1746
+ const alt = typeof block.alt === "string" ? block.alt : "";
1747
+ return `![${alt.replace(/([[\]\\])/g, "\\$1")}](${String(block.src ?? "")})`;
1748
+ }
1749
+ case "hr":
1750
+ return "---";
1751
+ case "pageBreak":
1752
+ return null;
1753
+ // no GFM equivalent
1754
+ default:
1755
+ return null;
1756
+ }
1757
+ }
1758
+ function listToMarkdown(items, ordered, depth) {
1759
+ const indent = (ordered ? " " : " ").repeat(depth);
1760
+ const lines = [];
1761
+ items.forEach((item, i) => {
1762
+ const marker = ordered ? `${i + 1}. ` : "- ";
1763
+ lines.push(indent + marker + inline(item.runs ?? []).replace(/\n/g, " "));
1764
+ if (item.children && item.children.length > 0) {
1765
+ lines.push(...listToMarkdown(item.children, ordered, depth + 1));
1766
+ }
1767
+ });
1768
+ return lines;
1769
+ }
1770
+ function tableToMarkdown(block) {
1771
+ const rows = block.rows ?? [];
1772
+ if (rows.length === 0) return "";
1773
+ const cols = Math.max(1, ...rows.map((r) => Array.isArray(r?.cells) ? r.cells.length : 0));
1774
+ const lineOf = (row) => {
1775
+ const cells = Array.isArray(row?.cells) ? row.cells : [];
1776
+ const rendered = [];
1777
+ for (let c = 0; c < cols; c++) {
1778
+ rendered.push(cellInline(cells[c]));
1779
+ }
1780
+ return "| " + rendered.join(" | ") + " |";
1781
+ };
1782
+ const lines = [];
1783
+ lines.push(lineOf(rows[0]));
1784
+ lines.push("| " + Array.from({ length: cols }, () => "---").join(" | ") + " |");
1785
+ for (let r = 1; r < rows.length; r++) lines.push(lineOf(rows[r]));
1786
+ return lines.join("\n");
1787
+ }
1788
+ function cellInline(cell) {
1789
+ const blocks = Array.isArray(cell?.blocks) ? cell.blocks : [];
1790
+ const parts = [];
1791
+ for (const b of blocks) {
1792
+ if (b?.type === "paragraph" || b?.type === "heading") parts.push(inline(b.runs ?? []));
1793
+ else if (b?.type === "code") parts.push("`" + String(b.text ?? "").replace(/\n/g, " ") + "`");
1794
+ else {
1795
+ const md = blockToMarkdown(b);
1796
+ if (md !== null) parts.push(md.replace(/\n/g, " "));
1797
+ }
1798
+ }
1799
+ return parts.join(" ").replace(/\|/g, "\\|").replace(/\n/g, " ");
1800
+ }
1801
+ function inline(runs) {
1802
+ let out = "";
1803
+ for (const run of runs) {
1804
+ out += runToMarkdown(run);
1805
+ }
1806
+ return out;
1807
+ }
1808
+ function runToMarkdown(run) {
1809
+ if (typeof run?.text !== "string") return "";
1810
+ let s = run.code ? codeSpan(run.text) : escapeText(run.text);
1811
+ if (run.bold) s = `**${s}**`;
1812
+ if (run.italic) s = `*${s}*`;
1813
+ if (run.strike) s = `~~${s}~~`;
1814
+ if (run.link) s = `[${s}](${run.link})`;
1815
+ return s;
1816
+ }
1817
+ function codeSpan(text) {
1818
+ if (!text.includes("`")) return "`" + text + "`";
1819
+ return "`` " + text + " ``";
1820
+ }
1821
+ function escapeText(text) {
1822
+ return text.replace(/([\\`*_~[\]])/g, "\\$1");
1823
+ }
1824
+ function guardLineStart(text) {
1825
+ if (/^(#{1,6} |[-*+] |\d+\. |>|(-{3,}|\*{3,}|_{3,})$|\|)/.test(text)) {
1826
+ return "\\" + text;
1827
+ }
1828
+ return text;
1829
+ }
1830
+ function clamp(level) {
1831
+ const n = Number.isFinite(Number(level)) ? Math.trunc(Number(level)) : 1;
1832
+ return Math.min(6, Math.max(1, n));
1833
+ }
1834
+
1835
+ // src/util.ts
1836
+ function isPlainObject(value) {
1837
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1838
+ }
1839
+ function isNumeric(v) {
1840
+ if (typeof v === "number") return Number.isFinite(v);
1841
+ if (typeof v !== "string") return false;
1842
+ return /^\s*[+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?$/.test(v);
1843
+ }
1844
+ function clone(v) {
1845
+ return typeof structuredClone === "function" ? structuredClone(v) : JSON.parse(JSON.stringify(v));
1846
+ }
1847
+
1848
+ // src/schema/schema.ts
1849
+ var Schema = {
1850
+ VERSION: "0.1.0",
1851
+ BLOCK_TYPES: [
1852
+ "heading",
1853
+ "paragraph",
1854
+ "list",
1855
+ "table",
1856
+ "code",
1857
+ "quote",
1858
+ "image",
1859
+ "pageBreak",
1860
+ "hr"
1861
+ ],
1862
+ ALIGNMENTS: ["left", "center", "right", "justify"],
1863
+ MAX_HEADING_LEVEL: 6,
1864
+ docRequiredKeys() {
1865
+ return ["blocks"];
1866
+ },
1867
+ jsonSchema() {
1868
+ const runSchema = {
1869
+ type: "object",
1870
+ required: ["text"],
1871
+ properties: {
1872
+ text: { type: "string" },
1873
+ bold: { type: "boolean" },
1874
+ italic: { type: "boolean" },
1875
+ underline: { type: "boolean" },
1876
+ strike: { type: "boolean" },
1877
+ code: { type: "boolean" },
1878
+ link: { type: "string" },
1879
+ color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$" },
1880
+ highlight: { type: "string", pattern: "^#[0-9a-fA-F]{6}$" }
1881
+ }
1882
+ };
1883
+ return {
1884
+ $schema: "http://json-schema.org/draft-07/schema#",
1885
+ title: "LastWord Doc",
1886
+ type: "object",
1887
+ required: this.docRequiredKeys(),
1888
+ properties: {
1889
+ title: { type: "string" },
1890
+ blocks: { type: "array", items: { $ref: "#/definitions/block" } }
1891
+ },
1892
+ definitions: {
1893
+ run: runSchema,
1894
+ listItem: {
1895
+ type: "object",
1896
+ required: ["runs"],
1897
+ properties: {
1898
+ runs: { type: "array", items: { $ref: "#/definitions/run" } },
1899
+ children: { type: "array", items: { $ref: "#/definitions/listItem" } }
1900
+ }
1901
+ },
1902
+ block: {
1903
+ type: "object",
1904
+ required: ["type"],
1905
+ properties: {
1906
+ type: { type: "string", enum: [...this.BLOCK_TYPES] },
1907
+ // heading
1908
+ level: { type: "integer", minimum: 1, maximum: this.MAX_HEADING_LEVEL },
1909
+ // heading + paragraph
1910
+ runs: { type: "array", items: { $ref: "#/definitions/run" } },
1911
+ align: { type: "string", enum: [...this.ALIGNMENTS] },
1912
+ // list
1913
+ ordered: { type: "boolean" },
1914
+ items: { type: "array", items: { $ref: "#/definitions/listItem" } },
1915
+ // table
1916
+ rows: {
1917
+ type: "array",
1918
+ items: {
1919
+ type: "object",
1920
+ required: ["cells"],
1921
+ properties: {
1922
+ header: { type: "boolean" },
1923
+ cells: {
1924
+ type: "array",
1925
+ items: {
1926
+ type: "object",
1927
+ required: ["blocks"],
1928
+ properties: {
1929
+ blocks: { type: "array", items: { $ref: "#/definitions/block" } }
1930
+ }
1931
+ }
1932
+ }
1933
+ }
1934
+ }
1935
+ },
1936
+ // code
1937
+ language: { type: "string" },
1938
+ text: { type: "string" },
1939
+ // quote
1940
+ blocks: { type: "array", items: { $ref: "#/definitions/block" } },
1941
+ // image
1942
+ src: { type: "string" },
1943
+ widthPx: { type: "number", exclusiveMinimum: 0 },
1944
+ heightPx: { type: "number", exclusiveMinimum: 0 },
1945
+ alt: { type: "string" }
1946
+ }
1947
+ }
1948
+ }
1949
+ };
1950
+ }
1951
+ };
1952
+
1953
+ // src/schema/repairer.ts
1954
+ var Repairer = class {
1955
+ constructor() {
1956
+ /** Errors describing content the last `repair()` call had to drop. */
1957
+ this.notes = [];
1958
+ }
1959
+ repair(docInput) {
1960
+ this.notes = [];
1961
+ const doc = isPlainObject(docInput) ? clone(docInput) : {};
1962
+ if (doc.title !== void 0 && typeof doc.title !== "string") {
1963
+ doc.title = String(doc.title);
1964
+ }
1965
+ doc.blocks = this.repairBlocks(doc.blocks ?? [], "/blocks");
1966
+ return doc;
1967
+ }
1968
+ repairBlocks(blocks, path) {
1969
+ if (!Array.isArray(blocks)) return [];
1970
+ const out = [];
1971
+ blocks.forEach((block, i) => {
1972
+ const repaired = this.repairBlock(block, `${path}/${i}`);
1973
+ if (repaired !== null) out.push(repaired);
1974
+ });
1975
+ return out;
1976
+ }
1977
+ repairBlock(block, path) {
1978
+ if (typeof block === "string") {
1979
+ return { type: "paragraph", runs: [{ text: block }] };
1980
+ }
1981
+ if (!isPlainObject(block)) {
1982
+ this.notes.push({ path, message: "Dropped non-object block during repair." });
1983
+ return null;
1984
+ }
1985
+ if (typeof block.type !== "string" || !Schema.BLOCK_TYPES.includes(block.type)) {
1986
+ if (typeof block.text === "string") {
1987
+ return { type: "paragraph", runs: [{ text: block.text }] };
1988
+ }
1989
+ this.notes.push({
1990
+ path: `${path}/type`,
1991
+ message: `Dropped block with unknown type \`${String(block.type)}\` during repair.`
1992
+ });
1993
+ return null;
1994
+ }
1995
+ switch (block.type) {
1996
+ case "heading": {
1997
+ let level = isNumeric(block.level) ? Math.trunc(Number(block.level)) : 1;
1998
+ if (level < 1) level = 1;
1999
+ if (level > Schema.MAX_HEADING_LEVEL) level = Schema.MAX_HEADING_LEVEL;
2000
+ block.level = level;
2001
+ block.runs = this.repairRuns(block.runs);
2002
+ break;
2003
+ }
2004
+ case "paragraph":
2005
+ block.runs = this.repairRuns(block.runs);
2006
+ if (block.align !== void 0 && !Schema.ALIGNMENTS.includes(block.align)) {
2007
+ delete block.align;
2008
+ }
2009
+ break;
2010
+ case "list":
2011
+ if (block.ordered !== void 0) block.ordered = Boolean(block.ordered);
2012
+ block.items = this.repairListItems(block.items);
2013
+ break;
2014
+ case "table":
2015
+ block.rows = this.repairRows(block.rows, path);
2016
+ break;
2017
+ case "code":
2018
+ block.text = typeof block.text === "string" ? block.text : String(block.text ?? "");
2019
+ if (block.language !== void 0 && typeof block.language !== "string") delete block.language;
2020
+ break;
2021
+ case "quote":
2022
+ block.blocks = this.repairBlocks(block.blocks ?? [], `${path}/blocks`);
2023
+ break;
2024
+ case "image":
2025
+ if (typeof block.src !== "string" || !block.src.startsWith("data:image/")) {
2026
+ this.notes.push({ path: `${path}/src`, message: "Dropped image without a usable data URL during repair." });
2027
+ return null;
2028
+ }
2029
+ for (const key of ["widthPx", "heightPx"]) {
2030
+ if (block[key] !== void 0 && (!isNumeric(block[key]) || Number(block[key]) <= 0)) {
2031
+ delete block[key];
2032
+ } else if (block[key] !== void 0) {
2033
+ block[key] = Number(block[key]);
2034
+ }
2035
+ }
2036
+ if (block.alt !== void 0 && typeof block.alt !== "string") delete block.alt;
2037
+ break;
2038
+ }
2039
+ return block;
2040
+ }
2041
+ repairRuns(runs) {
2042
+ if (typeof runs === "string") return [{ text: runs }];
2043
+ if (!Array.isArray(runs)) return [];
2044
+ const out = [];
2045
+ for (const run of runs) {
2046
+ if (typeof run === "string") {
2047
+ out.push({ text: run });
2048
+ continue;
2049
+ }
2050
+ if (!isPlainObject(run)) continue;
2051
+ if (typeof run.text !== "string") run.text = String(run.text ?? "");
2052
+ for (const flag of ["bold", "italic", "underline", "strike", "code"]) {
2053
+ if (run[flag] !== void 0 && typeof run[flag] !== "boolean") run[flag] = Boolean(run[flag]);
2054
+ }
2055
+ if (run.link !== void 0 && typeof run.link !== "string") delete run.link;
2056
+ for (const key of ["color", "highlight"]) {
2057
+ if (run[key] !== void 0 && (typeof run[key] !== "string" || !/^#[0-9a-fA-F]{6}$/.test(run[key]))) {
2058
+ delete run[key];
2059
+ }
2060
+ }
2061
+ out.push(run);
2062
+ }
2063
+ return out;
2064
+ }
2065
+ repairListItems(items) {
2066
+ if (!Array.isArray(items)) return [];
2067
+ const out = [];
2068
+ for (const item of items) {
2069
+ if (typeof item === "string") {
2070
+ out.push({ runs: [{ text: item }] });
2071
+ continue;
2072
+ }
2073
+ if (!isPlainObject(item)) continue;
2074
+ item.runs = this.repairRuns(item.runs);
2075
+ if (item.children !== void 0) {
2076
+ const children = this.repairListItems(item.children);
2077
+ if (children.length > 0) item.children = children;
2078
+ else delete item.children;
2079
+ }
2080
+ out.push(item);
2081
+ }
2082
+ return out;
2083
+ }
2084
+ repairRows(rows, path) {
2085
+ if (!Array.isArray(rows)) return [];
2086
+ const out = [];
2087
+ rows.forEach((row, r) => {
2088
+ if (!isPlainObject(row)) return;
2089
+ if (row.header !== void 0) row.header = Boolean(row.header);
2090
+ const cells = Array.isArray(row.cells) ? row.cells : [];
2091
+ row.cells = cells.filter((cell) => isPlainObject(cell) || typeof cell === "string").map((cell, c) => {
2092
+ if (typeof cell === "string") {
2093
+ return { blocks: [{ type: "paragraph", runs: [{ text: cell }] }] };
2094
+ }
2095
+ cell.blocks = this.repairBlocks(cell.blocks ?? [], `${path}/rows/${r}/cells/${c}/blocks`);
2096
+ return cell;
2097
+ });
2098
+ out.push(row);
2099
+ });
2100
+ return out;
2101
+ }
2102
+ };
2103
+
2104
+ // src/schema/validator.ts
2105
+ function err(path, message) {
2106
+ return { path, message };
2107
+ }
2108
+ var Validator = class {
2109
+ validate(doc) {
2110
+ const errors = [];
2111
+ if (!isPlainObject(doc)) {
2112
+ return [err("/", "Doc must be a JSON object with a `blocks` array.")];
2113
+ }
2114
+ if (doc.title !== void 0 && typeof doc.title !== "string") {
2115
+ errors.push(err("/title", "Doc title must be a string."));
2116
+ }
2117
+ if (!("blocks" in doc)) {
2118
+ errors.push(err("/blocks", "Doc must have a `blocks` array."));
2119
+ } else if (!Array.isArray(doc.blocks)) {
2120
+ errors.push(err("/blocks", "Doc blocks must be a JSON array."));
2121
+ } else {
2122
+ doc.blocks.forEach((block, i) => {
2123
+ errors.push(...this.validateBlock(block, `/blocks/${i}`));
2124
+ });
2125
+ }
2126
+ return errors;
2127
+ }
2128
+ validateBlock(block, path) {
2129
+ const errors = [];
2130
+ if (!isPlainObject(block)) {
2131
+ return [err(path, "Each block must be a JSON object with a `type` field.")];
2132
+ }
2133
+ if (typeof block.type !== "string") {
2134
+ return [err(`${path}/type`, "Block must have a string `type` field.")];
2135
+ }
2136
+ if (!Schema.BLOCK_TYPES.includes(block.type)) {
2137
+ return [err(`${path}/type`, `Unknown block type \`${block.type}\`.`)];
2138
+ }
2139
+ switch (block.type) {
2140
+ case "heading":
2141
+ if (!Number.isInteger(block.level) || block.level < 1 || block.level > Schema.MAX_HEADING_LEVEL) {
2142
+ errors.push(err(`${path}/level`, `Heading level must be an integer between 1 and ${Schema.MAX_HEADING_LEVEL}.`));
2143
+ }
2144
+ errors.push(...this.validateRuns(block.runs, `${path}/runs`));
2145
+ break;
2146
+ case "paragraph":
2147
+ errors.push(...this.validateRuns(block.runs, `${path}/runs`));
2148
+ if (block.align !== void 0 && !Schema.ALIGNMENTS.includes(block.align)) {
2149
+ errors.push(err(`${path}/align`, "Paragraph align must be left, center, right, or justify."));
2150
+ }
2151
+ break;
2152
+ case "list":
2153
+ if (block.ordered !== void 0 && typeof block.ordered !== "boolean") {
2154
+ errors.push(err(`${path}/ordered`, "List `ordered` must be a boolean."));
2155
+ }
2156
+ if (!Array.isArray(block.items)) {
2157
+ errors.push(err(`${path}/items`, "List must have an `items` array."));
2158
+ } else {
2159
+ block.items.forEach((item, i) => {
2160
+ errors.push(...this.validateListItem(item, `${path}/items/${i}`));
2161
+ });
2162
+ }
2163
+ break;
2164
+ case "table":
2165
+ if (!Array.isArray(block.rows)) {
2166
+ errors.push(err(`${path}/rows`, "Table must have a `rows` array."));
2167
+ } else {
2168
+ block.rows.forEach((row, r) => {
2169
+ const rowPath = `${path}/rows/${r}`;
2170
+ if (!isPlainObject(row)) {
2171
+ errors.push(err(rowPath, "Each table row must be a JSON object with a `cells` array."));
2172
+ return;
2173
+ }
2174
+ if (row.header !== void 0 && typeof row.header !== "boolean") {
2175
+ errors.push(err(`${rowPath}/header`, "Row `header` must be a boolean."));
2176
+ }
2177
+ if (!Array.isArray(row.cells)) {
2178
+ errors.push(err(`${rowPath}/cells`, "Table row must have a `cells` array."));
2179
+ return;
2180
+ }
2181
+ row.cells.forEach((cell, c) => {
2182
+ const cellPath = `${rowPath}/cells/${c}`;
2183
+ if (!isPlainObject(cell)) {
2184
+ errors.push(err(cellPath, "Each table cell must be a JSON object with a `blocks` array."));
2185
+ return;
2186
+ }
2187
+ if (!Array.isArray(cell.blocks)) {
2188
+ errors.push(err(`${cellPath}/blocks`, "Table cell must have a `blocks` array."));
2189
+ return;
2190
+ }
2191
+ cell.blocks.forEach((b, i) => {
2192
+ errors.push(...this.validateBlock(b, `${cellPath}/blocks/${i}`));
2193
+ });
2194
+ });
2195
+ });
2196
+ }
2197
+ break;
2198
+ case "code":
2199
+ if (typeof block.text !== "string") {
2200
+ errors.push(err(`${path}/text`, "Code block must have a string `text` field."));
2201
+ }
2202
+ if (block.language !== void 0 && typeof block.language !== "string") {
2203
+ errors.push(err(`${path}/language`, "Code block `language` must be a string."));
2204
+ }
2205
+ break;
2206
+ case "quote":
2207
+ if (!Array.isArray(block.blocks)) {
2208
+ errors.push(err(`${path}/blocks`, "Quote must have a `blocks` array."));
2209
+ } else {
2210
+ block.blocks.forEach((b, i) => {
2211
+ errors.push(...this.validateBlock(b, `${path}/blocks/${i}`));
2212
+ });
2213
+ }
2214
+ break;
2215
+ case "image":
2216
+ if (typeof block.src !== "string" || block.src === "") {
2217
+ errors.push(err(`${path}/src`, "Image must have a `src` data URL string."));
2218
+ } else if (!block.src.startsWith("data:image/")) {
2219
+ errors.push(err(`${path}/src`, "Image `src` must be a data:image/png or data:image/jpeg data URL."));
2220
+ }
2221
+ for (const key of ["widthPx", "heightPx"]) {
2222
+ if (block[key] !== void 0 && (typeof block[key] !== "number" || !(block[key] > 0))) {
2223
+ errors.push(err(`${path}/${key}`, `Image \`${key}\` must be a positive number.`));
2224
+ }
2225
+ }
2226
+ if (block.alt !== void 0 && typeof block.alt !== "string") {
2227
+ errors.push(err(`${path}/alt`, "Image `alt` must be a string."));
2228
+ }
2229
+ break;
2230
+ }
2231
+ return errors;
2232
+ }
2233
+ validateRuns(runs, path) {
2234
+ const errors = [];
2235
+ if (!Array.isArray(runs)) {
2236
+ return [err(path, "Must be an array of runs (`{ text, bold?, italic?, \u2026 }`).")];
2237
+ }
2238
+ runs.forEach((run, i) => {
2239
+ errors.push(...this.validateRun(run, `${path}/${i}`));
2240
+ });
2241
+ return errors;
2242
+ }
2243
+ validateRun(run, path) {
2244
+ const errors = [];
2245
+ if (!isPlainObject(run)) {
2246
+ return [err(path, "Each run must be a JSON object with a `text` field.")];
2247
+ }
2248
+ if (typeof run.text !== "string") {
2249
+ errors.push(err(`${path}/text`, "Run must have a string `text` field."));
2250
+ }
2251
+ for (const flag of ["bold", "italic", "underline", "strike", "code"]) {
2252
+ if (run[flag] !== void 0 && typeof run[flag] !== "boolean") {
2253
+ errors.push(err(`${path}/${flag}`, `Run \`${flag}\` must be a boolean.`));
2254
+ }
2255
+ }
2256
+ if (run.link !== void 0 && typeof run.link !== "string") {
2257
+ errors.push(err(`${path}/link`, "Run `link` must be a URL string."));
2258
+ }
2259
+ for (const key of ["color", "highlight"]) {
2260
+ if (run[key] !== void 0 && (typeof run[key] !== "string" || !/^#[0-9a-fA-F]{6}$/.test(run[key]))) {
2261
+ errors.push(err(`${path}/${key}`, `Run \`${key}\` must be a #RRGGBB hex string.`));
2262
+ }
2263
+ }
2264
+ return errors;
2265
+ }
2266
+ validateListItem(item, path) {
2267
+ const errors = [];
2268
+ if (!isPlainObject(item)) {
2269
+ return [err(path, "Each list item must be a JSON object with a `runs` array.")];
2270
+ }
2271
+ errors.push(...this.validateRuns(item.runs, `${path}/runs`));
2272
+ if (item.children !== void 0) {
2273
+ if (!Array.isArray(item.children)) {
2274
+ errors.push(err(`${path}/children`, "List item `children` must be an array of list items."));
2275
+ } else {
2276
+ item.children.forEach((child, i) => {
2277
+ errors.push(...this.validateListItem(child, `${path}/children/${i}`));
2278
+ });
2279
+ }
2280
+ }
2281
+ return errors;
2282
+ }
2283
+ };
2284
+
2285
+ // src/agent.ts
2286
+ var VERSION = "0.1.0";
2287
+ function toU8(input) {
2288
+ return input instanceof Uint8Array ? input : new Uint8Array(input);
2289
+ }
2290
+ function assertValid(doc) {
2291
+ const errors = new Validator().validate(doc);
2292
+ if (errors.length > 0) {
2293
+ throw new SchemaException(
2294
+ "Doc failed schema validation. Call Agent.validateAndRepair() for a recoverable form.",
2295
+ errors
2296
+ );
2297
+ }
2298
+ }
2299
+ var Agent = {
2300
+ /** Validate a doc without writing. Empty array = valid. */
2301
+ validate(doc) {
2302
+ return new Validator().validate(doc);
2303
+ },
2304
+ /**
2305
+ * Validate + apply heuristic repairs (coerce strings to runs, clamp heading
2306
+ * levels, drop unknown block types with the error retained, default missing
2307
+ * blocks to []). Returns `{ ok, schema, errors }` where `ok` is true when
2308
+ * the repaired doc validates clean; `errors` retains anything the repair
2309
+ * dropped plus any remaining validation errors.
2310
+ */
2311
+ validateAndRepair(doc) {
2312
+ const errors = this.validate(doc);
2313
+ if (errors.length === 0) {
2314
+ return { ok: true, schema: doc, errors: [] };
2315
+ }
2316
+ const repairer = new Repairer();
2317
+ const repaired = repairer.repair(doc);
2318
+ const remaining = this.validate(repaired);
2319
+ return {
2320
+ ok: remaining.length === 0,
2321
+ schema: repaired,
2322
+ errors: [...repairer.notes, ...remaining]
2323
+ };
2324
+ },
2325
+ /** DOCX bytes for a doc (no temp file). Universal. Throws SchemaException if invalid. */
2326
+ toBytes(doc) {
2327
+ assertValid(doc);
2328
+ return new DocxWriter().toBytes(doc);
2329
+ },
2330
+ /** Write a doc to disk as a .docx file (Node only). Throws SchemaException if invalid. */
2331
+ async write(doc, path) {
2332
+ assertValid(doc);
2333
+ const bytes = new DocxWriter().toBytes(doc);
2334
+ const fs = await import('fs');
2335
+ fs.writeFileSync(path, bytes);
2336
+ return { path, bytes: bytes.length, blocks: doc?.blocks?.length ?? 0 };
2337
+ },
2338
+ /** Read .docx bytes back into the Doc model. Universal. */
2339
+ read(input) {
2340
+ return new DocxReader().read(toU8(input));
2341
+ },
2342
+ /** Alias for {@see read}. */
2343
+ fromBytes(input) {
2344
+ return new DocxReader().read(toU8(input));
2345
+ },
2346
+ /** Doc → GFM markdown (the Editor bridge). */
2347
+ toMarkdown(doc) {
2348
+ return toMarkdown(doc);
2349
+ },
2350
+ /** GFM markdown → Doc (the Editor bridge). */
2351
+ fromMarkdown(markdown) {
2352
+ return fromMarkdown(markdown);
2353
+ },
2354
+ /** Plain-text summary of a doc: title, block counts by type, word count. */
2355
+ describe(doc) {
2356
+ const title = String(doc?.title ?? "Untitled");
2357
+ const blocks = Array.isArray(doc?.blocks) ? doc.blocks : [];
2358
+ const counts = {};
2359
+ for (const block of blocks) {
2360
+ const type = String(block?.type ?? "unknown");
2361
+ counts[type] = (counts[type] ?? 0) + 1;
2362
+ }
2363
+ const lines = [`Doc: ${title}`, `Blocks: ${blocks.length}`];
2364
+ const keys = Object.keys(counts);
2365
+ if (keys.length > 0) {
2366
+ lines.push("Kinds: " + keys.map((type) => `${counts[type]} ${type}`).join(", "));
2367
+ }
2368
+ lines.push(`Words: ${countWords(blocks)}`);
2369
+ return lines.join("\n");
2370
+ },
2371
+ /** JSON Schema export for LLM tool-use registration. */
2372
+ jsonSchema() {
2373
+ return Schema.jsonSchema();
2374
+ },
2375
+ version() {
2376
+ return VERSION;
2377
+ }
2378
+ };
2379
+ function countWords(blocks) {
2380
+ let text = "";
2381
+ const visitRuns = (runs) => {
2382
+ for (const run of runs ?? []) {
2383
+ if (typeof run?.text === "string") text += run.text + " ";
2384
+ }
2385
+ };
2386
+ const visitItems = (items) => {
2387
+ for (const item of items ?? []) {
2388
+ visitRuns(item.runs);
2389
+ visitItems(item.children);
2390
+ }
2391
+ };
2392
+ const visit = (list) => {
2393
+ for (const block of list ?? []) {
2394
+ switch (block?.type) {
2395
+ case "heading":
2396
+ case "paragraph":
2397
+ visitRuns(block.runs);
2398
+ break;
2399
+ case "list":
2400
+ visitItems(block.items);
2401
+ break;
2402
+ case "table":
2403
+ for (const row of block.rows ?? []) {
2404
+ for (const cell of row?.cells ?? []) visit(cell?.blocks ?? []);
2405
+ }
2406
+ break;
2407
+ case "quote":
2408
+ visit(block.blocks ?? []);
2409
+ break;
2410
+ }
2411
+ }
2412
+ };
2413
+ visit(blocks);
2414
+ return text.split(/\s+/).filter(Boolean).length;
2415
+ }
2416
+
2417
+ export { Agent, DocxReader, DocxWriter, EMU_PER_PX, MAX_WIDTH_PX, Repairer, Schema, SchemaException, VERSION, Validator, fromMarkdown, jpegSize, mergeRuns, parseDataUrl, parseInline, pngSize, resolveImageSize, sniffImageSize, toMarkdown, unzipSync, zipSync };
2418
+ //# sourceMappingURL=index.js.map
2419
+ //# sourceMappingURL=index.js.map