kordoc 3.16.1 → 3.17.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 (53) hide show
  1. package/README.md +32 -12
  2. package/dist/{-YVONYEEN.js → -3KPDRR3A.js} +7 -7
  3. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  4. package/dist/{chunk-QOPY6URE.js → chunk-E6NQWOFQ.js} +2 -2
  5. package/dist/{chunk-VNIQK4AL.cjs → chunk-EL2Y4BL2.cjs} +2 -2
  6. package/dist/chunk-EL2Y4BL2.cjs.map +1 -0
  7. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  8. package/dist/{chunk-K4L563FA.js → chunk-HVEEFHN7.js} +2 -2
  9. package/dist/{chunk-IY3NX4FT.js → chunk-NKICMZ7F.js} +79 -3
  10. package/dist/chunk-NKICMZ7F.js.map +1 -0
  11. package/dist/{chunk-CDFCA37S.js → chunk-SADQWCWR.js} +127 -68
  12. package/dist/chunk-SADQWCWR.js.map +1 -0
  13. package/dist/{chunk-7WUTJXKP.js → chunk-TDIDUEBH.js} +3 -3
  14. package/dist/{chunk-I6T3DKYK.js → chunk-VUS6LRVY.js} +7 -7
  15. package/dist/{chunk-F62GOZFB.js → chunk-WHMNPQXW.js} +2 -2
  16. package/dist/cli.js +12 -12
  17. package/dist/formula-5NKVS2LR.cjs.map +1 -1
  18. package/dist/index.cjs +393 -258
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +3 -2
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.js +203 -68
  23. package/dist/index.js.map +1 -1
  24. package/dist/mcp.js +8 -8
  25. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  26. package/dist/{parser-B4O7CW5Q.js → parser-CYJMOXUR.js} +4 -4
  27. package/dist/parser-CYJMOXUR.js.map +1 -0
  28. package/dist/{parser-3O2UF6VO.cjs → parser-I5GC4QQ5.cjs} +15 -15
  29. package/dist/parser-I5GC4QQ5.cjs.map +1 -0
  30. package/dist/{parser-BNEAMKUF.js → parser-LHBXIKXX.js} +3 -3
  31. package/dist/parser-LHBXIKXX.js.map +1 -0
  32. package/dist/provider-G4C2V2PD.cjs.map +1 -1
  33. package/dist/render-HY7KKYPV.js +10 -0
  34. package/dist/seal-ITGYHENO.js +10 -0
  35. package/dist/{watch-RLF5F3TN.js → watch-VT7BSLNI.js} +7 -7
  36. package/package.json +1 -1
  37. package/dist/chunk-CDFCA37S.js.map +0 -1
  38. package/dist/chunk-IY3NX4FT.js.map +0 -1
  39. package/dist/chunk-VNIQK4AL.cjs.map +0 -1
  40. package/dist/parser-3O2UF6VO.cjs.map +0 -1
  41. package/dist/parser-B4O7CW5Q.js.map +0 -1
  42. package/dist/parser-BNEAMKUF.js.map +0 -1
  43. package/dist/render-QCNMRR7L.js +0 -10
  44. package/dist/seal-REDSMUI5.js +0 -10
  45. /package/dist/{-YVONYEEN.js.map → -3KPDRR3A.js.map} +0 -0
  46. /package/dist/{chunk-QOPY6URE.js.map → chunk-E6NQWOFQ.js.map} +0 -0
  47. /package/dist/{chunk-K4L563FA.js.map → chunk-HVEEFHN7.js.map} +0 -0
  48. /package/dist/{chunk-7WUTJXKP.js.map → chunk-TDIDUEBH.js.map} +0 -0
  49. /package/dist/{chunk-I6T3DKYK.js.map → chunk-VUS6LRVY.js.map} +0 -0
  50. /package/dist/{chunk-F62GOZFB.js.map → chunk-WHMNPQXW.js.map} +0 -0
  51. /package/dist/{render-QCNMRR7L.js.map → render-HY7KKYPV.js.map} +0 -0
  52. /package/dist/{seal-REDSMUI5.js.map → seal-ITGYHENO.js.map} +0 -0
  53. /package/dist/{watch-RLF5F3TN.js.map → watch-VT7BSLNI.js.map} +0 -0
package/dist/index.d.cts CHANGED
@@ -1120,8 +1120,9 @@ interface RenderSvgResult {
1120
1120
  };
1121
1121
  }
1122
1122
  /**
1123
- * HWPX(한컴 저장본) → 레이아웃 보존 SVG (1페이지).
1124
- * 조판 캐시(linesegarray)가 없는 파일(예: markdownToHwpx 산출물)은 KordocError.
1123
+ * HWPX(한컴 저장본) → 레이아웃 보존 SVG. **전 구역(section*)을 세로 스택으로** 렌더한다.
1124
+ * 조판 캐시(linesegarray)가 없는 구역은 reflow 옵션으로 합성 조판(없으면 구역 생략);
1125
+ * 렌더 가능한 구역이 하나도 없으면 KordocError.
1125
1126
  */
1126
1127
  declare function renderHwpxToSvg(input: ArrayBuffer | Uint8Array, options?: RenderSvgOptions): Promise<RenderSvgResult>;
1127
1128
 
package/dist/index.d.ts CHANGED
@@ -1120,8 +1120,9 @@ interface RenderSvgResult {
1120
1120
  };
1121
1121
  }
1122
1122
  /**
1123
- * HWPX(한컴 저장본) → 레이아웃 보존 SVG (1페이지).
1124
- * 조판 캐시(linesegarray)가 없는 파일(예: markdownToHwpx 산출물)은 KordocError.
1123
+ * HWPX(한컴 저장본) → 레이아웃 보존 SVG. **전 구역(section*)을 세로 스택으로** 렌더한다.
1124
+ * 조판 캐시(linesegarray)가 없는 구역은 reflow 옵션으로 합성 조판(없으면 구역 생략);
1125
+ * 렌더 가능한 구역이 하나도 없으면 KordocError.
1125
1126
  */
1126
1127
  declare function renderHwpxToSvg(input: ArrayBuffer | Uint8Array, options?: RenderSvgOptions): Promise<RenderSvgResult>;
1127
1128
 
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  sanitizeHref,
20
20
  stripDtd,
21
21
  toArrayBuffer
22
- } from "./chunk-QOPY6URE.js";
22
+ } from "./chunk-E6NQWOFQ.js";
23
23
  import {
24
24
  parsePageRange
25
25
  } from "./chunk-GE43BE46.js";
@@ -22833,6 +22833,77 @@ var DEFAULT_PARA_GEOM = {
22833
22833
  spaceAfter: 0
22834
22834
  };
22835
22835
  var DEFAULT_CHAR = { height: 1e3, bold: false, italic: false, underline: false, ratio: 100, spacing: 0 };
22836
+ var FONT_ALIASES = {
22837
+ "\uD568\uCD08\uB86C\uBC14\uD0D5": "'HCR Batang','\uD568\uCD08\uB86C\uBC14\uD0D5','\uD55C\uCEF4\uBC14\uD0D5'",
22838
+ "\uD55C\uCEF4\uBC14\uD0D5": "'HCR Batang','\uD568\uCD08\uB86C\uBC14\uD0D5','\uD55C\uCEF4\uBC14\uD0D5'",
22839
+ "\uD568\uCD08\uB86C\uB3CB\uC6C0": "'HCR Dotum','\uD568\uCD08\uB86C\uB3CB\uC6C0','\uD55C\uCEF4\uB3CB\uC6C0'",
22840
+ "\uD55C\uCEF4\uB3CB\uC6C0": "'HCR Dotum','\uD568\uCD08\uB86C\uB3CB\uC6C0','\uD55C\uCEF4\uB3CB\uC6C0'",
22841
+ "\uB9D1\uC740 \uACE0\uB515": "'Malgun Gothic','\uB9D1\uC740 \uACE0\uB515'",
22842
+ "\uB9D1\uC740\uACE0\uB515": "'Malgun Gothic','\uB9D1\uC740 \uACE0\uB515'",
22843
+ "\uAD74\uB9BC": "'Gulim','\uAD74\uB9BC'",
22844
+ "\uAD74\uB9BC\uCCB4": "'GulimChe','\uAD74\uB9BC\uCCB4','Gulim'",
22845
+ "\uB3CB\uC6C0": "'Dotum','\uB3CB\uC6C0'",
22846
+ "\uB3CB\uC6C0\uCCB4": "'DotumChe','\uB3CB\uC6C0\uCCB4','Dotum'",
22847
+ "\uBC14\uD0D5": "'Batang','\uBC14\uD0D5'",
22848
+ "\uBC14\uD0D5\uCCB4": "'BatangChe','\uBC14\uD0D5\uCCB4','Batang'",
22849
+ "\uAD81\uC11C": "'Gungsuh','\uAD81\uC11C'",
22850
+ "\uAD81\uC11C\uCCB4": "'GungsuhChe','\uAD81\uC11C\uCCB4','Gungsuh'",
22851
+ "\uB098\uB214\uACE0\uB515": "'NanumGothic','\uB098\uB214\uACE0\uB515'",
22852
+ "\uB098\uB214\uBA85\uC870": "'NanumMyeongjo','\uB098\uB214\uBA85\uC870'",
22853
+ "\uB9D1\uC740 \uACE0\uB515 Semilight": "'Malgun Gothic Semilight','\uB9D1\uC740 \uACE0\uB515'"
22854
+ };
22855
+ function isSerifFace(face) {
22856
+ return /바탕|명조|Batang|Myeong|Mincho|궁서|Gungsuh|Serif|신명|순명|Song|송/i.test(face);
22857
+ }
22858
+ function cssQuote(name) {
22859
+ return /^[A-Za-z0-9_-]+$/.test(name) ? name : `'${name.replace(/['"\\]/g, "")}'`;
22860
+ }
22861
+ function hwpFaceToCssStack(face) {
22862
+ const trimmed = (face ?? "").trim();
22863
+ if (!trimmed) return "";
22864
+ const generic = isSerifFace(trimmed) ? "'HCR Batang','Batang','Noto Serif KR',serif" : "'Malgun Gothic','HCR Dotum','Noto Sans KR',sans-serif";
22865
+ const head = FONT_ALIASES[trimmed] ?? cssQuote(trimmed);
22866
+ return `${head},${generic}`;
22867
+ }
22868
+ function collectHangulFonts(root) {
22869
+ const map = /* @__PURE__ */ new Map();
22870
+ const findFaces = (el, depth) => {
22871
+ if (depth > 24) return null;
22872
+ for (const ch of Array.from(el.childNodes)) {
22873
+ if (ch.nodeType !== 1) continue;
22874
+ const e = ch;
22875
+ if ((e.tagName || "").replace(/^[^:]+:/, "") === "fontfaces") return e;
22876
+ const f = findFaces(e, depth + 1);
22877
+ if (f) return f;
22878
+ }
22879
+ return null;
22880
+ };
22881
+ const faces = findFaces(root, 0);
22882
+ if (!faces) return map;
22883
+ let group = null;
22884
+ let firstGroup = null;
22885
+ for (const ch of Array.from(faces.childNodes)) {
22886
+ if (ch.nodeType !== 1) continue;
22887
+ const e = ch;
22888
+ if ((e.tagName || "").replace(/^[^:]+:/, "") !== "fontface") continue;
22889
+ if (!firstGroup) firstGroup = e;
22890
+ if ((e.getAttribute("lang") ?? "").toUpperCase() === "HANGUL") {
22891
+ group = e;
22892
+ break;
22893
+ }
22894
+ }
22895
+ group = group ?? firstGroup;
22896
+ if (!group) return map;
22897
+ for (const ch of Array.from(group.childNodes)) {
22898
+ if (ch.nodeType !== 1) continue;
22899
+ const e = ch;
22900
+ if ((e.tagName || "").replace(/^[^:]+:/, "") !== "font") continue;
22901
+ const id = e.getAttribute("id");
22902
+ const face = e.getAttribute("face");
22903
+ if (id != null && face) map.set(id, face);
22904
+ }
22905
+ return map;
22906
+ }
22836
22907
  function borderWidthPt(v) {
22837
22908
  const n = parseFloat(v ?? "");
22838
22909
  if (!Number.isFinite(n)) return 0.34;
@@ -22884,6 +22955,7 @@ function parseRenderStyles(headXml) {
22884
22955
  const doc = createXmlParser().parseFromString(headXml, "text/xml");
22885
22956
  const root = doc.documentElement;
22886
22957
  if (!root) return styles;
22958
+ const hangulFonts = collectHangulFonts(root);
22887
22959
  const walk = (el) => {
22888
22960
  const tag = (el.tagName || "").replace(/^[^:]+:/, "");
22889
22961
  if (tag === "charPr") {
@@ -22893,6 +22965,9 @@ function parseRenderStyles(headXml) {
22893
22965
  const spacingEl = findChildByLocalName(el, "spacing");
22894
22966
  const underlineEl = findChildByLocalName(el, "underline");
22895
22967
  const textColor = el.getAttribute("textColor");
22968
+ const fontRef = findChildByLocalName(el, "fontRef");
22969
+ const fontId = fontRef?.getAttribute("hangul") ?? fontRef?.getAttribute("latin");
22970
+ const face = fontId != null ? hangulFonts.get(fontId) : void 0;
22896
22971
  styles.charPr.set(id, {
22897
22972
  height: Number(el.getAttribute("height")) || 1e3,
22898
22973
  bold: findChildByLocalName(el, "bold") != null,
@@ -22900,7 +22975,8 @@ function parseRenderStyles(headXml) {
22900
22975
  underline: underlineEl != null && (underlineEl.getAttribute("type") ?? "NONE") !== "NONE",
22901
22976
  color: textColor && textColor !== "#000000" && textColor.toLowerCase() !== "none" ? textColor : void 0,
22902
22977
  ratio: Number(ratioEl?.getAttribute("hangul")) || 100,
22903
- spacing: Number(spacingEl?.getAttribute("hangul")) || 0
22978
+ spacing: Number(spacingEl?.getAttribute("hangul")) || 0,
22979
+ fontFamily: face ? hwpFaceToCssStack(face) : void 0
22904
22980
  });
22905
22981
  }
22906
22982
  } else if (tag === "paraPr") {
@@ -26031,7 +26107,7 @@ function reflowPara(p, doc, styles, areaW, startV, mode) {
26031
26107
  const lsa = doc.createElement("hp:linesegarray");
26032
26108
  for (let li = 0; li < wrap.starts.length; li++) {
26033
26109
  const startReal = wrap.starts[li];
26034
- const textpos = startReal < realIdx.length ? realIdx[startReal] : m.chars.length;
26110
+ const textpos = startReal < realIdx.length ? realIdx[startReal] : 0;
26035
26111
  const vertpos = startV + li * pitch;
26036
26112
  const isFirst = li === 0;
26037
26113
  const seg = doc.createElement("hp:lineseg");
@@ -26311,6 +26387,7 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
26311
26387
  }
26312
26388
  if (text.trim().length > 0) {
26313
26389
  const attrs = [`x="${pt(cx)}"`, `y="${pt(y)}"`, `font-size="${pt(st.height)}"`];
26390
+ if (st.fontFamily) attrs.push(`font-family="${st.fontFamily}"`);
26314
26391
  if ([...text].length > 1 && sw > 50) {
26315
26392
  attrs.push(`textLength="${pt(sw)}"`, `lengthAdjust="${plan.scale < 1 ? "spacingAndGlyphs" : "spacing"}"`);
26316
26393
  }
@@ -26486,7 +26563,11 @@ function cellContentExtent(cell) {
26486
26563
  for (const s of m.segs) ext = Math.max(ext, s.vertpos + s.textheight);
26487
26564
  const baseV = m.segs[0]?.vertpos ?? 0;
26488
26565
  for (const o of m.objs) {
26489
- if (o.inline) continue;
26566
+ if (o.inline) {
26567
+ const h = o.tag === "tbl" ? Math.max(o.height, measureTableHeight(o.el)) : o.height;
26568
+ ext = Math.max(ext, baseV + h);
26569
+ continue;
26570
+ }
26490
26571
  const pos = findChildByLocalName(o.el, "pos");
26491
26572
  if ((pos?.getAttribute("vertRelTo") ?? "PARA") !== "PARA") continue;
26492
26573
  const om = findChildByLocalName(o.el, "outMargin");
@@ -26647,6 +26728,67 @@ function sniffMime(name, bytes) {
26647
26728
  if (lower.endsWith(".svg")) return "image/svg+xml";
26648
26729
  return "image/jpeg";
26649
26730
  }
26731
+ function readSectionGeom(root) {
26732
+ const pagePr = findFirst(root, "pagePr");
26733
+ const margin = pagePr ? findChildByLocalName(pagePr, "margin") : null;
26734
+ let PW = num2(pagePr, "width", 59528), PH = num2(pagePr, "height", 84188);
26735
+ if (pagePr?.getAttribute("landscape") === "NARROWLY" && PW < PH) {
26736
+ const t = PW;
26737
+ PW = PH;
26738
+ PH = t;
26739
+ }
26740
+ const ML = num2(margin, "left", 8504);
26741
+ const MT = num2(margin, "top", 5668) + num2(margin, "header", 0);
26742
+ const BODY_H = PH - MT - num2(margin, "bottom", 4252) - num2(margin, "footer", 0);
26743
+ const BODY_W = PW - ML - num2(margin, "right", 8504);
26744
+ return { PW, PH, ML, MT, BODY_W, BODY_H };
26745
+ }
26746
+ function renderSectionToPages(root, geom, ctxBase, hasCache, reflowMode) {
26747
+ const { PW, PH, ML, MT, BODY_W, BODY_H } = geom;
26748
+ if (!hasCache) reflowSection(root, ctxBase.styles, { BODY_W, BODY_H }, reflowMode);
26749
+ const colPr = findFirst(root, "colPr");
26750
+ const multiCol = num2(colPr, "colCount", 1) > 1;
26751
+ const paraSegPages = /* @__PURE__ */ new Map();
26752
+ let nPages = 1;
26753
+ let maxTopV = 0;
26754
+ {
26755
+ let prevV = Number.NEGATIVE_INFINITY;
26756
+ let prevH = Number.NEGATIVE_INFINITY;
26757
+ let cur = 0;
26758
+ for (const p of elements2(root)) {
26759
+ if (ln2(p) !== "p") continue;
26760
+ const lsa = findChildByLocalName(p, "linesegarray");
26761
+ const segEls = lsa ? elements2(lsa).filter((s) => ln2(s) === "lineseg") : [];
26762
+ const pagesOf = [];
26763
+ let paraFirst = true;
26764
+ for (const s of segEls) {
26765
+ const v = num2(s, "vertpos");
26766
+ const h = num2(s, "horzpos");
26767
+ const brk = v < prevV ? !multiCol || h <= prevH : paraFirst && v === prevV && h <= prevH;
26768
+ if (brk) cur++;
26769
+ paraFirst = false;
26770
+ pagesOf.push(cur);
26771
+ maxTopV = Math.max(maxTopV, v + num2(s, "textheight", 1e3));
26772
+ prevV = v;
26773
+ prevH = h;
26774
+ }
26775
+ paraSegPages.set(p, pagesOf);
26776
+ nPages = Math.max(nPages, cur + 1);
26777
+ }
26778
+ }
26779
+ const ctx = {
26780
+ ...ctxBase,
26781
+ pages: Array.from({ length: nPages }, () => []),
26782
+ page: 0,
26783
+ geom
26784
+ };
26785
+ for (const p of elements2(root)) {
26786
+ if (ln2(p) !== "p") continue;
26787
+ drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p));
26788
+ }
26789
+ const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2e3) : PH;
26790
+ return { pages: ctx.pages, pageH };
26791
+ }
26650
26792
  async function renderHwpxToSvg(input, options) {
26651
26793
  const maxImg = options?.maxImageBytes ?? 40 * 1024 * 1024;
26652
26794
  const ab = input instanceof Uint8Array ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) : input;
@@ -26657,18 +26799,18 @@ async function renderHwpxToSvg(input, options) {
26657
26799
  } catch {
26658
26800
  throw new KordocError("HWPX(ZIP) \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4 \u2014 \uB80C\uB354\uB294 HWPX\uB9CC \uC9C0\uC6D0");
26659
26801
  }
26660
- const secFile = zip.file("Contents/section0.xml") ?? zip.file(/Contents\/section\d+\.xml$/i).sort((a, b) => a.name.localeCompare(b.name))[0];
26661
- if (!secFile) throw new KordocError("Contents/section0.xml \uC5C6\uC74C \u2014 HWPX\uAC00 \uC544\uB2C8\uAC70\uB098 \uC190\uC0C1\uB428");
26662
- const secXml = await secFile.async("string");
26663
- if (secXml.length > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC139\uC158 XML\uC774 \uD5C8\uC6A9 \uD06C\uAE30\uB97C \uCD08\uACFC");
26664
- const hasCache = /<(?:[A-Za-z][\w.-]*:)?linesegarray[\s/>]/.test(secXml);
26665
- if (!hasCache && !options?.reflow) {
26666
- 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)");
26667
- }
26802
+ const secFiles = zip.file(/Contents\/section\d+\.xml$/i).sort((a, b) => a.name.localeCompare(b.name));
26803
+ if (secFiles.length === 0) throw new KordocError("Contents/section0.xml \uC5C6\uC74C \u2014 HWPX\uAC00 \uC544\uB2C8\uAC70\uB098 \uC190\uC0C1\uB428");
26668
26804
  const warnings = [];
26669
26805
  const headFile = zip.file("Contents/header.xml") ?? zip.file("Contents/head.xml");
26670
26806
  const styles = headFile ? parseRenderStyles(await headFile.async("string")) : { charPr: /* @__PURE__ */ new Map(), paraAlign: /* @__PURE__ */ new Map(), paraGeom: /* @__PURE__ */ new Map(), borderFill: /* @__PURE__ */ new Map() };
26671
26807
  if (!headFile) warnings.push("header.xml \uC5C6\uC74C \u2014 \uAE30\uBCF8 \uC2A4\uD0C0\uC77C\uB85C \uB80C\uB354");
26808
+ const secXmls = [];
26809
+ for (const f of secFiles) {
26810
+ const xml = await f.async("string");
26811
+ if (xml.length > MAX_DECOMPRESS_SIZE) throw new KordocError("\uC139\uC158 XML\uC774 \uD5C8\uC6A9 \uD06C\uAE30\uB97C \uCD08\uACFC");
26812
+ secXmls.push(xml);
26813
+ }
26672
26814
  const binmap = /* @__PURE__ */ new Map();
26673
26815
  const hpf = zip.file(/content\.hpf$/i)[0];
26674
26816
  if (hpf) {
@@ -26680,7 +26822,7 @@ async function renderHwpxToSvg(input, options) {
26680
26822
  const MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024;
26681
26823
  const images = /* @__PURE__ */ new Map();
26682
26824
  const refs = /* @__PURE__ */ new Set();
26683
- for (const m of secXml.matchAll(/binaryItemIDRef="([^"]+)"/g)) refs.add(m[1]);
26825
+ for (const xml of secXmls) for (const m of xml.matchAll(/binaryItemIDRef="([^"]+)"/g)) refs.add(m[1]);
26684
26826
  let totalImgBytes = 0;
26685
26827
  for (const ref of refs) {
26686
26828
  if (images.size >= MAX_IMAGE_REFS) {
@@ -26707,48 +26849,7 @@ async function renderHwpxToSvg(input, options) {
26707
26849
  totalImgBytes += bytes.length;
26708
26850
  images.set(ref, { dataUri: `data:${sniffMime(href, bytes)};base64,${Buffer.from(bytes).toString("base64")}` });
26709
26851
  }
26710
- const doc = createXmlParser().parseFromString(secXml, "text/xml");
26711
- const root = doc.documentElement;
26712
- if (!root) throw new KordocError("\uC139\uC158 XML \uD30C\uC2F1 \uC2E4\uD328");
26713
- const pagePr = findFirst(root, "pagePr");
26714
- const margin = pagePr ? findChildByLocalName(pagePr, "margin") : null;
26715
- const PW = num2(pagePr, "width", 59528), PH = num2(pagePr, "height", 84188);
26716
- const ML = num2(margin, "left", 8504);
26717
- const MT = num2(margin, "top", 5668) + num2(margin, "header", 0);
26718
- const BODY_H = PH - MT - num2(margin, "bottom", 4252) - num2(margin, "footer", 0);
26719
- const BODY_W = PW - ML - num2(margin, "right", 8504);
26720
- if (!hasCache) reflowSection(root, styles, { BODY_W, BODY_H }, options?.reflowMode ?? "keep");
26721
- const colPr = findFirst(root, "colPr");
26722
- const multiCol = num2(colPr, "colCount", 1) > 1;
26723
- const paraSegPages = /* @__PURE__ */ new Map();
26724
- let nPages = 1;
26725
- let maxTopV = 0;
26726
- {
26727
- let prevV = Number.NEGATIVE_INFINITY;
26728
- let prevH = Number.NEGATIVE_INFINITY;
26729
- let cur = 0;
26730
- for (const p of elements2(root)) {
26731
- if (ln2(p) !== "p") continue;
26732
- const lsa = findChildByLocalName(p, "linesegarray");
26733
- const segEls = lsa ? elements2(lsa).filter((s) => ln2(s) === "lineseg") : [];
26734
- const pagesOf = [];
26735
- for (const s of segEls) {
26736
- const v = num2(s, "vertpos");
26737
- const h = num2(s, "horzpos");
26738
- if (v < prevV && (!multiCol || h <= prevH)) cur++;
26739
- pagesOf.push(cur);
26740
- maxTopV = Math.max(maxTopV, v + num2(s, "textheight", 1e3));
26741
- prevV = v;
26742
- prevH = h;
26743
- }
26744
- paraSegPages.set(p, pagesOf);
26745
- nPages = Math.max(nPages, cur + 1);
26746
- }
26747
- }
26748
- const ctx = {
26749
- pages: Array.from({ length: nPages }, () => []),
26750
- page: 0,
26751
- geom: { PW, PH, ML, MT, BODY_W, BODY_H },
26852
+ const ctxBase = {
26752
26853
  styles,
26753
26854
  images,
26754
26855
  defs: [],
@@ -26757,23 +26858,57 @@ async function renderHwpxToSvg(input, options) {
26757
26858
  warned: /* @__PURE__ */ new Set(),
26758
26859
  stats: { texts: 0, images: 0, tables: 0 }
26759
26860
  };
26760
- for (const p of elements2(root)) {
26761
- if (ln2(p) !== "p") continue;
26762
- drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p));
26861
+ const rendered = [];
26862
+ let noCacheSkipped = false;
26863
+ for (let si = 0; si < secXmls.length; si++) {
26864
+ const secXml = secXmls[si];
26865
+ const hasCache = /<(?:[A-Za-z][\w.-]*:)?linesegarray[\s/>]/.test(secXml);
26866
+ if (!hasCache && !options?.reflow) {
26867
+ noCacheSkipped = true;
26868
+ warnings.push(`\uAD6C\uC5ED ${si}: \uC870\uD310 \uCE90\uC2DC \uC5C6\uC74C \u2014 reflow \uC635\uC158 \uD544\uC694, \uC0DD\uB7B5`);
26869
+ continue;
26870
+ }
26871
+ const doc = createXmlParser().parseFromString(secXml, "text/xml");
26872
+ const root = doc.documentElement;
26873
+ if (!root) {
26874
+ warnings.push(`\uAD6C\uC5ED ${si} XML \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uC0DD\uB7B5`);
26875
+ continue;
26876
+ }
26877
+ const geom = readSectionGeom(root);
26878
+ const { pages, pageH } = renderSectionToPages(root, geom, ctxBase, hasCache, options?.reflowMode ?? "keep");
26879
+ rendered.push({ pages, PW: geom.PW, pageH, clipId: `pgclip${si}` });
26880
+ }
26881
+ if (rendered.length === 0) {
26882
+ if (noCacheSkipped) {
26883
+ 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)");
26884
+ }
26885
+ throw new KordocError("\uB80C\uB354\uD560 \uAD6C\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 HWPX\uAC00 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
26763
26886
  }
26764
- const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2e3) : PH;
26765
26887
  const GAP = 2400;
26766
- const totalH = nPages * pageH + (nPages - 1) * GAP;
26767
- const pagesSvg = ctx.pages.map(
26768
- (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)">
26888
+ const clipDefs = [];
26889
+ const groups = [];
26890
+ let y = 0;
26891
+ let maxPW = 0;
26892
+ let pageNo = 0;
26893
+ for (const rs of rendered) {
26894
+ maxPW = Math.max(maxPW, rs.PW);
26895
+ clipDefs.push(`<clipPath id="${rs.clipId}"><rect x="0" y="0" width="${pt(rs.PW)}" height="${pt(rs.pageH)}"/></clipPath>`);
26896
+ for (const buf of rs.pages) {
26897
+ pageNo++;
26898
+ groups.push(
26899
+ `<g data-page="${pageNo}" transform="translate(0 ${pt(y)})"><rect width="${pt(rs.PW)}" height="${pt(rs.pageH)}" fill="white" stroke="#c9c7c4" stroke-width="0.75"/><g clip-path="url(#${rs.clipId})">
26769
26900
  ${buf.join("\n")}
26770
26901
  </g></g>`
26771
- ).join("\n");
26772
- 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">
26773
- <defs><clipPath id="pgclip"><rect x="0" y="0" width="${pt(PW)}" height="${pt(pageH)}"/></clipPath>${ctx.defs.join("")}</defs>
26774
- ${pagesSvg}
26902
+ );
26903
+ y += rs.pageH + GAP;
26904
+ }
26905
+ }
26906
+ const totalH = Math.max(0, y - GAP);
26907
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pt(maxPW)} ${pt(totalH)}" width="${pt(maxPW)}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">
26908
+ <defs>${clipDefs.join("")}${ctxBase.defs.join("")}</defs>
26909
+ ${groups.join("\n")}
26775
26910
  </svg>`;
26776
- return { svg, width: Math.round(PW) / 100, height: Math.round(totalH) / 100, pageCount: nPages, warnings, stats: ctx.stats };
26911
+ return { svg, width: Math.round(maxPW) / 100, height: Math.round(totalH) / 100, pageCount: pageNo, warnings, stats: ctxBase.stats };
26777
26912
  }
26778
26913
 
26779
26914
  // src/index.ts
@@ -26863,7 +26998,7 @@ async function parseHwp(buffer, options) {
26863
26998
  async function parsePdf(buffer, options) {
26864
26999
  let parsePdfDocument;
26865
27000
  try {
26866
- const mod = await import("./parser-BNEAMKUF.js");
27001
+ const mod = await import("./parser-LHBXIKXX.js");
26867
27002
  parsePdfDocument = mod.parsePdfDocument;
26868
27003
  } catch {
26869
27004
  return {