kordoc 3.13.0 → 3.16.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.
Files changed (54) hide show
  1. package/README.md +34 -1
  2. package/dist/{-MRPDPE2X.js → -H7N62NZU.js} +24 -13
  3. package/dist/{chunk-33CIKNL4.js → chunk-5RHRQ7HK.js} +479 -258
  4. package/dist/chunk-5RHRQ7HK.js.map +1 -0
  5. package/dist/{chunk-MEXUFQVC.cjs → chunk-BIP4JAVG.cjs} +5 -5
  6. package/dist/chunk-BIP4JAVG.cjs.map +1 -0
  7. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  8. package/dist/chunk-FGL7BH4P.js +1019 -0
  9. package/dist/chunk-FGL7BH4P.js.map +1 -0
  10. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  11. package/dist/{chunk-Q6UXVJXI.js → chunk-IC56KHBL.js} +2 -2
  12. package/dist/chunk-JFNWJFKP.js +185 -0
  13. package/dist/chunk-JFNWJFKP.js.map +1 -0
  14. package/dist/{chunk-YXOT7P5B.js → chunk-LYL4IL3E.js} +5 -5
  15. package/dist/{chunk-YXOT7P5B.js.map → chunk-LYL4IL3E.js.map} +1 -1
  16. package/dist/{chunk-O4WFPOFN.js → chunk-Q7EGGW3H.js} +5 -5
  17. package/dist/{chunk-O4WFPOFN.js.map → chunk-Q7EGGW3H.js.map} +1 -1
  18. package/dist/{chunk-TWACKS3K.js → chunk-WD5NUUU6.js} +571 -729
  19. package/dist/chunk-WD5NUUU6.js.map +1 -0
  20. package/dist/cli.js +125 -12
  21. package/dist/cli.js.map +1 -1
  22. package/dist/formula-5NKVS2LR.cjs.map +1 -1
  23. package/dist/index.cjs +1668 -449
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +144 -6
  26. package/dist/index.d.ts +144 -6
  27. package/dist/index.js +1502 -283
  28. package/dist/index.js.map +1 -1
  29. package/dist/mcp.js +93 -12
  30. package/dist/mcp.js.map +1 -1
  31. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  32. package/dist/{parser-Z3IL2ESY.js → parser-5G247XYD.js} +54 -6
  33. package/dist/parser-5G247XYD.js.map +1 -0
  34. package/dist/{parser-DZBI3EA2.cjs → parser-6V7F3GET.cjs} +66 -18
  35. package/dist/parser-6V7F3GET.cjs.map +1 -0
  36. package/dist/{parser-XUNGOH76.js → parser-HBHCD7TE.js} +55 -7
  37. package/dist/parser-HBHCD7TE.js.map +1 -0
  38. package/dist/provider-G4C2V2PD.cjs.map +1 -1
  39. package/dist/render-VA4NHROW.js +10 -0
  40. package/dist/seal-UVDRGHEF.js +10 -0
  41. package/dist/seal-UVDRGHEF.js.map +1 -0
  42. package/dist/{watch-G5NFIT5L.js → watch-HIYNOSFD.js} +7 -5
  43. package/dist/{watch-G5NFIT5L.js.map → watch-HIYNOSFD.js.map} +1 -1
  44. package/package.json +4 -3
  45. package/dist/chunk-33CIKNL4.js.map +0 -1
  46. package/dist/chunk-MEXUFQVC.cjs.map +0 -1
  47. package/dist/chunk-TWACKS3K.js.map +0 -1
  48. package/dist/parser-DZBI3EA2.cjs.map +0 -1
  49. package/dist/parser-XUNGOH76.js.map +0 -1
  50. package/dist/parser-Z3IL2ESY.js.map +0 -1
  51. package/dist/render-VZ4OGHZU.js +0 -9
  52. /package/dist/{-MRPDPE2X.js.map → -H7N62NZU.js.map} +0 -0
  53. /package/dist/{chunk-Q6UXVJXI.js.map → chunk-IC56KHBL.js.map} +0 -0
  54. /package/dist/{render-VZ4OGHZU.js.map → render-VA4NHROW.js.map} +0 -0
@@ -1,62 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_CHAR,
4
+ DEFAULT_PARA_GEOM,
5
+ MAX_DECOMPRESS_SIZE,
6
+ createXmlParser,
7
+ findChildByLocalName,
8
+ parseRenderStyles
9
+ } from "./chunk-JFNWJFKP.js";
2
10
  import {
3
11
  KordocError
4
- } from "./chunk-Q6UXVJXI.js";
12
+ } from "./chunk-IC56KHBL.js";
5
13
 
6
14
  // src/render/svg-render.ts
7
15
  import JSZip from "jszip";
8
16
 
9
- // src/hwpx/parser-shared.ts
10
- import { DOMParser } from "@xmldom/xmldom";
11
- var MAX_DECOMPRESS_SIZE = 100 * 1024 * 1024;
12
- var MAX_ZIP_ENTRIES = 500;
13
- function clampSpan(val, max) {
14
- return Math.max(1, Math.min(val, max));
15
- }
16
- var MAX_XML_DEPTH = 200;
17
- function createSectionShared() {
18
- return { numState: /* @__PURE__ */ new Map(), pageText: { headers: [], footers: [] }, track: { deleteDepth: 0, warned: false } };
19
- }
20
- function createXmlParser(warnings) {
21
- return new DOMParser({
22
- onError(level, msg) {
23
- if (level === "fatalError") throw new KordocError(`XML \uD30C\uC2F1 \uC2E4\uD328: ${msg}`);
24
- warnings?.push({ code: "MALFORMED_XML", message: `XML ${level === "warn" ? "\uACBD\uACE0" : "\uC624\uB958"}: ${msg}` });
25
- }
26
- });
27
- }
28
- function applyPageText(blocks, shared) {
29
- const { headers, footers } = shared.pageText;
30
- if (headers.length > 0) {
31
- blocks.unshift(...headers.map((t) => ({ type: "paragraph", text: t, pageNumber: 1 })));
32
- }
33
- if (footers.length > 0) {
34
- blocks.push(...footers.map((t) => ({ type: "paragraph", text: t })));
35
- }
36
- }
37
- function findChildByLocalName(parent, name) {
38
- const children = parent.childNodes;
39
- if (!children) return null;
40
- for (let i = 0; i < children.length; i++) {
41
- const ch = children[i];
42
- if (ch.nodeType !== 1) continue;
43
- const tag = (ch.tagName || ch.localName || "").replace(/^[^:]+:/, "");
44
- if (tag === name) return ch;
45
- }
46
- return null;
47
- }
48
- function extractTextFromNode(node) {
49
- let result = "";
50
- const children = node.childNodes;
51
- if (!children) return result;
52
- for (let i = 0; i < children.length; i++) {
53
- const child = children[i];
54
- if (child.nodeType === 3) result += child.textContent || "";
55
- else if (child.nodeType === 1) result += extractTextFromNode(child);
56
- }
57
- return result.trim();
58
- }
59
-
60
17
  // src/render/layout.ts
61
18
  function toInt32(v, fallback = 0) {
62
19
  if (v == null || v === "") return fallback;
@@ -370,80 +327,162 @@ function fitRatioForFewerLines(text, firstWidth, contWidth, height, baseRatio, m
370
327
  return null;
371
328
  }
372
329
 
373
- // src/render/head-styles.ts
374
- var DEFAULT_CHAR = { height: 1e3, bold: false, italic: false, underline: false, ratio: 100, spacing: 0 };
375
- function borderWidthPt(v) {
376
- const n = parseFloat(v ?? "");
377
- if (!Number.isFinite(n)) return 0.34;
378
- return n * 2.834645;
379
- }
380
- function parseEdge(el) {
381
- if (!el) return void 0;
382
- const type = el.getAttribute("type") ?? "NONE";
383
- if (type === "NONE") return void 0;
384
- return { type, widthPt: borderWidthPt(el.getAttribute("width")), color: el.getAttribute("color") ?? "#000000" };
385
- }
386
- function parseRenderStyles(headXml) {
387
- const styles = { charPr: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), borderFill: /* @__PURE__ */ new Map() };
388
- const doc = createXmlParser().parseFromString(headXml, "text/xml");
389
- const root = doc.documentElement;
390
- if (!root) return styles;
391
- const walk = (el) => {
392
- const tag = (el.tagName || "").replace(/^[^:]+:/, "");
393
- if (tag === "charPr") {
394
- const id = el.getAttribute("id");
395
- if (id != null) {
396
- const ratioEl = findChildByLocalName(el, "ratio");
397
- const spacingEl = findChildByLocalName(el, "spacing");
398
- const underlineEl = findChildByLocalName(el, "underline");
399
- const textColor = el.getAttribute("textColor");
400
- styles.charPr.set(id, {
401
- height: Number(el.getAttribute("height")) || 1e3,
402
- bold: findChildByLocalName(el, "bold") != null,
403
- italic: findChildByLocalName(el, "italic") != null,
404
- underline: underlineEl != null && (underlineEl.getAttribute("type") ?? "NONE") !== "NONE",
405
- color: textColor && textColor !== "#000000" && textColor.toLowerCase() !== "none" ? textColor : void 0,
406
- ratio: Number(ratioEl?.getAttribute("hangul")) || 100,
407
- spacing: Number(spacingEl?.getAttribute("hangul")) || 0
408
- });
409
- }
410
- } else if (tag === "paraPr") {
411
- const id = el.getAttribute("id");
412
- if (id != null) {
413
- const align = findChildByLocalName(el, "align");
414
- styles.paraAlign.set(id, align?.getAttribute("horizontal") || "JUSTIFY");
330
+ // src/render/reflow.ts
331
+ var LINESEG_FLAGS = "393216";
332
+ var BASELINE_RATIO = 0.85;
333
+ function ln(el) {
334
+ return (el.tagName || "").replace(/^[^:]+:/, "");
335
+ }
336
+ function elements(el) {
337
+ const out = [];
338
+ const kids = el.childNodes;
339
+ if (!kids) return out;
340
+ for (let i = 0; i < kids.length; i++) if (kids[i].nodeType === 1) out.push(kids[i]);
341
+ return out;
342
+ }
343
+ function num(el, attr, fallback = 0) {
344
+ return el ? toInt32(el.getAttribute(attr) ?? void 0, fallback) : fallback;
345
+ }
346
+ function shiftParaVert(p, delta) {
347
+ for (const lsa of elements(p)) {
348
+ if (ln(lsa) !== "linesegarray") continue;
349
+ for (const seg of elements(lsa)) {
350
+ if (ln(seg) !== "lineseg") continue;
351
+ seg.setAttribute("vertpos", String(num(seg, "vertpos") + delta));
352
+ }
353
+ }
354
+ }
355
+ function pitchFor(height, geom) {
356
+ const v = geom.lineSpacingValue;
357
+ switch (geom.lineSpacingType) {
358
+ case "PERCENT":
359
+ return Math.round(height * v / 100);
360
+ case "FIXED":
361
+ return v > 0 ? v : Math.round(height * 1.6);
362
+ // 고정 줄높이(HWPUNIT)
363
+ case "AT_LEAST":
364
+ return Math.max(v, height);
365
+ default:
366
+ return Math.round(height * 1.6);
367
+ }
368
+ }
369
+ function reflowPara(p, doc, styles, areaW, startV, mode) {
370
+ const m = buildPara(p);
371
+ if (m.segs.length > 0) return null;
372
+ const realIdx = [];
373
+ let text = "";
374
+ for (let i = 0; i < m.chars.length; i++) {
375
+ const ch = m.chars[i].ch;
376
+ if (ch === "") continue;
377
+ for (let u = 0; u < ch.length; u++) realIdx.push(i);
378
+ text += ch;
379
+ }
380
+ const geom = styles.paraGeom.get(m.paraPrId ?? "") ?? DEFAULT_PARA_GEOM;
381
+ let domChar = DEFAULT_CHAR;
382
+ for (const c of m.chars) {
383
+ if (c.ch !== "" && c.prId != null) {
384
+ const st = styles.charPr.get(c.prId);
385
+ if (st) {
386
+ domChar = st;
387
+ break;
415
388
  }
416
- } else if (tag === "borderFill") {
417
- const id = el.getAttribute("id");
418
- if (id != null) {
419
- const bf = {
420
- left: parseEdge(findChildByLocalName(el, "leftBorder")),
421
- right: parseEdge(findChildByLocalName(el, "rightBorder")),
422
- top: parseEdge(findChildByLocalName(el, "topBorder")),
423
- bottom: parseEdge(findChildByLocalName(el, "bottomBorder"))
424
- };
425
- const fillBrush = findChildByLocalName(el, "fillBrush");
426
- const winBrush = fillBrush ? findChildByLocalName(fillBrush, "winBrush") : null;
427
- const face = winBrush?.getAttribute("faceColor");
428
- if (face && face.toLowerCase() !== "none") bf.fill = face;
429
- styles.borderFill.set(id, bf);
389
+ }
390
+ }
391
+ const height = domChar.height || 1e3;
392
+ const ratio = domChar.ratio || 100;
393
+ const spacingPct = domChar.spacing || 0;
394
+ const marginL = geom.marginLeft;
395
+ const avail = Math.max(1e3, areaW - marginL - geom.marginRight);
396
+ const firstWidth = avail;
397
+ const contWidth = Math.max(500, avail + Math.min(0, geom.marginIntent));
398
+ const contHorz = marginL - Math.min(0, geom.marginIntent);
399
+ const wrap = text.length === 0 ? { lines: 1, starts: [0], lastLineWidth: 0 } : simulateWrap(text, firstWidth, contWidth, height, ratio, mode, { spacingPct });
400
+ const pitch = pitchFor(height, geom);
401
+ const baseline = Math.round(height * BASELINE_RATIO);
402
+ const spacing = Math.max(0, pitch - height);
403
+ const lsa = doc.createElement("hp:linesegarray");
404
+ for (let li = 0; li < wrap.starts.length; li++) {
405
+ const startReal = wrap.starts[li];
406
+ const textpos = startReal < realIdx.length ? realIdx[startReal] : m.chars.length;
407
+ const vertpos = startV + li * pitch;
408
+ const isFirst = li === 0;
409
+ const seg = doc.createElement("hp:lineseg");
410
+ seg.setAttribute("textpos", String(textpos));
411
+ seg.setAttribute("vertpos", String(vertpos));
412
+ seg.setAttribute("vertsize", String(height));
413
+ seg.setAttribute("textheight", String(height));
414
+ seg.setAttribute("baseline", String(baseline));
415
+ seg.setAttribute("spacing", String(spacing));
416
+ seg.setAttribute("horzpos", String(isFirst ? marginL : contHorz));
417
+ seg.setAttribute("horzsize", String(isFirst ? firstWidth : contWidth));
418
+ seg.setAttribute("flags", LINESEG_FLAGS);
419
+ lsa.appendChild(seg);
420
+ }
421
+ p.appendChild(lsa);
422
+ const textBottom = startV + wrap.starts.length * pitch;
423
+ let objBottom = startV;
424
+ for (const o of m.objs) {
425
+ const h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el)) : o.height;
426
+ objBottom = Math.max(objBottom, startV + h);
427
+ }
428
+ return { paraBottom: Math.max(textBottom, objBottom), spaceAfter: geom.spaceAfter };
429
+ }
430
+ function reflowTablesIn(p, doc, styles, mode, counter) {
431
+ for (const run of elements(p)) {
432
+ if (ln(run) !== "run") continue;
433
+ for (const obj of elements(run)) {
434
+ if (ln(obj) !== "tbl") continue;
435
+ for (const tr of elements(obj)) {
436
+ if (ln(tr) !== "tr") continue;
437
+ for (const tc of elements(tr)) {
438
+ if (ln(tc) !== "tc") continue;
439
+ const csz = findChildByLocalName(tc, "cellSz");
440
+ const cm = findChildByLocalName(tc, "cellMargin");
441
+ const cellW = num(csz, "width");
442
+ const mL = cm ? num(cm, "left", 141) : 141;
443
+ const mR = cm ? num(cm, "right", 141) : 141;
444
+ const areaW = Math.max(500, cellW - mL - mR);
445
+ const sub = findChildByLocalName(tc, "subList");
446
+ if (sub) reflowBlockFlow(sub, doc, styles, areaW, mode, counter, 0);
447
+ }
430
448
  }
431
449
  }
432
- const children = el.childNodes;
433
- for (let i = 0; i < children.length; i++) {
434
- const ch = children[i];
435
- if (ch.nodeType === 1) walk(ch);
450
+ }
451
+ }
452
+ function reflowBlockFlow(container, doc, styles, areaW, mode, counter, bodyH) {
453
+ let cursorV = 0;
454
+ let prevSpaceAfter = 0;
455
+ for (const p of elements(container)) {
456
+ if (ln(p) !== "p") continue;
457
+ reflowTablesIn(p, doc, styles, mode, counter);
458
+ const g = styles.paraGeom.get(p.getAttribute("paraPrIDRef") ?? "");
459
+ const startV = cursorV + prevSpaceAfter + (g?.spaceBefore ?? 0);
460
+ const res = reflowPara(p, doc, styles, areaW, startV, mode);
461
+ if (res) {
462
+ const paraH = res.paraBottom - startV;
463
+ if (bodyH > 0 && startV > 0 && res.paraBottom > bodyH && paraH <= bodyH) {
464
+ shiftParaVert(p, -startV);
465
+ cursorV = paraH;
466
+ } else {
467
+ cursorV = res.paraBottom;
468
+ }
469
+ prevSpaceAfter = res.spaceAfter;
470
+ counter.n++;
436
471
  }
437
- };
438
- walk(root);
439
- return styles;
472
+ }
473
+ }
474
+ function reflowSection(root, styles, geom, mode = "keep") {
475
+ const doc = root.ownerDocument;
476
+ const counter = { n: 0 };
477
+ reflowBlockFlow(root, doc, styles, geom.BODY_W, mode, counter, geom.BODY_H);
478
+ return counter.n;
440
479
  }
441
480
 
442
481
  // src/render/svg-render.ts
443
- function ln(el) {
482
+ function ln2(el) {
444
483
  return (el.tagName || "").replace(/^[^:]+:/, "");
445
484
  }
446
- function elements(el) {
485
+ function elements2(el) {
447
486
  const out = [];
448
487
  const children = el.childNodes;
449
488
  if (!children) return out;
@@ -452,13 +491,13 @@ function elements(el) {
452
491
  }
453
492
  return out;
454
493
  }
455
- function num(el, attr, fallback = 0) {
494
+ function num2(el, attr, fallback = 0) {
456
495
  return el ? toInt32(el.getAttribute(attr) ?? void 0, fallback) : fallback;
457
496
  }
458
497
  function findFirst(el, name, depth = 0) {
459
498
  if (depth > 64) return null;
460
- for (const ch of elements(el)) {
461
- if (ln(ch) === name) return ch;
499
+ for (const ch of elements2(el)) {
500
+ if (ln2(ch) === name) return ch;
462
501
  const found = findFirst(ch, name, depth + 1);
463
502
  if (found) return found;
464
503
  }
@@ -469,59 +508,80 @@ function escapeXml(s) {
469
508
  }
470
509
  var OBJ_TAGS = /* @__PURE__ */ new Set(["tbl", "pic", "container", "equation", "rect", "ellipse", "polygon", "curv", "line", "arc", "ole", "textart"]);
471
510
  var pt = (u) => String(Math.round(u) / 100);
511
+ function emit(ctx, s) {
512
+ ctx.pages[ctx.page].push(s);
513
+ }
472
514
  function warnOnce(ctx, key, msg) {
473
515
  if (ctx.warned.has(key)) return;
474
516
  ctx.warned.add(key);
475
517
  ctx.warnings.push(msg);
476
518
  }
477
- function textOfT(t) {
478
- let s = "";
479
- const walk = (n, d) => {
480
- if (d > 32) return;
481
- const kids = n.childNodes;
482
- if (!kids) return;
483
- for (let i = 0; i < kids.length; i++) {
484
- const c = kids[i];
485
- if (c.nodeType === 3) s += c.textContent ?? "";
486
- else if (c.nodeType === 1) walk(c, d + 1);
519
+ var CHAR_CTRL_1SLOT = /* @__PURE__ */ new Set(["lineBreak", "hyphen", "nbSpace", "fwSpace"]);
520
+ function pushFillers(chars, n, prId) {
521
+ for (let i = 0; i < n; i++) chars.push({ ch: "", prId });
522
+ }
523
+ function pushTextSlots(t, chars, prId, depth) {
524
+ if (depth > 32) return;
525
+ const kids = t.childNodes;
526
+ if (!kids) return;
527
+ for (let i = 0; i < kids.length; i++) {
528
+ const c = kids[i];
529
+ if (c.nodeType === 3) {
530
+ for (const cp of c.textContent ?? "") {
531
+ chars.push({ ch: cp, prId });
532
+ if (cp.length === 2) chars.push({ ch: "", prId });
533
+ }
534
+ } else if (c.nodeType === 1) {
535
+ const el = c;
536
+ const tag = ln2(el);
537
+ if (tag === "tab") {
538
+ pushFillers(chars, 8, prId);
539
+ } else if (CHAR_CTRL_1SLOT.has(tag)) {
540
+ chars.push({ ch: tag === "nbSpace" || tag === "fwSpace" ? " " : "", prId });
541
+ } else {
542
+ pushTextSlots(el, chars, prId, depth + 1);
543
+ }
487
544
  }
488
- };
489
- walk(t, 0);
490
- return s;
545
+ }
491
546
  }
492
547
  function buildPara(p) {
493
548
  const chars = [];
494
549
  const objs = [];
495
550
  let segs = [];
496
- for (const runEl of elements(p)) {
497
- const tag = ln(runEl);
551
+ for (const runEl of elements2(p)) {
552
+ const tag = ln2(runEl);
498
553
  if (tag === "run") {
499
554
  const prId = runEl.getAttribute("charPrIDRef");
500
- for (const ch of elements(runEl)) {
501
- const cn = ln(ch);
555
+ for (const ch of elements2(runEl)) {
556
+ const cn = ln2(ch);
502
557
  if (cn === "t") {
503
- for (const c of textOfT(ch)) chars.push({ ch: c, prId });
558
+ pushTextSlots(ch, chars, prId, 0);
504
559
  } else if (OBJ_TAGS.has(cn)) {
505
560
  const sz = findChildByLocalName(ch, "sz");
506
561
  const pos = findChildByLocalName(ch, "pos");
562
+ const w = num2(sz, "width") || num2(findChildByLocalName(ch, "curSz"), "width") || num2(findChildByLocalName(ch, "orgSz"), "width");
563
+ const h = num2(sz, "height") || num2(findChildByLocalName(ch, "curSz"), "height") || num2(findChildByLocalName(ch, "orgSz"), "height");
507
564
  objs.push({
508
565
  el: ch,
509
566
  tag: cn,
510
567
  index: chars.length,
511
568
  inline: pos?.getAttribute("treatAsChar") === "1",
512
- width: num(sz, "width"),
513
- height: num(sz, "height")
569
+ width: w,
570
+ height: h
514
571
  });
572
+ pushFillers(chars, 8, prId);
573
+ } else {
574
+ pushFillers(chars, 8, prId);
515
575
  }
516
576
  }
517
577
  } else if (tag === "linesegarray") {
518
- segs = elements(runEl).filter((s) => ln(s) === "lineseg").map((s) => ({
519
- textpos: num(s, "textpos"),
520
- vertpos: num(s, "vertpos"),
521
- horzpos: num(s, "horzpos"),
522
- horzsize: num(s, "horzsize"),
523
- textheight: num(s, "textheight", 1e3),
524
- baseline: num(s, "baseline", 850)
578
+ segs = elements2(runEl).filter((s) => ln2(s) === "lineseg").map((s) => ({
579
+ textpos: num2(s, "textpos"),
580
+ vertpos: num2(s, "vertpos"),
581
+ horzpos: num2(s, "horzpos"),
582
+ horzsize: num2(s, "horzsize"),
583
+ textheight: num2(s, "textheight", 1e3),
584
+ baseline: num2(s, "baseline", 850)
525
585
  }));
526
586
  }
527
587
  }
@@ -569,38 +629,41 @@ function advanceTo(m, styles, plan, upto) {
569
629
  for (const o of m.objs) if (o.inline && o.index >= plan.start && o.index < upto) x += o.width;
570
630
  return x;
571
631
  }
572
- function drawPara(p, ox, oy, areaW, ctx, depth) {
632
+ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
573
633
  if (depth > 16) {
574
634
  warnOnce(ctx, "depth", "\uC911\uCCA9 \uAE4A\uC774 16 \uCD08\uACFC \u2014 \uC774\uD558 \uC0DD\uB7B5");
575
635
  return;
576
636
  }
577
637
  const m = buildPara(p);
578
638
  if (m.segs.length === 0) {
639
+ if (m.chars.some((c) => c.ch !== "")) {
640
+ warnOnce(ctx, "no-lineseg", "\uC870\uD310 \uCE90\uC2DC \uC5C6\uB294 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uC0DD\uB7B5 \u2014 reflow \uC635\uC158\uC73C\uB85C \uD569\uC131 \uAC00\uB2A5");
641
+ }
579
642
  for (const o of m.objs) drawObject(o, ox, oy, 0, areaW, ctx, depth);
580
643
  return;
581
644
  }
582
645
  const plans = planLines(m, ctx.styles);
583
646
  const baseV = m.segs[0].vertpos;
584
- for (const plan of plans) {
647
+ for (let li = 0; li < plans.length; li++) {
648
+ const plan = plans[li];
649
+ if (segPages && segPages[li] !== void 0) ctx.page = segPages[li];
585
650
  const { seg } = plan;
586
651
  let i = plan.start;
587
652
  let cursor = ox + seg.horzpos + plan.xoff;
588
653
  const y = oy + seg.vertpos + seg.baseline;
589
654
  while (i < plan.end && i < m.chars.length) {
655
+ if (m.chars[i].ch === "") {
656
+ for (const o of m.objs) if (o.inline && o.index === i) cursor += o.width;
657
+ i++;
658
+ continue;
659
+ }
590
660
  const prId = m.chars[i].prId;
591
661
  let j = i;
592
662
  let piece = "";
593
- while (j < plan.end && j < m.chars.length && m.chars[j].prId === prId) {
663
+ while (j < plan.end && j < m.chars.length && m.chars[j].prId === prId && m.chars[j].ch !== "") {
594
664
  piece += m.chars[j].ch;
595
665
  j++;
596
666
  }
597
- for (const o of m.objs) {
598
- if (o.inline && o.index > i && o.index < j) {
599
- piece = piece.slice(0, o.index - i);
600
- j = o.index;
601
- break;
602
- }
603
- }
604
667
  {
605
668
  const cut = piece.search(/ {2,}/);
606
669
  if (cut > 0) {
@@ -613,32 +676,71 @@ function drawPara(p, ox, oy, areaW, ctx, depth) {
613
676
  }
614
677
  }
615
678
  const st = (prId != null ? ctx.styles.charPr.get(prId) : void 0) ?? DEFAULT_CHAR;
616
- const w = measureTextWidth(piece, st.height, st.ratio, { spacingPct: st.spacing }) * plan.scale;
617
- if (piece.trim().length > 0) {
618
- const attrs = [`x="${pt(cursor)}"`, `y="${pt(y)}"`, `font-size="${pt(st.height)}"`];
619
- if ([...piece].length > 1 && w > 50) {
620
- attrs.push(`textLength="${pt(w)}"`, `lengthAdjust="${plan.scale < 1 ? "spacingAndGlyphs" : "spacing"}"`);
679
+ const renderSeg = (text, cx, hit) => {
680
+ const sw = measureTextWidth(text, st.height, st.ratio, { spacingPct: st.spacing }) * plan.scale;
681
+ if (hit) {
682
+ emit(ctx, `<rect x="${pt(cx)}" y="${pt(oy + seg.vertpos)}" width="${pt(sw)}" height="${pt(seg.textheight)}" fill="#ffd54f" fill-opacity="0.45"/>`);
683
+ }
684
+ if (text.trim().length > 0) {
685
+ const attrs = [`x="${pt(cx)}"`, `y="${pt(y)}"`, `font-size="${pt(st.height)}"`];
686
+ if ([...text].length > 1 && sw > 50) {
687
+ attrs.push(`textLength="${pt(sw)}"`, `lengthAdjust="${plan.scale < 1 ? "spacingAndGlyphs" : "spacing"}"`);
688
+ }
689
+ if (st.bold) attrs.push(`font-weight="bold"`);
690
+ if (st.italic) attrs.push(`font-style="italic"`);
691
+ if (st.underline) attrs.push(`text-decoration="underline"`);
692
+ if (st.color) attrs.push(`fill="${escapeXml(st.color)}"`);
693
+ emit(ctx, `<text ${attrs.join(" ")}>${escapeXml(text)}</text>`);
694
+ ctx.stats.texts++;
695
+ }
696
+ return sw;
697
+ };
698
+ const merged = [];
699
+ if (ctx.highlights.length > 0 && piece.trim().length > 0) {
700
+ const found = [];
701
+ const lower = piece.toLowerCase();
702
+ for (const term of ctx.highlights) {
703
+ for (let f = lower.indexOf(term); f !== -1; f = lower.indexOf(term, f + term.length)) {
704
+ found.push([f, f + term.length]);
705
+ }
706
+ }
707
+ found.sort((a, b) => a[0] - b[0]);
708
+ for (const [s, e] of found) {
709
+ const tail = merged[merged.length - 1];
710
+ if (tail && s <= tail[1]) tail[1] = Math.max(tail[1], e);
711
+ else merged.push([s, e]);
621
712
  }
622
- if (st.bold) attrs.push(`font-weight="bold"`);
623
- if (st.italic) attrs.push(`font-style="italic"`);
624
- if (st.underline) attrs.push(`text-decoration="underline"`);
625
- if (st.color) attrs.push(`fill="${escapeXml(st.color)}"`);
626
- ctx.svg.push(`<text ${attrs.join(" ")}>${escapeXml(piece)}</text>`);
627
- ctx.stats.texts++;
628
713
  }
629
- cursor += w;
630
- for (const o of m.objs) if (o.inline && o.index === j) cursor += o.width;
714
+ if (merged.length === 0) {
715
+ cursor += renderSeg(piece, cursor, false);
716
+ } else {
717
+ let segCur = cursor;
718
+ let last = 0;
719
+ for (const [s, e] of merged) {
720
+ segCur += renderSeg(piece.slice(last, s), segCur, false);
721
+ segCur += renderSeg(piece.slice(s, e), segCur, true);
722
+ last = e;
723
+ }
724
+ segCur += renderSeg(piece.slice(last), segCur, false);
725
+ cursor = segCur;
726
+ }
631
727
  i = j;
632
728
  }
633
729
  }
634
730
  for (const o of m.objs) {
635
731
  if (o.inline) {
636
- let plan = plans[0];
637
- for (const pl of plans) if (pl.start <= o.index && (o.index < pl.end || pl === plans[plans.length - 1])) plan = pl;
732
+ let planIdx = 0;
733
+ for (let k = 0; k < plans.length; k++) {
734
+ const pl = plans[k];
735
+ if (pl.start <= o.index && (o.index < pl.end || k === plans.length - 1)) planIdx = k;
736
+ }
737
+ const plan = plans[planIdx];
738
+ if (segPages && segPages[planIdx] !== void 0) ctx.page = segPages[planIdx];
638
739
  const x = ox + plan.seg.horzpos + plan.xoff + advanceTo(m, ctx.styles, plan, o.index);
639
740
  const yTop = oy + plan.seg.vertpos + Math.max(0, plan.seg.baseline - o.height);
640
741
  drawObject(o, x, yTop, baseV, areaW, ctx, depth);
641
742
  } else {
743
+ if (segPages && segPages[0] !== void 0) ctx.page = segPages[0];
642
744
  const { x, y } = anchorObject(o, ox, oy, baseV, areaW, ctx);
643
745
  drawObject(o, x, y, baseV, areaW, ctx, depth);
644
746
  }
@@ -648,11 +750,11 @@ function anchorObject(o, ox, oy, baseV, areaW, ctx) {
648
750
  const { PW, PH, ML, MT, BODY_W, BODY_H } = ctx.geom;
649
751
  const pos = findChildByLocalName(o.el, "pos");
650
752
  const om = findChildByLocalName(o.el, "outMargin");
651
- const omT = num(om, "top"), omB = num(om, "bottom");
753
+ const omT = num2(om, "top"), omB = num2(om, "bottom");
652
754
  const w = o.width, h = o.height;
653
755
  if (!pos) return { x: ox, y: oy + baseV };
654
- const vo = num(pos, "vertOffset");
655
- const ho = num(pos, "horzOffset");
756
+ const vo = num2(pos, "vertOffset");
757
+ const ho = num2(pos, "horzOffset");
656
758
  const vrel = pos.getAttribute("vertRelTo") ?? "PARA";
657
759
  const hrel = pos.getAttribute("horzRelTo") ?? "PARA";
658
760
  const va = pos.getAttribute("vertAlign") ?? "TOP";
@@ -684,25 +786,74 @@ function drawObject(o, x, y, baseV, areaW, ctx, depth) {
684
786
  if (o.tag === "tbl") drawTable(o.el, x, y, ctx, depth + 1);
685
787
  else if (o.tag === "pic") drawPic(o.el, x, y, ctx);
686
788
  else if (o.tag === "container") {
687
- for (const ch of elements(o.el)) {
688
- const tag = ln(ch);
789
+ for (const ch of elements2(o.el)) {
790
+ const tag = ln2(ch);
689
791
  if (!OBJ_TAGS.has(tag)) continue;
690
792
  const sz = findChildByLocalName(ch, "sz");
691
793
  const off = findChildByLocalName(ch, "offset");
692
- const sub = { el: ch, tag, index: 0, inline: true, width: num(sz, "width"), height: num(sz, "height") };
693
- drawObject(sub, x + num(off, "x"), y + num(off, "y"), baseV, areaW, ctx, depth + 1);
794
+ const sub = { el: ch, tag, index: 0, inline: true, width: num2(sz, "width"), height: num2(sz, "height") };
795
+ drawObject(sub, x + num2(off, "x"), y + num2(off, "y"), baseV, areaW, ctx, depth + 1);
694
796
  }
695
797
  } else if (o.tag === "equation") {
696
798
  warnOnce(ctx, "equation", "\uC218\uC2DD \uAC1C\uCCB4\uB294 \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 \uC0DD\uB7B5");
799
+ } else if (SHAPE_TAGS.has(o.tag)) {
800
+ drawShape(o, x, y, ctx, depth);
697
801
  } else {
698
- warnOnce(ctx, `shape:${o.tag}`, `\uB3C4\uD615 \uAC1C\uCCB4(${o.tag}) \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 \uC0DD\uB7B5`);
802
+ warnOnce(ctx, `shape:${o.tag}`, `\uAC1C\uCCB4(${o.tag}) \uB80C\uB354 \uBBF8\uC9C0\uC6D0 \u2014 \uC0DD\uB7B5`);
699
803
  }
700
804
  }
701
- function cellContentExtent(cell, ctx) {
805
+ var SHAPE_TAGS = /* @__PURE__ */ new Set(["rect", "ellipse", "line", "polygon", "curv", "arc"]);
806
+ function shapeStrokePt(v) {
807
+ return Math.max(0.2, v / 100 * 2.834645);
808
+ }
809
+ function drawShape(o, x, y, ctx, depth) {
810
+ const el = o.el;
811
+ const orgSz = findChildByLocalName(el, "orgSz");
812
+ const curSz = findChildByLocalName(el, "curSz");
813
+ const ow = num2(orgSz, "width"), oh = num2(orgSz, "height");
814
+ const w = num2(curSz, "width") || ow || o.width;
815
+ const h = num2(curSz, "height") || oh || o.height;
816
+ const sx = ow > 0 ? w / ow : 1;
817
+ const sy = oh > 0 ? h / oh : 1;
818
+ const lineShape = findChildByLocalName(el, "lineShape");
819
+ const lstyle = lineShape?.getAttribute("style") ?? "SOLID";
820
+ const strokeCol = lineShape?.getAttribute("color") || "#000000";
821
+ const hasStroke = lstyle !== "NONE";
822
+ const strokeW = hasStroke ? shapeStrokePt(lineShape ? num2(lineShape, "width") : 33) : 0;
823
+ const dash = /DASH|DOT/.test(lstyle) ? ` stroke-dasharray="${lstyle.includes("DOT") ? "1,1.5" : "3,1.5"}"` : "";
824
+ const strokeAttr = hasStroke ? ` stroke="${escapeXml(strokeCol)}" stroke-width="${strokeW.toFixed(2)}"${dash}` : "";
825
+ const fillBrush = findChildByLocalName(el, "fillBrush");
826
+ const winBrush = fillBrush ? findChildByLocalName(fillBrush, "winBrush") : null;
827
+ const face = winBrush?.getAttribute("faceColor");
828
+ const fill = face && face.toLowerCase() !== "none" ? face : "none";
829
+ const fillAttr = ` fill="${fill === "none" ? "none" : escapeXml(fill)}"`;
830
+ if (o.tag === "rect") {
831
+ emit(ctx, `<rect x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}"${fillAttr}${strokeAttr}/>`);
832
+ } else if (o.tag === "ellipse") {
833
+ emit(ctx, `<ellipse cx="${pt(x + w / 2)}" cy="${pt(y + h / 2)}" rx="${pt(w / 2)}" ry="${pt(h / 2)}"${fillAttr}${strokeAttr}/>`);
834
+ } else if (o.tag === "line") {
835
+ const s = findChildByLocalName(el, "startPt"), e = findChildByLocalName(el, "endPt");
836
+ const x1 = x + num2(s, "x") * sx, y1 = y + num2(s, "y") * sy;
837
+ const x2 = x + num2(e, "x") * sx, y2 = y + num2(e, "y") * sy;
838
+ emit(ctx, `<line x1="${pt(x1)}" y1="${pt(y1)}" x2="${pt(x2)}" y2="${pt(y2)}" stroke="${escapeXml(strokeCol)}" stroke-width="${(strokeW || 0.3).toFixed(2)}"${dash}/>`);
839
+ } else if (o.tag === "polygon" || o.tag === "curv") {
840
+ const pts = [];
841
+ for (const c of elements2(el)) if (ln2(c) === "pt") pts.push(`${pt(x + num2(c, "x") * sx)},${pt(y + num2(c, "y") * sy)}`);
842
+ if (pts.length >= 2) emit(ctx, `<polygon points="${pts.join(" ")}"${fillAttr}${strokeAttr}/>`);
843
+ } else if (o.tag === "arc") {
844
+ emit(ctx, `<ellipse cx="${pt(x + w / 2)}" cy="${pt(y + h / 2)}" rx="${pt(w / 2)}" ry="${pt(h / 2)}" fill="none"${strokeAttr || ` stroke="${escapeXml(strokeCol)}" stroke-width="0.3"`}/>`);
845
+ }
846
+ const dt = findChildByLocalName(el, "drawText");
847
+ const sub = dt ? findChildByLocalName(dt, "subList") : null;
848
+ if (sub) {
849
+ for (const p of elements2(sub)) if (ln2(p) === "p") drawPara(p, x, y, w, ctx, depth + 1);
850
+ }
851
+ }
852
+ function cellContentExtent(cell) {
702
853
  if (!cell.sub) return 0;
703
854
  let ext = 0;
704
- for (const p of elements(cell.sub)) {
705
- if (ln(p) !== "p") continue;
855
+ for (const p of elements2(cell.sub)) {
856
+ if (ln2(p) !== "p") continue;
706
857
  const m = buildPara(p);
707
858
  for (const s of m.segs) ext = Math.max(ext, s.vertpos + s.textheight);
708
859
  const baseV = m.segs[0]?.vertpos ?? 0;
@@ -711,9 +862,9 @@ function cellContentExtent(cell, ctx) {
711
862
  const pos = findChildByLocalName(o.el, "pos");
712
863
  if ((pos?.getAttribute("vertRelTo") ?? "PARA") !== "PARA") continue;
713
864
  const om = findChildByLocalName(o.el, "outMargin");
714
- const pushed = baseV - (num(om, "top") + o.height + num(om, "bottom"));
865
+ const pushed = baseV - (num2(om, "top") + o.height + num2(om, "bottom"));
715
866
  const anchor = pushed >= -100 ? pushed : baseV;
716
- ext = Math.max(ext, anchor + num(om, "top") + num(pos, "vertOffset") + o.height);
867
+ ext = Math.max(ext, anchor + num2(om, "top") + num2(pos, "vertOffset") + o.height);
717
868
  }
718
869
  }
719
870
  return ext;
@@ -722,21 +873,15 @@ function edgeLine(x1, y1, x2, y2, e) {
722
873
  const dash = /DASH|DOT/.test(e.type) ? ` stroke-dasharray="${e.type.includes("DOT") ? "1,1.5" : "3,1.5"}"` : "";
723
874
  return `<line x1="${pt(x1)}" y1="${pt(y1)}" x2="${pt(x2)}" y2="${pt(y2)}" stroke="${escapeXml(e.color)}" stroke-width="${e.widthPt.toFixed(2)}"${dash}/>`;
724
875
  }
725
- function drawTable(tbl, tx, ty, ctx, depth) {
726
- if (depth > 16) {
727
- warnOnce(ctx, "depth", "\uC911\uCCA9 \uAE4A\uC774 16 \uCD08\uACFC \u2014 \uC774\uD558 \uC0DD\uB7B5");
728
- return;
729
- }
730
- ctx.stats.tables++;
731
- const tblSz = findChildByLocalName(tbl, "sz");
876
+ function collectCells(tbl) {
732
877
  const inMargin = findChildByLocalName(tbl, "inMargin");
733
- const defL = num(inMargin, "left", 141), defR = num(inMargin, "right", 141);
734
- const defT = num(inMargin, "top", 141), defB = num(inMargin, "bottom", 141);
878
+ const defL = num2(inMargin, "left", 141), defR = num2(inMargin, "right", 141);
879
+ const defT = num2(inMargin, "top", 141), defB = num2(inMargin, "bottom", 141);
735
880
  const cells = [];
736
- for (const tr of elements(tbl)) {
737
- if (ln(tr) !== "tr") continue;
738
- for (const tc of elements(tr)) {
739
- if (ln(tc) !== "tc") continue;
881
+ for (const tr of elements2(tbl)) {
882
+ if (ln2(tr) !== "tr") continue;
883
+ for (const tc of elements2(tr)) {
884
+ if (ln2(tc) !== "tc") continue;
740
885
  const addr = findChildByLocalName(tc, "cellAddr");
741
886
  const span = findChildByLocalName(tc, "cellSpan");
742
887
  const csz = findChildByLocalName(tc, "cellSz");
@@ -744,28 +889,50 @@ function drawTable(tbl, tx, ty, ctx, depth) {
744
889
  if (!addr || !csz) continue;
745
890
  cells.push({
746
891
  el: tc,
747
- ca: num(addr, "colAddr"),
748
- ra: num(addr, "rowAddr"),
749
- cs: Math.max(1, num(span, "colSpan", 1)),
750
- rs: Math.max(1, num(span, "rowSpan", 1)),
751
- w: num(csz, "width"),
752
- h: num(csz, "height"),
892
+ ca: num2(addr, "colAddr"),
893
+ ra: num2(addr, "rowAddr"),
894
+ cs: Math.max(1, num2(span, "colSpan", 1)),
895
+ rs: Math.max(1, num2(span, "rowSpan", 1)),
896
+ w: num2(csz, "width"),
897
+ h: num2(csz, "height"),
753
898
  bfId: tc.getAttribute("borderFillIDRef"),
754
899
  sub: findChildByLocalName(tc, "subList"),
755
- marginL: cm ? num(cm, "left", defL) : defL,
756
- marginR: cm ? num(cm, "right", defR) : defR,
757
- marginT: cm ? num(cm, "top", defT) : defT,
758
- marginB: cm ? num(cm, "bottom", defB) : defB
900
+ marginL: cm ? num2(cm, "left", defL) : defL,
901
+ marginR: cm ? num2(cm, "right", defR) : defR,
902
+ marginT: cm ? num2(cm, "top", defT) : defT,
903
+ marginB: cm ? num2(cm, "bottom", defB) : defB
759
904
  });
760
905
  }
761
906
  }
907
+ return cells;
908
+ }
909
+ function measureTableHeight(tbl) {
910
+ const cells = collectCells(tbl);
911
+ if (cells.length === 0 || cells.length > 4096) return 0;
912
+ const nRows = Math.max(...cells.map((c) => c.ra + c.rs));
913
+ const rowH = solveRowHeights(
914
+ cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : void 0 })),
915
+ nRows
916
+ );
917
+ let sum = 0;
918
+ for (const h of rowH) sum += h;
919
+ return sum;
920
+ }
921
+ function drawTable(tbl, tx, ty, ctx, depth) {
922
+ if (depth > 16) {
923
+ warnOnce(ctx, "depth", "\uC911\uCCA9 \uAE4A\uC774 16 \uCD08\uACFC \u2014 \uC774\uD558 \uC0DD\uB7B5");
924
+ return;
925
+ }
926
+ ctx.stats.tables++;
927
+ const tblSz = findChildByLocalName(tbl, "sz");
928
+ const cells = collectCells(tbl);
762
929
  if (cells.length === 0 || cells.length > 4096) return;
763
930
  const nCols = Math.max(...cells.map((c) => c.ca + c.cs));
764
931
  const nRows = Math.max(...cells.map((c) => c.ra + c.rs));
765
932
  const colCons = cells.map((c) => ({ a: c.ca, b: c.ca + c.cs, size: c.w }));
766
- const colX = solveBoundaries(colCons, nCols, num(tblSz, "width") || void 0);
933
+ const colX = solveBoundaries(colCons, nCols, num2(tblSz, "width") || void 0);
767
934
  const rowH = solveRowHeights(
768
- cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c, ctx) : void 0 })),
935
+ cells.map((c) => ({ rowAddr: c.ra, rowSpan: c.rs, height: c.h, contentH: c.rs === 1 ? cellContentExtent(c) : void 0 })),
769
936
  nRows
770
937
  );
771
938
  const rowY = [0];
@@ -779,55 +946,69 @@ function drawTable(tbl, tx, ty, ctx, depth) {
779
946
  }));
780
947
  for (const g of geom) {
781
948
  const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : void 0;
782
- if (bf?.fill) ctx.svg.push(`<rect x="${pt(g.x)}" y="${pt(g.y)}" width="${pt(g.w)}" height="${pt(g.h)}" fill="${escapeXml(bf.fill)}"/>`);
949
+ if (bf?.fill) emit(ctx, `<rect x="${pt(g.x)}" y="${pt(g.y)}" width="${pt(g.w)}" height="${pt(g.h)}" fill="${escapeXml(bf.fill)}"/>`);
783
950
  }
784
951
  for (const g of geom) {
785
952
  const { c } = g;
786
953
  if (!c.sub) continue;
787
954
  const innerH = g.h - c.marginT - c.marginB;
788
- const extent = cellContentExtent(c, ctx);
955
+ const extent = cellContentExtent(c);
789
956
  const va = c.sub.getAttribute("vertAlign") ?? "TOP";
790
957
  let yoff = 0;
791
958
  if (va === "CENTER") yoff = Math.max(0, (innerH - extent) / 2);
792
959
  else if (va === "BOTTOM") yoff = Math.max(0, innerH - extent);
793
- for (const p of elements(c.sub)) {
794
- if (ln(p) !== "p") continue;
960
+ for (const p of elements2(c.sub)) {
961
+ if (ln2(p) !== "p") continue;
795
962
  drawPara(p, g.x + c.marginL, g.y + c.marginT + yoff, g.w - c.marginL - c.marginR, ctx, depth + 1);
796
963
  }
797
964
  }
798
965
  for (const g of geom) {
799
966
  const bf = g.c.bfId != null ? ctx.styles.borderFill.get(g.c.bfId) : void 0;
800
967
  if (!bf) continue;
801
- if (bf.top) ctx.svg.push(edgeLine(g.x, g.y, g.x + g.w, g.y, bf.top));
802
- if (bf.bottom) ctx.svg.push(edgeLine(g.x, g.y + g.h, g.x + g.w, g.y + g.h, bf.bottom));
803
- if (bf.left) ctx.svg.push(edgeLine(g.x, g.y, g.x, g.y + g.h, bf.left));
804
- if (bf.right) ctx.svg.push(edgeLine(g.x + g.w, g.y, g.x + g.w, g.y + g.h, bf.right));
968
+ if (bf.top) emit(ctx, edgeLine(g.x, g.y, g.x + g.w, g.y, bf.top));
969
+ if (bf.bottom) emit(ctx, edgeLine(g.x, g.y + g.h, g.x + g.w, g.y + g.h, bf.bottom));
970
+ if (bf.left) emit(ctx, edgeLine(g.x, g.y, g.x, g.y + g.h, bf.left));
971
+ if (bf.right) emit(ctx, edgeLine(g.x + g.w, g.y, g.x + g.w, g.y + g.h, bf.right));
805
972
  }
806
973
  }
974
+ function imageSymbol(loaded, ctx) {
975
+ if (!loaded.symId) {
976
+ loaded.symId = `bin${ctx.defs.length}`;
977
+ ctx.defs.push(
978
+ `<symbol id="${loaded.symId}" viewBox="0 0 100 100" preserveAspectRatio="none"><image width="100" height="100" preserveAspectRatio="none" href="${loaded.dataUri}"/></symbol>`
979
+ );
980
+ }
981
+ return loaded.symId;
982
+ }
807
983
  function drawPic(pic, x, y, ctx) {
808
984
  const sz = findChildByLocalName(pic, "sz");
809
- const w = num(sz, "width", 5669), h = num(sz, "height", 5669);
985
+ const w = num2(sz, "width", 5669), h = num2(sz, "height", 5669);
810
986
  const img = findFirst(pic, "img");
811
987
  const ref = img?.getAttribute("binaryItemIDRef");
812
988
  const loaded = ref != null ? ctx.images.get(ref) : void 0;
813
989
  if (!loaded) {
814
- ctx.svg.push(`<rect x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}" fill="#eee" stroke="#c00" stroke-width="0.5"/>`);
990
+ emit(ctx, `<rect x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}" fill="#eee" stroke="#c00" stroke-width="0.5"/>`);
815
991
  warnOnce(ctx, `img:${ref}`, `\uC774\uBBF8\uC9C0 \uBC14\uC774\uB108\uB9AC \uB204\uB77D: ${ref ?? "(ref \uC5C6\uC74C)"}`);
816
992
  return;
817
993
  }
818
994
  ctx.stats.images++;
819
- const orgSz = findChildByLocalName(pic, "orgSz");
820
995
  const clip = findChildByLocalName(pic, "imgClip");
821
- const orgW = num(orgSz, "width"), orgH = num(orgSz, "height");
822
- const cl = num(clip, "left"), ct = num(clip, "top");
823
- const cr = num(clip, "right", orgW), cb = num(clip, "bottom", orgH);
824
- const cropped = orgW > 0 && orgH > 0 && clip != null && (cl > 0 || ct > 0 || cr < orgW || cb < orgH) && cr > cl && cb > ct;
996
+ const imgDim = findChildByLocalName(pic, "imgDim");
997
+ const orgSz = findChildByLocalName(pic, "orgSz");
998
+ const dimW = num2(imgDim, "dimwidth"), dimH = num2(imgDim, "dimheight");
999
+ const refW = dimW > 0 ? dimW : num2(orgSz, "width");
1000
+ const refH = dimH > 0 ? dimH : num2(orgSz, "height");
1001
+ const cl = num2(clip, "left"), ct = num2(clip, "top");
1002
+ const cr = num2(clip, "right", refW), cb = num2(clip, "bottom", refH);
1003
+ const cropped = refW > 0 && refH > 0 && clip != null && (cl > 0 || ct > 0 || cr < refW || cb < refH) && cr > cl && cb > ct;
1004
+ const symId = imageSymbol(loaded, ctx);
825
1005
  if (cropped) {
826
- ctx.svg.push(
827
- `<svg x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}" viewBox="${pt(cl)} ${pt(ct)} ${pt(cr - cl)} ${pt(cb - ct)}" preserveAspectRatio="none"><image x="0" y="0" width="${pt(orgW)}" height="${pt(orgH)}" preserveAspectRatio="none" href="${loaded.dataUri}"/></svg>`
1006
+ emit(
1007
+ ctx,
1008
+ `<svg x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}" viewBox="${pt(cl)} ${pt(ct)} ${pt(cr - cl)} ${pt(cb - ct)}" preserveAspectRatio="none"><use href="#${symId}" x="0" y="0" width="${pt(refW)}" height="${pt(refH)}"/></svg>`
828
1009
  );
829
1010
  } else {
830
- ctx.svg.push(`<image x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}" preserveAspectRatio="none" href="${loaded.dataUri}"/>`);
1011
+ emit(ctx, `<use href="#${symId}" x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}"/>`);
831
1012
  }
832
1013
  }
833
1014
  function sniffMime(name, bytes) {
@@ -850,12 +1031,13 @@ async function renderHwpxToSvg(input, options) {
850
1031
  if (!secFile) throw new KordocError("Contents/section0.xml \uC5C6\uC74C \u2014 HWPX\uAC00 \uC544\uB2C8\uAC70\uB098 \uC190\uC0C1\uB428");
851
1032
  const secXml = await secFile.async("string");
852
1033
  if (secXml.length > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC139\uC158 XML\uC774 \uD5C8\uC6A9 \uD06C\uAE30\uB97C \uCD08\uACFC");
853
- if (!secXml.includes("linesegarray")) {
854
- throw new KordocError("\uC870\uD310 \uCE90\uC2DC(linesegarray) \uC5C6\uC74C \u2014 \uD55C\uCEF4\uC5D0\uC11C \uC800\uC7A5\uD55C HWPX\uB9CC \uB80C\uB354 \uAC00\uB2A5");
1034
+ const hasCache = /<(?:[A-Za-z][\w.-]*:)?linesegarray[\s/>]/.test(secXml);
1035
+ if (!hasCache && !options?.reflow) {
1036
+ throw new KordocError("\uC870\uD310 \uCE90\uC2DC(linesegarray) \uC5C6\uC74C \u2014 \uD55C\uCEF4\uC5D0\uC11C \uC800\uC7A5\uD55C HWPX\uB9CC \uB80C\uB354 \uAC00\uB2A5 (reflow \uC635\uC158\uC73C\uB85C \uD569\uC131 \uB80C\uB354 \uAC00\uB2A5)");
855
1037
  }
856
1038
  const warnings = [];
857
1039
  const headFile = zip.file("Contents/header.xml") ?? zip.file("Contents/head.xml");
858
- const styles = headFile ? parseRenderStyles(await headFile.async("string")) : { charPr: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), borderFill: /* @__PURE__ */ new Map() };
1040
+ const styles = headFile ? parseRenderStyles(await headFile.async("string")) : { charPr: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), paraGeom: /* @__PURE__ */ new Map(), borderFill: /* @__PURE__ */ new Map() };
859
1041
  if (!headFile) warnings.push("header.xml \uC5C6\uC74C \u2014 \uAE30\uBCF8 \uC2A4\uD0C0\uC77C\uB85C \uB80C\uB354");
860
1042
  const binmap = /* @__PURE__ */ new Map();
861
1043
  const hpf = zip.file(/content\.hpf$/i)[0];
@@ -864,10 +1046,17 @@ async function renderHwpxToSvg(input, options) {
864
1046
  for (const m of man.matchAll(/<[^>]*\bid="([^"]+)"[^>]*\bhref="(BinData\/[^"]+)"[^>]*>/g)) binmap.set(m[1], m[2]);
865
1047
  for (const m of man.matchAll(/<[^>]*\bhref="(BinData\/[^"]+)"[^>]*\bid="([^"]+)"[^>]*>/g)) binmap.set(m[2], m[1]);
866
1048
  }
1049
+ const MAX_IMAGE_REFS = 256;
1050
+ const MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024;
867
1051
  const images = /* @__PURE__ */ new Map();
868
1052
  const refs = /* @__PURE__ */ new Set();
869
1053
  for (const m of secXml.matchAll(/binaryItemIDRef="([^"]+)"/g)) refs.add(m[1]);
1054
+ let totalImgBytes = 0;
870
1055
  for (const ref of refs) {
1056
+ if (images.size >= MAX_IMAGE_REFS) {
1057
+ warnings.push(`\uC774\uBBF8\uC9C0 ${refs.size}\uC885 \uC911 ${MAX_IMAGE_REFS}\uC885\uB9CC \uB85C\uB529 \u2014 \uAC1C\uC218 \uD55C\uB3C4 \uCD08\uACFC\uBD84 \uC0DD\uB7B5`);
1058
+ break;
1059
+ }
871
1060
  let href = binmap.get(ref);
872
1061
  if (!href) {
873
1062
  const cand = zip.file(new RegExp(`BinData/.*${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "i"))[0];
@@ -881,6 +1070,11 @@ async function renderHwpxToSvg(input, options) {
881
1070
  warnings.push(`\uC774\uBBF8\uC9C0 ${href} ${(bytes.length / 1048576).toFixed(1)}MB \u2014 \uD55C\uB3C4 \uCD08\uACFC\uB85C \uC0DD\uB7B5`);
882
1071
  continue;
883
1072
  }
1073
+ if (totalImgBytes + bytes.length > MAX_TOTAL_IMAGE_BYTES) {
1074
+ warnings.push(`\uC774\uBBF8\uC9C0 \uB204\uC801 ${Math.round(MAX_TOTAL_IMAGE_BYTES / 1048576)}MB \uD55C\uB3C4 \uCD08\uACFC \u2014 \uC774\uD6C4 \uC0DD\uB7B5`);
1075
+ break;
1076
+ }
1077
+ totalImgBytes += bytes.length;
884
1078
  images.set(ref, { dataUri: `data:${sniffMime(href, bytes)};base64,${Buffer.from(bytes).toString("base64")}` });
885
1079
  }
886
1080
  const doc = createXmlParser().parseFromString(secXml, "text/xml");
@@ -888,44 +1082,71 @@ async function renderHwpxToSvg(input, options) {
888
1082
  if (!root) throw new KordocError("\uC139\uC158 XML \uD30C\uC2F1 \uC2E4\uD328");
889
1083
  const pagePr = findFirst(root, "pagePr");
890
1084
  const margin = pagePr ? findChildByLocalName(pagePr, "margin") : null;
891
- const PW = num(pagePr, "width", 59528), PH = num(pagePr, "height", 84188);
892
- const ML = num(margin, "left", 8504);
893
- const MT = num(margin, "top", 5668) + num(margin, "header", 0);
894
- const BODY_H = PH - MT - num(margin, "bottom", 4252) - num(margin, "footer", 0);
895
- const BODY_W = PW - ML - num(margin, "right", 8504);
1085
+ const PW = num2(pagePr, "width", 59528), PH = num2(pagePr, "height", 84188);
1086
+ const ML = num2(margin, "left", 8504);
1087
+ const MT = num2(margin, "top", 5668) + num2(margin, "header", 0);
1088
+ const BODY_H = PH - MT - num2(margin, "bottom", 4252) - num2(margin, "footer", 0);
1089
+ const BODY_W = PW - ML - num2(margin, "right", 8504);
1090
+ if (!hasCache) reflowSection(root, styles, { BODY_W, BODY_H }, options?.reflowMode ?? "keep");
1091
+ const colPr = findFirst(root, "colPr");
1092
+ const multiCol = num2(colPr, "colCount", 1) > 1;
1093
+ const paraSegPages = /* @__PURE__ */ new Map();
1094
+ let nPages = 1;
1095
+ let maxTopV = 0;
1096
+ {
1097
+ let prevV = Number.NEGATIVE_INFINITY;
1098
+ let prevH = Number.NEGATIVE_INFINITY;
1099
+ let cur = 0;
1100
+ for (const p of elements2(root)) {
1101
+ if (ln2(p) !== "p") continue;
1102
+ const lsa = findChildByLocalName(p, "linesegarray");
1103
+ const segEls = lsa ? elements2(lsa).filter((s) => ln2(s) === "lineseg") : [];
1104
+ const pagesOf = [];
1105
+ for (const s of segEls) {
1106
+ const v = num2(s, "vertpos");
1107
+ const h = num2(s, "horzpos");
1108
+ if (v < prevV && (!multiCol || h <= prevH)) cur++;
1109
+ pagesOf.push(cur);
1110
+ maxTopV = Math.max(maxTopV, v + num2(s, "textheight", 1e3));
1111
+ prevV = v;
1112
+ prevH = h;
1113
+ }
1114
+ paraSegPages.set(p, pagesOf);
1115
+ nPages = Math.max(nPages, cur + 1);
1116
+ }
1117
+ }
896
1118
  const ctx = {
897
- svg: [],
1119
+ pages: Array.from({ length: nPages }, () => []),
1120
+ page: 0,
898
1121
  geom: { PW, PH, ML, MT, BODY_W, BODY_H },
899
1122
  styles,
900
1123
  images,
1124
+ defs: [],
1125
+ highlights: (options?.highlights ?? []).map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0),
901
1126
  warnings,
902
1127
  warned: /* @__PURE__ */ new Set(),
903
1128
  stats: { texts: 0, images: 0, tables: 0 }
904
1129
  };
905
- for (const p of elements(root)) {
906
- if (ln(p) !== "p") continue;
907
- const segs = findChildByLocalName(p, "linesegarray");
908
- const first = segs ? elements(segs)[0] : null;
909
- if (first && num(first, "vertpos") > BODY_H) continue;
910
- drawPara(p, ML, MT, BODY_W, ctx, 0);
911
- }
912
- const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pt(PW)} ${pt(PH)}" width="${pt(PW)}pt" height="${pt(PH)}pt" font-family="'HCR Batang','\uD568\uCD08\uB86C\uBC14\uD0D5','Hancom Batang',AppleMyungjo,'Noto Serif CJK KR',serif" xml:space="preserve">
913
- <rect width="100%" height="100%" fill="white"/>
914
- ${ctx.svg.join("\n")}
1130
+ for (const p of elements2(root)) {
1131
+ if (ln2(p) !== "p") continue;
1132
+ drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p));
1133
+ }
1134
+ const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2e3) : PH;
1135
+ const GAP = 2400;
1136
+ const totalH = nPages * pageH + (nPages - 1) * GAP;
1137
+ const pagesSvg = ctx.pages.map(
1138
+ (buf, k) => `<g data-page="${k + 1}" transform="translate(0 ${pt(k * (pageH + GAP))})"><rect width="${pt(PW)}" height="${pt(pageH)}" fill="white" stroke="#c9c7c4" stroke-width="0.75"/><g clip-path="url(#pgclip)">
1139
+ ${buf.join("\n")}
1140
+ </g></g>`
1141
+ ).join("\n");
1142
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pt(PW)} ${pt(totalH)}" width="${pt(PW)}pt" height="${pt(totalH)}pt" font-family="'HCR Batang','\uD568\uCD08\uB86C\uBC14\uD0D5','Hancom Batang',AppleMyungjo,'Noto Serif CJK KR',serif" xml:space="preserve">
1143
+ <defs><clipPath id="pgclip"><rect x="0" y="0" width="${pt(PW)}" height="${pt(pageH)}"/></clipPath>${ctx.defs.join("")}</defs>
1144
+ ${pagesSvg}
915
1145
  </svg>`;
916
- return { svg, width: Math.round(PW) / 100, height: Math.round(PH) / 100, warnings, stats: ctx.stats };
1146
+ return { svg, width: Math.round(PW) / 100, height: Math.round(totalH) / 100, pageCount: nPages, warnings, stats: ctx.stats };
917
1147
  }
918
1148
 
919
1149
  export {
920
- MAX_DECOMPRESS_SIZE,
921
- MAX_ZIP_ENTRIES,
922
- clampSpan,
923
- MAX_XML_DEPTH,
924
- createSectionShared,
925
- createXmlParser,
926
- applyPageText,
927
- findChildByLocalName,
928
- extractTextFromNode,
929
1150
  charWidthEm1000,
930
1151
  SPACE_EM_FIXED,
931
1152
  SPACE_EM_FONT,
@@ -935,4 +1156,4 @@ export {
935
1156
  fitRatioForFewerLines,
936
1157
  renderHwpxToSvg
937
1158
  };
938
- //# sourceMappingURL=chunk-33CIKNL4.js.map
1159
+ //# sourceMappingURL=chunk-5RHRQ7HK.js.map