kordoc 3.8.1 → 3.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +12 -0
  2. package/dist/{-LD4BZDDJ.js → -F5IXBLQQ.js} +3 -3
  3. package/dist/{chunk-PELBIL4K.js → chunk-7J73IGMK.js} +2 -2
  4. package/dist/{chunk-KT2BCHXI.js → chunk-B27QMVL7.js} +872 -825
  5. package/dist/chunk-B27QMVL7.js.map +1 -0
  6. package/dist/{chunk-IFYJFWD2.js → chunk-KIWKBRCJ.js} +2 -2
  7. package/dist/{chunk-LFCS3UVG.cjs → chunk-XL6O3VAY.cjs} +2 -2
  8. package/dist/{chunk-LFCS3UVG.cjs.map → chunk-XL6O3VAY.cjs.map} +1 -1
  9. package/dist/cli.js +4 -4
  10. package/dist/index.cjs +1065 -1018
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +17 -8
  13. package/dist/index.d.ts +17 -8
  14. package/dist/index.js +871 -824
  15. package/dist/index.js.map +1 -1
  16. package/dist/mcp.js +3 -3
  17. package/dist/{parser-FFEBMLSH.js → parser-6GP535ZB.js} +211 -25
  18. package/dist/parser-6GP535ZB.js.map +1 -0
  19. package/dist/{parser-XEDROIM7.js → parser-TM3AS25T.js} +211 -25
  20. package/dist/parser-TM3AS25T.js.map +1 -0
  21. package/dist/{parser-IXK5V7YG.cjs → parser-WWKYMRGJ.cjs} +240 -52
  22. package/dist/parser-WWKYMRGJ.cjs.map +1 -0
  23. package/dist/{watch-MAWCDNFI.js → watch-HETTZ7BO.js} +3 -3
  24. package/package.json +2 -1
  25. package/dist/chunk-KT2BCHXI.js.map +0 -1
  26. package/dist/parser-FFEBMLSH.js.map +0 -1
  27. package/dist/parser-IXK5V7YG.cjs.map +0 -1
  28. package/dist/parser-XEDROIM7.js.map +0 -1
  29. /package/dist/{-LD4BZDDJ.js.map → -F5IXBLQQ.js.map} +0 -0
  30. /package/dist/{chunk-PELBIL4K.js.map → chunk-7J73IGMK.js.map} +0 -0
  31. /package/dist/{chunk-IFYJFWD2.js.map → chunk-KIWKBRCJ.js.map} +0 -0
  32. /package/dist/{watch-MAWCDNFI.js.map → watch-HETTZ7BO.js.map} +0 -0
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  sanitizeHref,
20
20
  stripDtd,
21
21
  toArrayBuffer
22
- } from "./chunk-PELBIL4K.js";
22
+ } from "./chunk-7J73IGMK.js";
23
23
  import {
24
24
  parsePageRange
25
25
  } from "./chunk-GE43BE46.js";
@@ -16799,6 +16799,9 @@ function getElements(parent, tagName) {
16799
16799
  const nodes = parent.getElementsByTagName(tagName);
16800
16800
  const result = [];
16801
16801
  for (let i = 0; i < nodes.length; i++) result.push(nodes[i]);
16802
+ if (result.length > 0) return result;
16803
+ const nsNodes = parent.getElementsByTagNameNS?.("*", tagName);
16804
+ if (nsNodes) for (let i = 0; i < nsNodes.length; i++) result.push(nsNodes[i]);
16802
16805
  return result;
16803
16806
  }
16804
16807
  function getTextContent(el) {
@@ -18622,6 +18625,7 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
18622
18625
  if (tag === "P") {
18623
18626
  if (!inHeaderFooter) {
18624
18627
  parseParagraph3(el, blocks, paraShapeMap, sectionNum);
18628
+ walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings);
18625
18629
  }
18626
18630
  continue;
18627
18631
  }
@@ -18638,6 +18642,21 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
18638
18642
  walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth + 1);
18639
18643
  }
18640
18644
  }
18645
+ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, depth = 0) {
18646
+ if (depth > MAX_XML_DEPTH2) return;
18647
+ const children = node.childNodes;
18648
+ for (let i = 0; i < children.length; i++) {
18649
+ const el = children[i];
18650
+ if (el.nodeType !== 1) continue;
18651
+ const tag = localName(el);
18652
+ if (tag === "TABLE") {
18653
+ parseTable2(el, blocks, paraShapeMap, sectionNum, warnings);
18654
+ continue;
18655
+ }
18656
+ if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
18657
+ walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, depth + 1);
18658
+ }
18659
+ }
18641
18660
  function parseParagraph3(el, blocks, paraShapeMap, sectionNum) {
18642
18661
  const paraShapeId = el.getAttribute("ParaShape") ?? "";
18643
18662
  const shapeInfo = paraShapeMap.get(paraShapeId);
@@ -18733,13 +18752,29 @@ function collectCellText(node, parts, depth) {
18733
18752
  if (tag === "P") {
18734
18753
  const t = extractParagraphText(el);
18735
18754
  if (t) parts.push(t);
18755
+ collectNestedTableText(el, parts, depth + 1);
18736
18756
  } else if (tag === "TABLE") {
18737
- parts.push("[\uC911\uCCA9 \uD14C\uC774\uBE14]");
18757
+ collectCellText(el, parts, depth + 1);
18738
18758
  } else {
18739
18759
  collectCellText(el, parts, depth + 1);
18740
18760
  }
18741
18761
  }
18742
18762
  }
18763
+ function collectNestedTableText(node, parts, depth) {
18764
+ if (depth > 20) return;
18765
+ const children = node.childNodes;
18766
+ for (let i = 0; i < children.length; i++) {
18767
+ const el = children[i];
18768
+ if (el.nodeType !== 1) continue;
18769
+ const tag = localName(el);
18770
+ if (tag === "TABLE") {
18771
+ collectCellText(el, parts, depth + 1);
18772
+ continue;
18773
+ }
18774
+ if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
18775
+ collectNestedTableText(el, parts, depth + 1);
18776
+ }
18777
+ }
18743
18778
  function localName(el) {
18744
18779
  return (el.tagName || el.localName || "").replace(/^[^:]+:/, "");
18745
18780
  }
@@ -20622,53 +20657,112 @@ function mmToHwpunit(mm) {
20622
20657
  return Math.round(mm * 7200 / 25.4);
20623
20658
  }
20624
20659
 
20625
- // src/diff/text-diff.ts
20626
- function similarity(a, b) {
20627
- if (a === b) return 1;
20628
- if (!a || !b) return 0;
20629
- const maxLen = Math.max(a.length, b.length);
20630
- if (maxLen === 0) return 1;
20631
- return 1 - levenshtein(a, b) / maxLen;
20660
+ // src/hwpx/gen-ids.ts
20661
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
20662
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
20663
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
20664
+ var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
20665
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
20666
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
20667
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
20668
+ var CHAR_NORMAL = 0;
20669
+ var CHAR_BOLD = 1;
20670
+ var CHAR_ITALIC = 2;
20671
+ var CHAR_BOLD_ITALIC = 3;
20672
+ var CHAR_CODE = 4;
20673
+ var CHAR_H1 = 5;
20674
+ var CHAR_H2 = 6;
20675
+ var CHAR_H3 = 7;
20676
+ var CHAR_H4 = 8;
20677
+ var CHAR_TABLE_HEADER = 9;
20678
+ var CHAR_QUOTE = 10;
20679
+ var PARA_NORMAL = 0;
20680
+ var PARA_H1 = 1;
20681
+ var PARA_H2 = 2;
20682
+ var PARA_H3 = 3;
20683
+ var PARA_H4 = 4;
20684
+ var PARA_CODE = 5;
20685
+ var PARA_QUOTE = 6;
20686
+ var PARA_LIST = 7;
20687
+ var DEFAULT_TEXT_COLOR = "#000000";
20688
+ function resolveTheme(theme) {
20689
+ return {
20690
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
20691
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
20692
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20693
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20694
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20695
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
20696
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
20697
+ hasQuoteOption: theme?.quoteColor !== void 0,
20698
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20699
+ tableHeaderBold: !!theme?.tableHeaderBold
20700
+ };
20632
20701
  }
20633
- function normalizedSimilarity(a, b) {
20634
- return similarity(normalize(a), normalize(b));
20702
+ function escapeXml(text) {
20703
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20635
20704
  }
20636
- function normalize(s) {
20637
- return s.replace(/\s+/g, " ").trim();
20705
+ function headingParaPrId(level) {
20706
+ if (level === 1) return PARA_H1;
20707
+ if (level === 2) return PARA_H2;
20708
+ if (level === 3) return PARA_H3;
20709
+ return PARA_H4;
20638
20710
  }
20639
- var MAX_LEVENSHTEIN_LEN = 1e4;
20640
- function levenshtein(a, b) {
20641
- if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20642
- const sampleLen = Math.min(500, a.length, b.length);
20643
- let diffs = 0;
20644
- for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20645
- const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20646
- return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20647
- }
20648
- if (a.length > b.length) [a, b] = [b, a];
20649
- const m = a.length;
20650
- const n = b.length;
20651
- let prev = Array.from({ length: m + 1 }, (_, i) => i);
20652
- let curr = new Array(m + 1);
20653
- for (let j = 1; j <= n; j++) {
20654
- curr[0] = j;
20655
- for (let i = 1; i <= m; i++) {
20656
- if (a[i - 1] === b[j - 1]) {
20657
- curr[i] = prev[i - 1];
20658
- } else {
20659
- curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20660
- }
20661
- }
20662
- ;
20663
- [prev, curr] = [curr, prev];
20664
- }
20665
- return prev[m];
20711
+ function headingCharPrId(level) {
20712
+ if (level === 1) return CHAR_H1;
20713
+ if (level === 2) return CHAR_H2;
20714
+ if (level === 3) return CHAR_H3;
20715
+ return CHAR_H4;
20716
+ }
20717
+ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100) {
20718
+ const boldAttr = bold ? ` bold="1"` : "";
20719
+ const italicAttr = italic ? ` italic="1"` : "";
20720
+ const effFont = bold ? 2 : fontId;
20721
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
20722
+ <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
20723
+ <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
20724
+ <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20725
+ <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
20726
+ <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20727
+ </hh:charPr>`;
20728
+ }
20729
+ function paraPr(id, opts = {}) {
20730
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, keepWord = false } = opts;
20731
+ const breakNonLatin = keepWord ? "KEEP_WORD" : "BREAK_WORD";
20732
+ const snapGrid = keepWord ? "0" : "1";
20733
+ return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="${snapGrid}" suppressLineNumbers="0" checked="0" textDir="AUTO">
20734
+ <hh:align horizontal="${align}" vertical="BASELINE"/>
20735
+ <hh:heading type="NONE" idRef="0" level="0"/>
20736
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
20737
+ <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20738
+ <hh:margin><hc:intent value="${indent}" unit="HWPUNIT"/><hc:left value="${left}" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="${spaceBefore}" unit="HWPUNIT"/><hc:next value="${spaceAfter}" unit="HWPUNIT"/></hh:margin>
20739
+ <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20740
+ <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20741
+ </hh:paraPr>`;
20666
20742
  }
20743
+ var GONGMUN_LIST_BASE = 8;
20744
+ var GONGMUN_LIST_LEVELS = 8;
20745
+ var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
20746
+ var CHAR_VARIANT_BASE = 11;
20747
+ var GONGMUN_BODY_RATIO = 95;
20667
20748
 
20668
- // src/roundtrip/markdown-units.ts
20669
- function splitMarkdownUnits(md2) {
20749
+ // src/hwpx/md-runs.ts
20750
+ function buildPrvText(blocks) {
20751
+ const lines = [];
20752
+ let bytes = 0;
20753
+ for (const b of blocks) {
20754
+ let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
20755
+ if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
20756
+ if (!text) continue;
20757
+ lines.push(text);
20758
+ bytes += text.length * 3;
20759
+ if (bytes > 1024) break;
20760
+ }
20761
+ return lines.join("\n").slice(0, 1024);
20762
+ }
20763
+ function parseMarkdownToBlocks(md2) {
20670
20764
  const lines = md2.split("\n");
20671
- const units = [];
20765
+ const blocks = [];
20672
20766
  let i = 0;
20673
20767
  while (i < lines.length) {
20674
20768
  const line = lines[i];
@@ -20676,442 +20770,451 @@ function splitMarkdownUnits(md2) {
20676
20770
  i++;
20677
20771
  continue;
20678
20772
  }
20679
- if (line.trim().startsWith("<table>")) {
20680
- const collected2 = [];
20773
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20774
+ if (fenceMatch) {
20775
+ const fence = fenceMatch[1];
20776
+ const lang = fenceMatch[2].trim();
20777
+ const codeLines = [];
20778
+ i++;
20779
+ while (i < lines.length && !lines[i].startsWith(fence)) {
20780
+ codeLines.push(lines[i]);
20781
+ i++;
20782
+ }
20783
+ if (i < lines.length) i++;
20784
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20785
+ continue;
20786
+ }
20787
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20788
+ blocks.push({ type: "hr" });
20789
+ i++;
20790
+ continue;
20791
+ }
20792
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20793
+ if (headingMatch) {
20794
+ blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20795
+ i++;
20796
+ continue;
20797
+ }
20798
+ if (/^<table[\s>]/i.test(line.trimStart())) {
20799
+ const htmlLines = [];
20681
20800
  let depth = 0;
20682
20801
  while (i < lines.length) {
20683
20802
  const l = lines[i];
20684
- collected2.push(l);
20685
- depth += (l.match(/<table>/g) || []).length;
20686
- depth -= (l.match(/<\/table>/g) || []).length;
20803
+ htmlLines.push(l);
20804
+ depth += (l.match(/<table[\s>]/gi) ?? []).length;
20805
+ depth -= (l.match(/<\/table>/gi) ?? []).length;
20687
20806
  i++;
20688
20807
  if (depth <= 0) break;
20689
20808
  }
20690
- units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20809
+ blocks.push({ type: "html_table", text: htmlLines.join("\n") });
20691
20810
  continue;
20692
20811
  }
20693
20812
  if (line.trimStart().startsWith("|")) {
20694
- const collected2 = [];
20813
+ const tableRows = [];
20695
20814
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20696
- collected2.push(lines[i]);
20815
+ const row = lines[i];
20816
+ if (/^[\s|:\-]+$/.test(row)) {
20817
+ i++;
20818
+ continue;
20819
+ }
20820
+ const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20821
+ if (cells.length > 0) tableRows.push(cells);
20697
20822
  i++;
20698
20823
  }
20699
- units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20824
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
20700
20825
  continue;
20701
20826
  }
20702
- if (/^-{3,}\s*$/.test(line.trim())) {
20703
- units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20704
- i++;
20827
+ if (line.trimStart().startsWith("> ")) {
20828
+ const quoteLines = [];
20829
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20830
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
20831
+ i++;
20832
+ }
20833
+ for (const ql of quoteLines) {
20834
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
20835
+ }
20705
20836
  continue;
20706
20837
  }
20707
- if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20708
- units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20838
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20839
+ if (listMatch) {
20840
+ const indent = Math.floor(listMatch[1].length / 2);
20841
+ const ordered = /\d/.test(listMatch[2]);
20842
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
20709
20843
  i++;
20710
20844
  continue;
20711
20845
  }
20712
- const collected = [];
20713
- while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20714
- collected.push(lines[i].trim());
20715
- i++;
20716
- }
20717
- units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20846
+ blocks.push({ type: "paragraph", text: line.trim() });
20847
+ i++;
20718
20848
  }
20719
- return units;
20849
+ return blocks;
20720
20850
  }
20721
- function alignUnits(a, b) {
20722
- const m = a.length, n = b.length;
20723
- if (m * n > 4e6) {
20724
- const result2 = [];
20725
- let pre = 0;
20726
- while (pre < m && pre < n && a[pre] === b[pre]) {
20727
- result2.push([pre, pre]);
20728
- pre++;
20729
- }
20730
- let suf = 0;
20731
- while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
20732
- const aMid = m - pre - suf, bMid = n - pre - suf;
20733
- if (aMid === bMid) {
20734
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
20735
- } else {
20736
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
20737
- for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
20851
+ function parseInlineMarkdown(text) {
20852
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
20853
+ text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
20854
+ text = text.replace(/~~([^~]+)~~/g, "$1");
20855
+ const spans = [];
20856
+ const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
20857
+ let lastIdx = 0;
20858
+ for (const match of text.matchAll(regex)) {
20859
+ const idx = match.index;
20860
+ if (idx > lastIdx) {
20861
+ spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
20738
20862
  }
20739
- for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
20740
- return result2;
20741
- }
20742
- const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
20743
- for (let i2 = 1; i2 <= m; i2++) {
20744
- for (let j2 = 1; j2 <= n; j2++) {
20745
- dp[i2][j2] = a[i2 - 1] === b[j2 - 1] ? dp[i2 - 1][j2 - 1] + 1 : Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
20863
+ const raw = match[0];
20864
+ if (raw.startsWith("`")) {
20865
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
20866
+ } else if (raw.startsWith("***") || raw.startsWith("___")) {
20867
+ spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
20868
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
20869
+ spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
20870
+ } else {
20871
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
20746
20872
  }
20873
+ lastIdx = idx + raw.length;
20747
20874
  }
20748
- const matches = [];
20749
- let i = m, j = n;
20750
- while (i > 0 && j > 0) {
20751
- if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
20752
- matches.push([i - 1, j - 1]);
20753
- i--;
20754
- j--;
20755
- } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
20756
- else j--;
20875
+ if (lastIdx < text.length) {
20876
+ spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
20757
20877
  }
20758
- matches.reverse();
20759
- const result = [];
20760
- let ai = 0, bi = 0;
20761
- const flushGap = (aEnd, bEnd) => {
20762
- if (aEnd - ai === bEnd - bi) {
20763
- while (ai < aEnd) result.push([ai++, bi++]);
20764
- return;
20765
- }
20766
- while (ai < aEnd && bi < bEnd) {
20767
- const sim = normalizedSimilarity(a[ai], b[bi]);
20768
- if (sim >= 0.4) {
20769
- if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
20770
- result.push([ai++, null]);
20771
- } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
20772
- result.push([null, bi++]);
20773
- } else {
20774
- result.push([ai++, bi++]);
20775
- }
20776
- } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
20777
- else result.push([null, bi++]);
20778
- }
20779
- while (ai < aEnd) result.push([ai++, null]);
20780
- while (bi < bEnd) result.push([null, bi++]);
20781
- };
20782
- for (const [pi, pj] of matches) {
20783
- flushGap(pi, pj);
20784
- result.push([ai++, bi++]);
20878
+ if (spans.length === 0) {
20879
+ spans.push({ text, bold: false, italic: false, code: false });
20785
20880
  }
20786
- flushGap(m, n);
20787
- return result;
20881
+ return spans;
20788
20882
  }
20789
- function bestSimInRange(arr, from, to, target) {
20790
- let best = 0;
20791
- for (let k = from; k <= to && k < arr.length; k++) {
20792
- const s = normalizedSimilarity(arr[k], target);
20793
- if (s > best) best = s;
20794
- }
20795
- return best;
20883
+ function spanToCharPrId(span) {
20884
+ if (span.code) return CHAR_CODE;
20885
+ if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
20886
+ if (span.bold) return CHAR_BOLD;
20887
+ if (span.italic) return CHAR_ITALIC;
20888
+ return CHAR_NORMAL;
20796
20889
  }
20797
- function escapeGfm(text) {
20798
- return text.replace(/~/g, "\\~");
20890
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
20891
+ const spans = parseInlineMarkdown(text);
20892
+ return spans.map((span) => {
20893
+ let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
20894
+ if (mapCharId) charId = mapCharId(charId);
20895
+ return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
20896
+ }).join("");
20799
20897
  }
20800
- var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
20801
- function sanitizeText(text) {
20802
- let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
20803
- if (result.length <= 30 && result.includes(" ")) {
20804
- const tokens = result.split(" ");
20805
- const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
20806
- if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
20807
- result = tokens.join("");
20808
- }
20898
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
20899
+ if (paraPrId === PARA_CODE) {
20900
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
20809
20901
  }
20810
- return result;
20811
- }
20812
- function normForMatch(text) {
20813
- return sanitizeText(text).replace(/\s+/g, " ").trim();
20902
+ const runs = generateRuns(text, charPrId, mapCharId);
20903
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
20814
20904
  }
20815
- function unescapeGfm(text) {
20816
- return text.replace(/\\~/g, "~");
20905
+
20906
+ // src/hwpx/gen-header.ts
20907
+ function generateContainerXml() {
20908
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20909
+ <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
20910
+ <ocf:rootfiles>
20911
+ <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
20912
+ </ocf:rootfiles>
20913
+ </ocf:container>`;
20817
20914
  }
20818
- function summarize(text) {
20819
- const t = text.replace(/\s+/g, " ").trim();
20820
- return t.length > 80 ? t.slice(0, 77) + "..." : t;
20915
+ function generateManifest() {
20916
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20917
+ <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
20918
+ <opf:manifest>
20919
+ <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
20920
+ <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
20921
+ </opf:manifest>
20922
+ <opf:spine>
20923
+ <opf:itemref idref="header" linear="no"/>
20924
+ <opf:itemref idref="section0" linear="yes"/>
20925
+ </opf:spine>
20926
+ </opf:package>`;
20821
20927
  }
20822
- function replicateGfmTable(table) {
20823
- const { cells, rows: numRows, cols: numCols } = table;
20824
- if (numRows === 0 || numCols === 0) return null;
20825
- if (numRows === 1 && numCols === 1) return null;
20826
- if (numCols === 1) return null;
20827
- const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
20828
- const skip = /* @__PURE__ */ new Set();
20829
- for (let r = 0; r < numRows; r++) {
20830
- for (let c = 0; c < numCols; c++) {
20831
- if (skip.has(`${r},${c}`)) continue;
20832
- const cell = cells[r]?.[c];
20833
- if (!cell) continue;
20834
- display[r][c] = {
20835
- text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
20836
- gridR: r,
20837
- gridC: c
20838
- };
20839
- for (let dr = 0; dr < cell.rowSpan; dr++) {
20840
- for (let dc = 0; dc < cell.colSpan; dc++) {
20841
- if (dr === 0 && dc === 0) continue;
20842
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20843
- }
20844
- }
20845
- c += cell.colSpan - 1;
20846
- }
20928
+ function buildCharProperties(theme, gongmun, ratioVariants = []) {
20929
+ let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
20930
+ if (gongmun) {
20931
+ body = gongmun.bodyHeight;
20932
+ code = Math.max(body - 200, 900);
20933
+ h1 = gongmun.preset === "report" || gongmun.preset === "plan" ? 2e3 : 1700;
20934
+ h2 = 1600;
20935
+ h3 = body;
20936
+ h4 = Math.max(body - 100, 1300);
20847
20937
  }
20848
- const uniqueRows = [];
20849
- let pendingLabelRow = null;
20850
- for (let r = 0; r < display.length; r++) {
20851
- const row = display[r];
20852
- if (row.every((cell) => cell.text === "")) continue;
20853
- const nonEmptyCols = row.filter((cell) => cell.text !== "");
20854
- const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
20855
- if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
20856
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20857
- pendingLabelRow = row;
20858
- continue;
20859
- }
20860
- if (pendingLabelRow) {
20861
- if (row[0].text === "") row[0] = pendingLabelRow[0];
20862
- else uniqueRows.push(pendingLabelRow);
20863
- pendingLabelRow = null;
20864
- }
20865
- uniqueRows.push(row);
20938
+ const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
20939
+ const rows = [
20940
+ charPr(0, body, false, false, 0, theme.body, bodyRatio),
20941
+ charPr(1, body, true, false, 0, theme.body, bodyRatio),
20942
+ charPr(2, body, false, true, 0, theme.body, bodyRatio),
20943
+ charPr(3, body, true, true, 0, theme.body, bodyRatio),
20944
+ charPr(4, code, false, false, 1),
20945
+ charPr(5, h1, true, false, 1, theme.h1),
20946
+ charPr(6, h2, true, false, 1, theme.h2),
20947
+ charPr(7, h3, true, false, 1, theme.h3),
20948
+ charPr(8, h4, true, false, 1, theme.h4),
20949
+ charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
20950
+ charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
20951
+ ];
20952
+ for (const r of ratioVariants) {
20953
+ rows.push(
20954
+ charPr(rows.length, body, false, false, 0, theme.body, r),
20955
+ charPr(rows.length + 1, body, true, false, 0, theme.body, r),
20956
+ charPr(rows.length + 2, body, false, true, 0, theme.body, r),
20957
+ charPr(rows.length + 3, body, true, true, 0, theme.body, r)
20958
+ );
20866
20959
  }
20867
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20868
- return uniqueRows.length > 0 ? uniqueRows : null;
20960
+ return `<hh:charProperties itemCnt="${rows.length}">
20961
+ ${rows.join("\n")}
20962
+ </hh:charProperties>`;
20869
20963
  }
20870
- function parseGfmTable(lines) {
20871
- const rows = [];
20872
- for (const line of lines) {
20873
- const trimmed = line.trim();
20874
- if (!trimmed.startsWith("|")) continue;
20875
- const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
20876
- if (cells.length === 0) continue;
20877
- if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
20878
- rows.push(cells);
20964
+ function buildParaProperties(gongmun) {
20965
+ if (!gongmun) {
20966
+ const base2 = [
20967
+ paraPr(0),
20968
+ paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 }),
20969
+ paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 }),
20970
+ paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 }),
20971
+ paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 }),
20972
+ paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 }),
20973
+ paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 }),
20974
+ paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })
20975
+ ];
20976
+ return `<hh:paraProperties itemCnt="${base2.length}">
20977
+ ${base2.join("\n")}
20978
+ </hh:paraProperties>`;
20879
20979
  }
20880
- return rows;
20881
- }
20882
- function unescapeGfmCell(text) {
20883
- return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
20884
- }
20885
- function replicateCellInnerHtml(cell) {
20886
- if (cell.blocks?.length) {
20887
- return cell.blocks.map((b) => {
20888
- if (b.type === "table" && b.table) {
20889
- const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
20890
- return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
20891
- }
20892
- if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
20893
- const t = sanitizeText(b.text ?? "");
20894
- return t ? t.replace(/\n/g, "<br>") : "";
20895
- }).filter(Boolean).join("<br>");
20980
+ const ls = gongmun.lineSpacing;
20981
+ const titleAlign = gongmun.centerTitle ? "CENTER" : "LEFT";
20982
+ const base = [
20983
+ paraPr(0, { lineSpacing: ls, keepWord: true }),
20984
+ paraPr(1, { align: titleAlign, spaceBefore: 400, spaceAfter: 400, lineSpacing: ls, keepWord: true }),
20985
+ paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: ls, keepWord: true }),
20986
+ paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
20987
+ paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
20988
+ paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400, keepWord: true }),
20989
+ paraPr(6, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true }),
20990
+ paraPr(7, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true })
20991
+ ];
20992
+ for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
20993
+ const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering);
20994
+ const sectionGap = gongmun.numbering === "report" && d === 0 ? Math.round(gongmun.bodyHeight * 0.5) : 0;
20995
+ base.push(paraPr(GONGMUN_LIST_BASE + d, { align: "JUSTIFY", lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true }));
20896
20996
  }
20897
- return sanitizeText(cell.text).replace(/\n/g, "<br>");
20997
+ base.push(paraPr(GONGMUN_CENTER, { align: "CENTER", lineSpacing: ls, keepWord: true }));
20998
+ return `<hh:paraProperties itemCnt="${base.length}">
20999
+ ${base.join("\n")}
21000
+ </hh:paraProperties>`;
20898
21001
  }
20899
- function replicateTableToHtml(table) {
20900
- const rows = replicateHtmlTable(table);
20901
- const lines = ["<table>"];
20902
- for (let r = 0; r < rows.length; r++) {
20903
- const tag = rows[r].tag;
20904
- const rowHtml = rows[r].cells.map((cell) => {
20905
- const attrs = [];
20906
- if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
20907
- if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
20908
- const attrStr = attrs.length ? " " + attrs.join(" ") : "";
20909
- return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
20910
- });
20911
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
20912
- }
20913
- lines.push("</table>");
20914
- return lines.join("\n");
21002
+ function generateHeaderXml(theme, gongmun, ratioVariants = []) {
21003
+ const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
21004
+ const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
21005
+ const paraPropsXml = buildParaProperties(gongmun);
21006
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21007
+ <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
21008
+ <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
21009
+ <hh:refList>
21010
+ <hh:fontfaces itemCnt="7">
21011
+ <hh:fontface lang="HANGUL" fontCnt="3">
21012
+ <hh:font id="0" face="${bodyFace}" type="TTF" isEmbedded="0">
21013
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21014
+ </hh:font>
21015
+ <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
21016
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21017
+ </hh:font>
21018
+ <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
21019
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21020
+ </hh:font>
21021
+ </hh:fontface>
21022
+ <hh:fontface lang="LATIN" fontCnt="3">
21023
+ <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
21024
+ <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
21025
+ </hh:font>
21026
+ <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
21027
+ <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
21028
+ </hh:font>
21029
+ <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
21030
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
21031
+ </hh:font>
21032
+ </hh:fontface>
21033
+ <hh:fontface lang="HANJA" fontCnt="1">
21034
+ <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
21035
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21036
+ </hh:font>
21037
+ </hh:fontface>
21038
+ <hh:fontface lang="JAPANESE" fontCnt="1">
21039
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21040
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21041
+ </hh:font>
21042
+ </hh:fontface>
21043
+ <hh:fontface lang="OTHER" fontCnt="1">
21044
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21045
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21046
+ </hh:font>
21047
+ </hh:fontface>
21048
+ <hh:fontface lang="SYMBOL" fontCnt="1">
21049
+ <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
21050
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21051
+ </hh:font>
21052
+ </hh:fontface>
21053
+ <hh:fontface lang="USER" fontCnt="1">
21054
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21055
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21056
+ </hh:font>
21057
+ </hh:fontface>
21058
+ </hh:fontfaces>
21059
+ <hh:borderFills itemCnt="2">
21060
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
21061
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
21062
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
21063
+ <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
21064
+ <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
21065
+ <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
21066
+ <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
21067
+ </hh:borderFill>
21068
+ <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
21069
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
21070
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
21071
+ <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
21072
+ <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
21073
+ <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
21074
+ <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
21075
+ </hh:borderFill>
21076
+ </hh:borderFills>
21077
+ ${charPropsXml}
21078
+ <hh:tabProperties itemCnt="0"/>
21079
+ <hh:numberings itemCnt="0"/>
21080
+ <hh:bullets itemCnt="0"/>
21081
+ ${paraPropsXml}
21082
+ <hh:styles itemCnt="1">
21083
+ <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
21084
+ </hh:styles>
21085
+ </hh:refList>
21086
+ <hh:compatibleDocument targetProgram="HWP2018"><hh:layoutCompatibility/></hh:compatibleDocument>
21087
+ </hh:head>`;
20915
21088
  }
20916
- function replicateHtmlTable(table) {
20917
- const { cells, rows: numRows, cols: numCols } = table;
20918
- const skip = /* @__PURE__ */ new Set();
20919
- const result = [];
20920
- for (let r = 0; r < numRows; r++) {
20921
- const tag = r === 0 ? "th" : "td";
20922
- const rowCells = [];
20923
- for (let c = 0; c < numCols; c++) {
20924
- if (skip.has(`${r},${c}`)) continue;
20925
- const cell = cells[r]?.[c];
20926
- if (!cell) continue;
20927
- for (let dr = 0; dr < cell.rowSpan; dr++) {
20928
- for (let dc = 0; dc < cell.colSpan; dc++) {
20929
- if (dr === 0 && dc === 0) continue;
20930
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20931
- }
20932
- }
20933
- rowCells.push({
20934
- inner: replicateCellInnerHtml(cell),
20935
- colSpan: cell.colSpan,
20936
- rowSpan: cell.rowSpan,
20937
- gridR: r,
20938
- gridC: c
20939
- });
21089
+
21090
+ // src/hwpx/gen-gongmun-fit.ts
21091
+ function plainRenderText(text) {
21092
+ return parseInlineMarkdown(text).map((s) => s.text).join("");
21093
+ }
21094
+ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
21095
+ const minRatio = gongmun.autoFitMinRatio;
21096
+ if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
21097
+ const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
21098
+ const ratioByBlock = /* @__PURE__ */ new Map();
21099
+ const variants = [];
21100
+ for (let i = 0; i < blocks.length; i++) {
21101
+ const block = blocks[i];
21102
+ let text;
21103
+ let firstW;
21104
+ let contW;
21105
+ if (block.type === "list_item" && gongmunList.has(i)) {
21106
+ const { marker, depth } = gongmunList.get(i);
21107
+ const content = plainRenderText(block.text || "");
21108
+ text = marker ? `${marker} ${content}` : content;
21109
+ const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
21110
+ firstW = pageW - left - Math.max(indent, 0);
21111
+ contW = pageW - left - Math.max(-indent, 0);
21112
+ } else if (block.type === "paragraph") {
21113
+ const raw = (block.text || "").trim();
21114
+ if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
21115
+ text = plainRenderText(raw);
21116
+ firstW = contW = pageW;
21117
+ } else {
21118
+ continue;
20940
21119
  }
20941
- if (rowCells.length) result.push({ tag, cells: rowCells });
21120
+ if (!text) continue;
21121
+ const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
21122
+ if (r === null) continue;
21123
+ ratioByBlock.set(i, r);
21124
+ if (!variants.includes(r)) variants.push(r);
20942
21125
  }
20943
- return result;
21126
+ return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
20944
21127
  }
20945
- function parseHtmlTable(raw) {
20946
- const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
20947
- let depth = 0;
20948
- let currentRow = null;
20949
- let cellStart = -1;
20950
- let cellInfo = null;
20951
- const rows = [];
20952
- let m;
20953
- while ((m = re.exec(raw)) !== null) {
20954
- const isClose = m[1] === "/";
20955
- const tag = m[2].toLowerCase();
20956
- const attrs = m[3] || "";
20957
- if (tag === "table") {
20958
- depth += isClose ? -1 : 1;
20959
- if (depth < 0) return null;
21128
+ function variantMapper(fit, blockIdx) {
21129
+ const r = fit.ratioByBlock.get(blockIdx);
21130
+ if (r === void 0) return void 0;
21131
+ const vi = fit.variants.indexOf(r);
21132
+ return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
21133
+ }
21134
+ function precomputeGongmunList(blocks, gongmun) {
21135
+ const result = /* @__PURE__ */ new Map();
21136
+ let i = 0;
21137
+ while (i < blocks.length) {
21138
+ if (blocks[i].type !== "list_item") {
21139
+ i++;
20960
21140
  continue;
20961
21141
  }
20962
- if (depth !== 1) continue;
20963
- if (tag === "tr") {
20964
- if (!isClose) currentRow = [];
20965
- else if (currentRow) {
20966
- rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
20967
- currentRow = null;
21142
+ const run = [];
21143
+ while (i < blocks.length) {
21144
+ const t = blocks[i].type;
21145
+ if (t === "list_item") {
21146
+ run.push(i);
21147
+ i++;
21148
+ continue;
20968
21149
  }
20969
- } else {
20970
- if (!isClose) {
20971
- const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20972
- const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20973
- cellStart = m.index + m[0].length;
20974
- cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
20975
- } else if (cellStart >= 0 && cellInfo && currentRow) {
20976
- currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
20977
- cellStart = -1;
20978
- cellInfo = null;
21150
+ if (t === "table" || t === "html_table") {
21151
+ let j = i + 1;
21152
+ while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
21153
+ if (j < blocks.length && blocks[j].type === "list_item") {
21154
+ i = j;
21155
+ continue;
21156
+ }
20979
21157
  }
21158
+ break;
20980
21159
  }
21160
+ const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
21161
+ const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
21162
+ const numberer = new GongmunNumberer(gongmun.numbering);
21163
+ run.forEach((bi, k) => {
21164
+ const marker = numberer.next(depths[k], suppress[k]);
21165
+ result.set(bi, { marker, depth: depths[k] });
21166
+ });
20981
21167
  }
20982
- if (depth !== 0) return null;
20983
- return rows;
21168
+ return result;
20984
21169
  }
20985
- var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
20986
- function htmlCellInnerToLines(inner) {
20987
- let hadNonText = false;
20988
- let work = inner;
20989
- if (/<table[\s>]/i.test(work)) {
20990
- hadNonText = true;
20991
- work = removeNestedTables(work);
20992
- }
20993
- if (/<img\s/i.test(work)) {
20994
- hadNonText = true;
20995
- work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
20996
- }
20997
- const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
20998
- return { lines, hadNonText };
21170
+
21171
+ // src/diff/text-diff.ts
21172
+ function similarity(a, b) {
21173
+ if (a === b) return 1;
21174
+ if (!a || !b) return 0;
21175
+ const maxLen = Math.max(a.length, b.length);
21176
+ if (maxLen === 0) return 1;
21177
+ return 1 - levenshtein(a, b) / maxLen;
20999
21178
  }
21000
- function extractTopLevelTables(html) {
21001
- const result = [];
21002
- let depth = 0;
21003
- let start = -1;
21004
- const re = /<(\/?)table(?:[\s>]|>)/gi;
21005
- let m;
21006
- while ((m = re.exec(html)) !== null) {
21007
- if (m[1] !== "/") {
21008
- if (depth === 0) start = m.index;
21009
- depth++;
21010
- } else {
21011
- depth--;
21012
- if (depth === 0 && start >= 0) {
21013
- result.push(html.slice(start, m.index + m[0].length));
21014
- start = -1;
21015
- }
21016
- if (depth < 0) depth = 0;
21017
- }
21018
- }
21019
- return result;
21179
+ function normalizedSimilarity(a, b) {
21180
+ return similarity(normalize(a), normalize(b));
21020
21181
  }
21021
- function removeNestedTables(html) {
21022
- let result = "";
21023
- let depth = 0;
21024
- const re = /<(\/?)table(?:[\s>]|>)/gi;
21025
- let last = 0;
21026
- let m;
21027
- while ((m = re.exec(html)) !== null) {
21028
- if (m[1] !== "/") {
21029
- if (depth === 0) result += html.slice(last, m.index);
21030
- depth++;
21031
- } else {
21032
- depth--;
21033
- if (depth === 0) last = m.index + m[0].length;
21034
- if (depth < 0) depth = 0;
21182
+ function normalize(s) {
21183
+ return s.replace(/\s+/g, " ").trim();
21184
+ }
21185
+ var MAX_LEVENSHTEIN_LEN = 1e4;
21186
+ function levenshtein(a, b) {
21187
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
21188
+ const sampleLen = Math.min(500, a.length, b.length);
21189
+ let diffs = 0;
21190
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
21191
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
21192
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
21193
+ }
21194
+ if (a.length > b.length) [a, b] = [b, a];
21195
+ const m = a.length;
21196
+ const n = b.length;
21197
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
21198
+ let curr = new Array(m + 1);
21199
+ for (let j = 1; j <= n; j++) {
21200
+ curr[0] = j;
21201
+ for (let i = 1; i <= m; i++) {
21202
+ if (a[i - 1] === b[j - 1]) {
21203
+ curr[i] = prev[i - 1];
21204
+ } else {
21205
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
21206
+ }
21035
21207
  }
21208
+ ;
21209
+ [prev, curr] = [curr, prev];
21036
21210
  }
21037
- if (depth === 0) result += html.slice(last);
21038
- return result;
21211
+ return prev[m];
21039
21212
  }
21040
21213
 
21041
- // src/hwpx/generator.ts
21042
- var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
21043
- var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
21044
- var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
21045
- var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
21046
- var NS_OPF = "http://www.idpf.org/2007/opf/";
21047
- var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
21048
- var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
21049
- var CHAR_NORMAL = 0;
21050
- var CHAR_BOLD = 1;
21051
- var CHAR_ITALIC = 2;
21052
- var CHAR_BOLD_ITALIC = 3;
21053
- var CHAR_CODE = 4;
21054
- var CHAR_H1 = 5;
21055
- var CHAR_H2 = 6;
21056
- var CHAR_H3 = 7;
21057
- var CHAR_H4 = 8;
21058
- var CHAR_TABLE_HEADER = 9;
21059
- var CHAR_QUOTE = 10;
21060
- var PARA_NORMAL = 0;
21061
- var PARA_H1 = 1;
21062
- var PARA_H2 = 2;
21063
- var PARA_H3 = 3;
21064
- var PARA_H4 = 4;
21065
- var PARA_CODE = 5;
21066
- var PARA_QUOTE = 6;
21067
- var PARA_LIST = 7;
21068
- var DEFAULT_TEXT_COLOR = "#000000";
21069
- function resolveTheme(theme) {
21070
- return {
21071
- h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
21072
- h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
21073
- h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
21074
- h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
21075
- body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
21076
- quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
21077
- /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
21078
- hasQuoteOption: theme?.quoteColor !== void 0,
21079
- tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
21080
- tableHeaderBold: !!theme?.tableHeaderBold
21081
- };
21082
- }
21083
- async function markdownToHwpx(markdown, options) {
21084
- const theme = resolveTheme(options?.theme);
21085
- const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
21086
- const blocks = parseMarkdownToBlocks(markdown);
21087
- const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
21088
- const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
21089
- const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
21090
- const zip = new JSZip7();
21091
- zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
21092
- zip.file("META-INF/container.xml", generateContainerXml());
21093
- zip.file("Contents/content.hpf", generateManifest());
21094
- zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
21095
- zip.file("Contents/section0.xml", sectionXml);
21096
- zip.file("Preview/PrvText.txt", buildPrvText(blocks));
21097
- return await zip.generateAsync({ type: "arraybuffer" });
21098
- }
21099
- function buildPrvText(blocks) {
21100
- const lines = [];
21101
- let bytes = 0;
21102
- for (const b of blocks) {
21103
- let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
21104
- if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
21105
- if (!text) continue;
21106
- lines.push(text);
21107
- bytes += text.length * 3;
21108
- if (bytes > 1024) break;
21109
- }
21110
- return lines.join("\n").slice(0, 1024);
21111
- }
21112
- function parseMarkdownToBlocks(md2) {
21214
+ // src/roundtrip/markdown-units.ts
21215
+ function splitMarkdownUnits(md2) {
21113
21216
  const lines = md2.split("\n");
21114
- const blocks = [];
21217
+ const units = [];
21115
21218
  let i = 0;
21116
21219
  while (i < lines.length) {
21117
21220
  const line = lines[i];
@@ -21119,420 +21222,369 @@ function parseMarkdownToBlocks(md2) {
21119
21222
  i++;
21120
21223
  continue;
21121
21224
  }
21122
- const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
21123
- if (fenceMatch) {
21124
- const fence = fenceMatch[1];
21125
- const lang = fenceMatch[2].trim();
21126
- const codeLines = [];
21127
- i++;
21128
- while (i < lines.length && !lines[i].startsWith(fence)) {
21129
- codeLines.push(lines[i]);
21130
- i++;
21131
- }
21132
- if (i < lines.length) i++;
21133
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
21134
- continue;
21135
- }
21136
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
21137
- blocks.push({ type: "hr" });
21138
- i++;
21139
- continue;
21140
- }
21141
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
21142
- if (headingMatch) {
21143
- blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
21144
- i++;
21145
- continue;
21146
- }
21147
- if (/^<table[\s>]/i.test(line.trimStart())) {
21148
- const htmlLines = [];
21225
+ if (line.trim().startsWith("<table>")) {
21226
+ const collected2 = [];
21149
21227
  let depth = 0;
21150
21228
  while (i < lines.length) {
21151
21229
  const l = lines[i];
21152
- htmlLines.push(l);
21153
- depth += (l.match(/<table[\s>]/gi) ?? []).length;
21154
- depth -= (l.match(/<\/table>/gi) ?? []).length;
21230
+ collected2.push(l);
21231
+ depth += (l.match(/<table>/g) || []).length;
21232
+ depth -= (l.match(/<\/table>/g) || []).length;
21155
21233
  i++;
21156
21234
  if (depth <= 0) break;
21157
21235
  }
21158
- blocks.push({ type: "html_table", text: htmlLines.join("\n") });
21236
+ units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
21159
21237
  continue;
21160
21238
  }
21161
21239
  if (line.trimStart().startsWith("|")) {
21162
- const tableRows = [];
21240
+ const collected2 = [];
21163
21241
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
21164
- const row = lines[i];
21165
- if (/^[\s|:\-]+$/.test(row)) {
21166
- i++;
21167
- continue;
21168
- }
21169
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
21170
- if (cells.length > 0) tableRows.push(cells);
21242
+ collected2.push(lines[i]);
21171
21243
  i++;
21172
21244
  }
21173
- if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
21245
+ units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
21174
21246
  continue;
21175
21247
  }
21176
- if (line.trimStart().startsWith("> ")) {
21177
- const quoteLines = [];
21178
- while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
21179
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
21180
- i++;
21181
- }
21182
- for (const ql of quoteLines) {
21183
- blocks.push({ type: "blockquote", text: ql.trim() || "" });
21184
- }
21248
+ if (/^-{3,}\s*$/.test(line.trim())) {
21249
+ units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
21250
+ i++;
21185
21251
  continue;
21186
21252
  }
21187
- const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
21188
- if (listMatch) {
21189
- const indent = Math.floor(listMatch[1].length / 2);
21190
- const ordered = /\d/.test(listMatch[2]);
21191
- blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
21253
+ if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
21254
+ units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
21192
21255
  i++;
21193
21256
  continue;
21194
21257
  }
21195
- blocks.push({ type: "paragraph", text: line.trim() });
21196
- i++;
21258
+ const collected = [];
21259
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
21260
+ collected.push(lines[i].trim());
21261
+ i++;
21262
+ }
21263
+ units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
21197
21264
  }
21198
- return blocks;
21265
+ return units;
21199
21266
  }
21200
- function parseInlineMarkdown(text) {
21201
- text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
21202
- text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
21203
- text = text.replace(/~~([^~]+)~~/g, "$1");
21204
- const spans = [];
21205
- const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
21206
- let lastIdx = 0;
21207
- for (const match of text.matchAll(regex)) {
21208
- const idx = match.index;
21209
- if (idx > lastIdx) {
21210
- spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
21211
- }
21212
- const raw = match[0];
21213
- if (raw.startsWith("`")) {
21214
- spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
21215
- } else if (raw.startsWith("***") || raw.startsWith("___")) {
21216
- spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
21217
- } else if (raw.startsWith("**") || raw.startsWith("__")) {
21218
- spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
21267
+ function alignUnits(a, b) {
21268
+ const m = a.length, n = b.length;
21269
+ if (m * n > 4e6) {
21270
+ const result2 = [];
21271
+ let pre = 0;
21272
+ while (pre < m && pre < n && a[pre] === b[pre]) {
21273
+ result2.push([pre, pre]);
21274
+ pre++;
21275
+ }
21276
+ let suf = 0;
21277
+ while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
21278
+ const aMid = m - pre - suf, bMid = n - pre - suf;
21279
+ if (aMid === bMid) {
21280
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
21219
21281
  } else {
21220
- spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
21282
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
21283
+ for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
21221
21284
  }
21222
- lastIdx = idx + raw.length;
21285
+ for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
21286
+ return result2;
21223
21287
  }
21224
- if (lastIdx < text.length) {
21225
- spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
21288
+ const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
21289
+ for (let i2 = 1; i2 <= m; i2++) {
21290
+ for (let j2 = 1; j2 <= n; j2++) {
21291
+ dp[i2][j2] = a[i2 - 1] === b[j2 - 1] ? dp[i2 - 1][j2 - 1] + 1 : Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
21292
+ }
21226
21293
  }
21227
- if (spans.length === 0) {
21228
- spans.push({ text, bold: false, italic: false, code: false });
21294
+ const matches = [];
21295
+ let i = m, j = n;
21296
+ while (i > 0 && j > 0) {
21297
+ if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
21298
+ matches.push([i - 1, j - 1]);
21299
+ i--;
21300
+ j--;
21301
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
21302
+ else j--;
21229
21303
  }
21230
- return spans;
21231
- }
21232
- function spanToCharPrId(span) {
21233
- if (span.code) return CHAR_CODE;
21234
- if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
21235
- if (span.bold) return CHAR_BOLD;
21236
- if (span.italic) return CHAR_ITALIC;
21237
- return CHAR_NORMAL;
21304
+ matches.reverse();
21305
+ const result = [];
21306
+ let ai = 0, bi = 0;
21307
+ const flushGap = (aEnd, bEnd) => {
21308
+ if (aEnd - ai === bEnd - bi) {
21309
+ while (ai < aEnd) result.push([ai++, bi++]);
21310
+ return;
21311
+ }
21312
+ while (ai < aEnd && bi < bEnd) {
21313
+ const sim = normalizedSimilarity(a[ai], b[bi]);
21314
+ if (sim >= 0.4) {
21315
+ if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
21316
+ result.push([ai++, null]);
21317
+ } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
21318
+ result.push([null, bi++]);
21319
+ } else {
21320
+ result.push([ai++, bi++]);
21321
+ }
21322
+ } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
21323
+ else result.push([null, bi++]);
21324
+ }
21325
+ while (ai < aEnd) result.push([ai++, null]);
21326
+ while (bi < bEnd) result.push([null, bi++]);
21327
+ };
21328
+ for (const [pi, pj] of matches) {
21329
+ flushGap(pi, pj);
21330
+ result.push([ai++, bi++]);
21331
+ }
21332
+ flushGap(m, n);
21333
+ return result;
21238
21334
  }
21239
- function escapeXml(text) {
21240
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
21335
+ function bestSimInRange(arr, from, to, target) {
21336
+ let best = 0;
21337
+ for (let k = from; k <= to && k < arr.length; k++) {
21338
+ const s = normalizedSimilarity(arr[k], target);
21339
+ if (s > best) best = s;
21340
+ }
21341
+ return best;
21241
21342
  }
21242
- function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
21243
- const spans = parseInlineMarkdown(text);
21244
- return spans.map((span) => {
21245
- let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
21246
- if (mapCharId) charId = mapCharId(charId);
21247
- return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
21248
- }).join("");
21343
+ function escapeGfm(text) {
21344
+ return text.replace(/~/g, "\\~");
21249
21345
  }
21250
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
21251
- if (paraPrId === PARA_CODE) {
21252
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
21346
+ var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
21347
+ function sanitizeText(text) {
21348
+ let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
21349
+ if (result.length <= 30 && result.includes(" ")) {
21350
+ const tokens = result.split(" ");
21351
+ const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
21352
+ if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
21353
+ result = tokens.join("");
21354
+ }
21253
21355
  }
21254
- const runs = generateRuns(text, charPrId, mapCharId);
21255
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
21256
- }
21257
- function headingParaPrId(level) {
21258
- if (level === 1) return PARA_H1;
21259
- if (level === 2) return PARA_H2;
21260
- if (level === 3) return PARA_H3;
21261
- return PARA_H4;
21356
+ return result;
21262
21357
  }
21263
- function headingCharPrId(level) {
21264
- if (level === 1) return CHAR_H1;
21265
- if (level === 2) return CHAR_H2;
21266
- if (level === 3) return CHAR_H3;
21267
- return CHAR_H4;
21358
+ function normForMatch(text) {
21359
+ return sanitizeText(text).replace(/\s+/g, " ").trim();
21268
21360
  }
21269
- function generateContainerXml() {
21270
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21271
- <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
21272
- <ocf:rootfiles>
21273
- <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
21274
- </ocf:rootfiles>
21275
- </ocf:container>`;
21361
+ function unescapeGfm(text) {
21362
+ return text.replace(/\\~/g, "~");
21276
21363
  }
21277
- function generateManifest() {
21278
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21279
- <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
21280
- <opf:manifest>
21281
- <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
21282
- <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
21283
- </opf:manifest>
21284
- <opf:spine>
21285
- <opf:itemref idref="header" linear="no"/>
21286
- <opf:itemref idref="section0" linear="yes"/>
21287
- </opf:spine>
21288
- </opf:package>`;
21364
+ function summarize(text) {
21365
+ const t = text.replace(/\s+/g, " ").trim();
21366
+ return t.length > 80 ? t.slice(0, 77) + "..." : t;
21289
21367
  }
21290
- function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100) {
21291
- const boldAttr = bold ? ` bold="1"` : "";
21292
- const italicAttr = italic ? ` italic="1"` : "";
21293
- const effFont = bold ? 2 : fontId;
21294
- return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
21295
- <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
21296
- <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
21297
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
21298
- <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
21299
- <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
21300
- </hh:charPr>`;
21368
+ function replicateGfmTable(table) {
21369
+ const { cells, rows: numRows, cols: numCols } = table;
21370
+ if (numRows === 0 || numCols === 0) return null;
21371
+ if (numRows === 1 && numCols === 1) return null;
21372
+ if (numCols === 1) return null;
21373
+ const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
21374
+ const skip = /* @__PURE__ */ new Set();
21375
+ for (let r = 0; r < numRows; r++) {
21376
+ for (let c = 0; c < numCols; c++) {
21377
+ if (skip.has(`${r},${c}`)) continue;
21378
+ const cell = cells[r]?.[c];
21379
+ if (!cell) continue;
21380
+ display[r][c] = {
21381
+ text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
21382
+ gridR: r,
21383
+ gridC: c
21384
+ };
21385
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
21386
+ for (let dc = 0; dc < cell.colSpan; dc++) {
21387
+ if (dr === 0 && dc === 0) continue;
21388
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
21389
+ }
21390
+ }
21391
+ c += cell.colSpan - 1;
21392
+ }
21393
+ }
21394
+ const uniqueRows = [];
21395
+ let pendingLabelRow = null;
21396
+ for (let r = 0; r < display.length; r++) {
21397
+ const row = display[r];
21398
+ if (row.every((cell) => cell.text === "")) continue;
21399
+ const nonEmptyCols = row.filter((cell) => cell.text !== "");
21400
+ const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
21401
+ if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
21402
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
21403
+ pendingLabelRow = row;
21404
+ continue;
21405
+ }
21406
+ if (pendingLabelRow) {
21407
+ if (row[0].text === "") row[0] = pendingLabelRow[0];
21408
+ else uniqueRows.push(pendingLabelRow);
21409
+ pendingLabelRow = null;
21410
+ }
21411
+ uniqueRows.push(row);
21412
+ }
21413
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
21414
+ return uniqueRows.length > 0 ? uniqueRows : null;
21301
21415
  }
21302
- function paraPr(id, opts = {}) {
21303
- const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, keepWord = false } = opts;
21304
- const breakNonLatin = keepWord ? "KEEP_WORD" : "BREAK_WORD";
21305
- const snapGrid = keepWord ? "0" : "1";
21306
- return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="${snapGrid}" suppressLineNumbers="0" checked="0" textDir="AUTO">
21307
- <hh:align horizontal="${align}" vertical="BASELINE"/>
21308
- <hh:heading type="NONE" idRef="0" level="0"/>
21309
- <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
21310
- <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
21311
- <hh:margin><hc:intent value="${indent}" unit="HWPUNIT"/><hc:left value="${left}" unit="HWPUNIT"/><hc:right value="0" unit="HWPUNIT"/><hc:prev value="${spaceBefore}" unit="HWPUNIT"/><hc:next value="${spaceAfter}" unit="HWPUNIT"/></hh:margin>
21312
- <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
21313
- <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
21314
- </hh:paraPr>`;
21416
+ function parseGfmTable(lines) {
21417
+ const rows = [];
21418
+ for (const line of lines) {
21419
+ const trimmed = line.trim();
21420
+ if (!trimmed.startsWith("|")) continue;
21421
+ const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
21422
+ if (cells.length === 0) continue;
21423
+ if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
21424
+ rows.push(cells);
21425
+ }
21426
+ return rows;
21315
21427
  }
21316
- var GONGMUN_LIST_BASE = 8;
21317
- var GONGMUN_LIST_LEVELS = 8;
21318
- var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
21319
- var CHAR_VARIANT_BASE = 11;
21320
- var GONGMUN_BODY_RATIO = 95;
21321
- function plainRenderText(text) {
21322
- return parseInlineMarkdown(text).map((s) => s.text).join("");
21428
+ function unescapeGfmCell(text) {
21429
+ return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
21323
21430
  }
21324
- function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
21325
- const minRatio = gongmun.autoFitMinRatio;
21326
- if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
21327
- const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
21328
- const ratioByBlock = /* @__PURE__ */ new Map();
21329
- const variants = [];
21330
- for (let i = 0; i < blocks.length; i++) {
21331
- const block = blocks[i];
21332
- let text;
21333
- let firstW;
21334
- let contW;
21335
- if (block.type === "list_item" && gongmunList.has(i)) {
21336
- const { marker, depth } = gongmunList.get(i);
21337
- const content = plainRenderText(block.text || "");
21338
- text = marker ? `${marker} ${content}` : content;
21339
- const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
21340
- firstW = pageW - left - Math.max(indent, 0);
21341
- contW = pageW - left - Math.max(-indent, 0);
21342
- } else if (block.type === "paragraph") {
21343
- const raw = (block.text || "").trim();
21344
- if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
21345
- text = plainRenderText(raw);
21346
- firstW = contW = pageW;
21347
- } else {
21348
- continue;
21349
- }
21350
- if (!text) continue;
21351
- const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
21352
- if (r === null) continue;
21353
- ratioByBlock.set(i, r);
21354
- if (!variants.includes(r)) variants.push(r);
21431
+ function replicateCellInnerHtml(cell) {
21432
+ if (cell.blocks?.length) {
21433
+ return cell.blocks.map((b) => {
21434
+ if (b.type === "table" && b.table) {
21435
+ const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
21436
+ return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
21437
+ }
21438
+ if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
21439
+ const t = sanitizeText(b.text ?? "");
21440
+ return t ? t.replace(/\n/g, "<br>") : "";
21441
+ }).filter(Boolean).join("<br>");
21355
21442
  }
21356
- return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
21443
+ return sanitizeText(cell.text).replace(/\n/g, "<br>");
21357
21444
  }
21358
- function variantMapper(fit, blockIdx) {
21359
- const r = fit.ratioByBlock.get(blockIdx);
21360
- if (r === void 0) return void 0;
21361
- const vi = fit.variants.indexOf(r);
21362
- return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
21445
+ function replicateTableToHtml(table) {
21446
+ const rows = replicateHtmlTable(table);
21447
+ const lines = ["<table>"];
21448
+ for (let r = 0; r < rows.length; r++) {
21449
+ const tag = rows[r].tag;
21450
+ const rowHtml = rows[r].cells.map((cell) => {
21451
+ const attrs = [];
21452
+ if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
21453
+ if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
21454
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
21455
+ return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
21456
+ });
21457
+ if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
21458
+ }
21459
+ lines.push("</table>");
21460
+ return lines.join("\n");
21363
21461
  }
21364
- function buildCharProperties(theme, gongmun, ratioVariants = []) {
21365
- let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
21366
- if (gongmun) {
21367
- body = gongmun.bodyHeight;
21368
- code = Math.max(body - 200, 900);
21369
- h1 = gongmun.preset === "report" || gongmun.preset === "plan" ? 2e3 : 1700;
21370
- h2 = 1600;
21371
- h3 = body;
21372
- h4 = Math.max(body - 100, 1300);
21462
+ function replicateHtmlTable(table) {
21463
+ const { cells, rows: numRows, cols: numCols } = table;
21464
+ const skip = /* @__PURE__ */ new Set();
21465
+ const result = [];
21466
+ for (let r = 0; r < numRows; r++) {
21467
+ const tag = r === 0 ? "th" : "td";
21468
+ const rowCells = [];
21469
+ for (let c = 0; c < numCols; c++) {
21470
+ if (skip.has(`${r},${c}`)) continue;
21471
+ const cell = cells[r]?.[c];
21472
+ if (!cell) continue;
21473
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
21474
+ for (let dc = 0; dc < cell.colSpan; dc++) {
21475
+ if (dr === 0 && dc === 0) continue;
21476
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
21477
+ }
21478
+ }
21479
+ rowCells.push({
21480
+ inner: replicateCellInnerHtml(cell),
21481
+ colSpan: cell.colSpan,
21482
+ rowSpan: cell.rowSpan,
21483
+ gridR: r,
21484
+ gridC: c
21485
+ });
21486
+ }
21487
+ if (rowCells.length) result.push({ tag, cells: rowCells });
21373
21488
  }
21374
- const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
21375
- const rows = [
21376
- charPr(0, body, false, false, 0, theme.body, bodyRatio),
21377
- charPr(1, body, true, false, 0, theme.body, bodyRatio),
21378
- charPr(2, body, false, true, 0, theme.body, bodyRatio),
21379
- charPr(3, body, true, true, 0, theme.body, bodyRatio),
21380
- charPr(4, code, false, false, 1),
21381
- charPr(5, h1, true, false, 1, theme.h1),
21382
- charPr(6, h2, true, false, 1, theme.h2),
21383
- charPr(7, h3, true, false, 1, theme.h3),
21384
- charPr(8, h4, true, false, 1, theme.h4),
21385
- charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
21386
- charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
21387
- ];
21388
- for (const r of ratioVariants) {
21389
- rows.push(
21390
- charPr(rows.length, body, false, false, 0, theme.body, r),
21391
- charPr(rows.length + 1, body, true, false, 0, theme.body, r),
21392
- charPr(rows.length + 2, body, false, true, 0, theme.body, r),
21393
- charPr(rows.length + 3, body, true, true, 0, theme.body, r)
21394
- );
21489
+ return result;
21490
+ }
21491
+ function parseHtmlTable(raw) {
21492
+ const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
21493
+ let depth = 0;
21494
+ let currentRow = null;
21495
+ let cellStart = -1;
21496
+ let cellInfo = null;
21497
+ const rows = [];
21498
+ let m;
21499
+ while ((m = re.exec(raw)) !== null) {
21500
+ const isClose = m[1] === "/";
21501
+ const tag = m[2].toLowerCase();
21502
+ const attrs = m[3] || "";
21503
+ if (tag === "table") {
21504
+ depth += isClose ? -1 : 1;
21505
+ if (depth < 0) return null;
21506
+ continue;
21507
+ }
21508
+ if (depth !== 1) continue;
21509
+ if (tag === "tr") {
21510
+ if (!isClose) currentRow = [];
21511
+ else if (currentRow) {
21512
+ rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
21513
+ currentRow = null;
21514
+ }
21515
+ } else {
21516
+ if (!isClose) {
21517
+ const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
21518
+ const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
21519
+ cellStart = m.index + m[0].length;
21520
+ cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
21521
+ } else if (cellStart >= 0 && cellInfo && currentRow) {
21522
+ currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
21523
+ cellStart = -1;
21524
+ cellInfo = null;
21525
+ }
21526
+ }
21395
21527
  }
21396
- return `<hh:charProperties itemCnt="${rows.length}">
21397
- ${rows.join("\n")}
21398
- </hh:charProperties>`;
21528
+ if (depth !== 0) return null;
21529
+ return rows;
21399
21530
  }
21400
- function buildParaProperties(gongmun) {
21401
- if (!gongmun) {
21402
- const base2 = [
21403
- paraPr(0),
21404
- paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 }),
21405
- paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 }),
21406
- paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 }),
21407
- paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 }),
21408
- paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 }),
21409
- paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 }),
21410
- paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })
21411
- ];
21412
- return `<hh:paraProperties itemCnt="${base2.length}">
21413
- ${base2.join("\n")}
21414
- </hh:paraProperties>`;
21531
+ var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
21532
+ function htmlCellInnerToLines(inner) {
21533
+ let hadNonText = false;
21534
+ let work = inner;
21535
+ if (/<table[\s>]/i.test(work)) {
21536
+ hadNonText = true;
21537
+ work = removeNestedTables(work);
21415
21538
  }
21416
- const ls = gongmun.lineSpacing;
21417
- const titleAlign = gongmun.centerTitle ? "CENTER" : "LEFT";
21418
- const base = [
21419
- paraPr(0, { lineSpacing: ls, keepWord: true }),
21420
- paraPr(1, { align: titleAlign, spaceBefore: 400, spaceAfter: 400, lineSpacing: ls, keepWord: true }),
21421
- paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: ls, keepWord: true }),
21422
- paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
21423
- paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
21424
- paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400, keepWord: true }),
21425
- paraPr(6, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true }),
21426
- paraPr(7, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true })
21427
- ];
21428
- for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
21429
- const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering);
21430
- const sectionGap = gongmun.numbering === "report" && d === 0 ? Math.round(gongmun.bodyHeight * 0.5) : 0;
21431
- base.push(paraPr(GONGMUN_LIST_BASE + d, { align: "JUSTIFY", lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true }));
21539
+ if (/<img\s/i.test(work)) {
21540
+ hadNonText = true;
21541
+ work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
21432
21542
  }
21433
- base.push(paraPr(GONGMUN_CENTER, { align: "CENTER", lineSpacing: ls, keepWord: true }));
21434
- return `<hh:paraProperties itemCnt="${base.length}">
21435
- ${base.join("\n")}
21436
- </hh:paraProperties>`;
21543
+ const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
21544
+ return { lines, hadNonText };
21437
21545
  }
21438
- function generateHeaderXml(theme, gongmun, ratioVariants = []) {
21439
- const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
21440
- const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
21441
- const paraPropsXml = buildParaProperties(gongmun);
21442
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21443
- <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
21444
- <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
21445
- <hh:refList>
21446
- <hh:fontfaces itemCnt="7">
21447
- <hh:fontface lang="HANGUL" fontCnt="3">
21448
- <hh:font id="0" face="${bodyFace}" type="TTF" isEmbedded="0">
21449
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21450
- </hh:font>
21451
- <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
21452
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21453
- </hh:font>
21454
- <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
21455
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21456
- </hh:font>
21457
- </hh:fontface>
21458
- <hh:fontface lang="LATIN" fontCnt="3">
21459
- <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
21460
- <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
21461
- </hh:font>
21462
- <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
21463
- <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
21464
- </hh:font>
21465
- <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
21466
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
21467
- </hh:font>
21468
- </hh:fontface>
21469
- <hh:fontface lang="HANJA" fontCnt="1">
21470
- <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
21471
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21472
- </hh:font>
21473
- </hh:fontface>
21474
- <hh:fontface lang="JAPANESE" fontCnt="1">
21475
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21476
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21477
- </hh:font>
21478
- </hh:fontface>
21479
- <hh:fontface lang="OTHER" fontCnt="1">
21480
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21481
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21482
- </hh:font>
21483
- </hh:fontface>
21484
- <hh:fontface lang="SYMBOL" fontCnt="1">
21485
- <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
21486
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21487
- </hh:font>
21488
- </hh:fontface>
21489
- <hh:fontface lang="USER" fontCnt="1">
21490
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21491
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21492
- </hh:font>
21493
- </hh:fontface>
21494
- </hh:fontfaces>
21495
- <hh:borderFills itemCnt="2">
21496
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
21497
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
21498
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
21499
- <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
21500
- <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
21501
- <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
21502
- <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
21503
- </hh:borderFill>
21504
- <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
21505
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
21506
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
21507
- <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
21508
- <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
21509
- <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
21510
- <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
21511
- </hh:borderFill>
21512
- </hh:borderFills>
21513
- ${charPropsXml}
21514
- <hh:tabProperties itemCnt="0"/>
21515
- <hh:numberings itemCnt="0"/>
21516
- <hh:bullets itemCnt="0"/>
21517
- ${paraPropsXml}
21518
- <hh:styles itemCnt="1">
21519
- <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
21520
- </hh:styles>
21521
- </hh:refList>
21522
- <hh:compatibleDocument targetProgram="HWP2018"><hh:layoutCompatibility/></hh:compatibleDocument>
21523
- </hh:head>`;
21546
+ function extractTopLevelTables(html) {
21547
+ const result = [];
21548
+ let depth = 0;
21549
+ let start = -1;
21550
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
21551
+ let m;
21552
+ while ((m = re.exec(html)) !== null) {
21553
+ if (m[1] !== "/") {
21554
+ if (depth === 0) start = m.index;
21555
+ depth++;
21556
+ } else {
21557
+ depth--;
21558
+ if (depth === 0 && start >= 0) {
21559
+ result.push(html.slice(start, m.index + m[0].length));
21560
+ start = -1;
21561
+ }
21562
+ if (depth < 0) depth = 0;
21563
+ }
21564
+ }
21565
+ return result;
21524
21566
  }
21525
- function generateSecPr(gongmun) {
21526
- const m = gongmun ? {
21527
- top: mmToHwpunit(gongmun.margins.top),
21528
- bottom: mmToHwpunit(gongmun.margins.bottom),
21529
- left: mmToHwpunit(gongmun.margins.left),
21530
- right: mmToHwpunit(gongmun.margins.right),
21531
- header: 0,
21532
- footer: 0
21533
- } : { top: 8504, bottom: 4252, left: 5670, right: 4252, header: 2835, footer: 2835 };
21534
- return `<hp:secPr textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" outlineShapeIDRef="0" memoShapeIDRef="0" textVerticalWidthHead="0" masterPageCnt="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="${m.header}" footer="${m.footer}" gutter="0" left="${m.left}" right="${m.right}" top="${m.top}" bottom="${m.bottom}"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr></hp:secPr>`;
21567
+ function removeNestedTables(html) {
21568
+ let result = "";
21569
+ let depth = 0;
21570
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
21571
+ let last = 0;
21572
+ let m;
21573
+ while ((m = re.exec(html)) !== null) {
21574
+ if (m[1] !== "/") {
21575
+ if (depth === 0) result += html.slice(last, m.index);
21576
+ depth++;
21577
+ } else {
21578
+ depth--;
21579
+ if (depth === 0) last = m.index + m[0].length;
21580
+ if (depth < 0) depth = 0;
21581
+ }
21582
+ }
21583
+ if (depth === 0) result += html.slice(last);
21584
+ return result;
21535
21585
  }
21586
+
21587
+ // src/hwpx/gen-table.ts
21536
21588
  var TABLE_ID_BASE = 1e3;
21537
21589
  var tableIdCounter = TABLE_ID_BASE;
21538
21590
  function nextTableId() {
@@ -21622,41 +21674,18 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3) {
21622
21674
  }
21623
21675
  return `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0"><hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${cellH * rowCnt}" heightRelTo="ABSOLUTE" protect="0"/><hp:pos treatAsChar="1" affectLSpacing="0" flowWithText="0" allowOverlap="0" holdAnchorAndSO="0" vertRelTo="PARA" horzRelTo="PARA" vertAlign="TOP" horzAlign="LEFT" vertOffset="0" horzOffset="0"/><hp:outMargin left="0" right="0" top="0" bottom="0"/><hp:inMargin left="510" right="510" top="141" bottom="141"/>` + trXmls.join("") + `</hp:tbl>`;
21624
21676
  }
21625
- function precomputeGongmunList(blocks, gongmun) {
21626
- const result = /* @__PURE__ */ new Map();
21627
- let i = 0;
21628
- while (i < blocks.length) {
21629
- if (blocks[i].type !== "list_item") {
21630
- i++;
21631
- continue;
21632
- }
21633
- const run = [];
21634
- while (i < blocks.length) {
21635
- const t = blocks[i].type;
21636
- if (t === "list_item") {
21637
- run.push(i);
21638
- i++;
21639
- continue;
21640
- }
21641
- if (t === "table" || t === "html_table") {
21642
- let j = i + 1;
21643
- while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
21644
- if (j < blocks.length && blocks[j].type === "list_item") {
21645
- i = j;
21646
- continue;
21647
- }
21648
- }
21649
- break;
21650
- }
21651
- const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
21652
- const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
21653
- const numberer = new GongmunNumberer(gongmun.numbering);
21654
- run.forEach((bi, k) => {
21655
- const marker = numberer.next(depths[k], suppress[k]);
21656
- result.set(bi, { marker, depth: depths[k] });
21657
- });
21658
- }
21659
- return result;
21677
+
21678
+ // src/hwpx/gen-section.ts
21679
+ function generateSecPr(gongmun) {
21680
+ const m = gongmun ? {
21681
+ top: mmToHwpunit(gongmun.margins.top),
21682
+ bottom: mmToHwpunit(gongmun.margins.bottom),
21683
+ left: mmToHwpunit(gongmun.margins.left),
21684
+ right: mmToHwpunit(gongmun.margins.right),
21685
+ header: 0,
21686
+ footer: 0
21687
+ } : { top: 8504, bottom: 4252, left: 5670, right: 4252, header: 2835, footer: 2835 };
21688
+ return `<hp:secPr textDirection="HORIZONTAL" spaceColumns="1134" tabStop="8000" outlineShapeIDRef="0" memoShapeIDRef="0" textVerticalWidthHead="0" masterPageCnt="0"><hp:grid lineGrid="0" charGrid="0" wonggojiFormat="0"/><hp:startNum pageStartsOn="BOTH" page="0" pic="0" tbl="0" equation="0"/><hp:visibility hideFirstHeader="0" hideFirstFooter="0" hideFirstMasterPage="0" border="SHOW_ALL" fill="SHOW_ALL" hideFirstPageNum="0" hideFirstEmptyLine="0" showLineNumber="0"/><hp:pagePr landscape="WIDELY" width="59528" height="84188" gutterType="LEFT_ONLY"><hp:margin header="${m.header}" footer="${m.footer}" gutter="0" left="${m.left}" right="${m.right}" top="${m.top}" bottom="${m.bottom}"/></hp:pagePr><hp:footNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="-1" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="283" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="EACH_COLUMN" beneathText="0"/></hp:footNotePr><hp:endNotePr><hp:autoNumFormat type="DIGIT" userChar="" prefixChar="" suffixChar=")" supscript="0"/><hp:noteLine length="14692344" type="SOLID" width="0.12 mm" color="#000000"/><hp:noteSpacing betweenNotes="0" belowLine="567" aboveLine="850"/><hp:numbering type="CONTINUOUS" newNum="1"/><hp:placement place="END_OF_DOCUMENT" beneathText="0"/></hp:endNotePr></hp:secPr>`;
21660
21689
  }
21661
21690
  function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null) {
21662
21691
  const paraXmls = [];
@@ -21779,6 +21808,24 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
21779
21808
  </hs:sec>`;
21780
21809
  }
21781
21810
 
21811
+ // src/hwpx/generator.ts
21812
+ async function markdownToHwpx(markdown, options) {
21813
+ const theme = resolveTheme(options?.theme);
21814
+ const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
21815
+ const blocks = parseMarkdownToBlocks(markdown);
21816
+ const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
21817
+ const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
21818
+ const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
21819
+ const zip = new JSZip7();
21820
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
21821
+ zip.file("META-INF/container.xml", generateContainerXml());
21822
+ zip.file("Contents/content.hpf", generateManifest());
21823
+ zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
21824
+ zip.file("Contents/section0.xml", sectionXml);
21825
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
21826
+ return await zip.generateAsync({ type: "arraybuffer" });
21827
+ }
21828
+
21782
21829
  // src/diff/compare.ts
21783
21830
  var SIMILARITY_THRESHOLD = 0.4;
21784
21831
  async function compare(bufferA, bufferB, options) {
@@ -24503,7 +24550,7 @@ async function parseHwp(buffer, options) {
24503
24550
  async function parsePdf(buffer, options) {
24504
24551
  let parsePdfDocument;
24505
24552
  try {
24506
- const mod = await import("./parser-XEDROIM7.js");
24553
+ const mod = await import("./parser-TM3AS25T.js");
24507
24554
  parsePdfDocument = mod.parsePdfDocument;
24508
24555
  } catch {
24509
24556
  return {