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
@@ -25,7 +25,7 @@ import {
25
25
  sanitizeHref,
26
26
  stripDtd,
27
27
  toArrayBuffer
28
- } from "./chunk-IFYJFWD2.js";
28
+ } from "./chunk-KIWKBRCJ.js";
29
29
  import {
30
30
  parsePageRange
31
31
  } from "./chunk-MOL7MDBG.js";
@@ -16581,6 +16581,9 @@ function getElements(parent, tagName) {
16581
16581
  const nodes = parent.getElementsByTagName(tagName);
16582
16582
  const result = [];
16583
16583
  for (let i = 0; i < nodes.length; i++) result.push(nodes[i]);
16584
+ if (result.length > 0) return result;
16585
+ const nsNodes = parent.getElementsByTagNameNS?.("*", tagName);
16586
+ if (nsNodes) for (let i = 0; i < nsNodes.length; i++) result.push(nsNodes[i]);
16584
16587
  return result;
16585
16588
  }
16586
16589
  function getTextContent(el) {
@@ -18404,6 +18407,7 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
18404
18407
  if (tag === "P") {
18405
18408
  if (!inHeaderFooter) {
18406
18409
  parseParagraph3(el, blocks, paraShapeMap, sectionNum);
18410
+ walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings);
18407
18411
  }
18408
18412
  continue;
18409
18413
  }
@@ -18420,6 +18424,21 @@ function walkContent(node, blocks, paraShapeMap, sectionNum, warnings, inHeaderF
18420
18424
  walkContent(el, blocks, paraShapeMap, sectionNum, warnings, inHeaderFooter, depth + 1);
18421
18425
  }
18422
18426
  }
18427
+ function walkTablesInP(node, blocks, paraShapeMap, sectionNum, warnings, depth = 0) {
18428
+ if (depth > MAX_XML_DEPTH2) return;
18429
+ const children = node.childNodes;
18430
+ for (let i = 0; i < children.length; i++) {
18431
+ const el = children[i];
18432
+ if (el.nodeType !== 1) continue;
18433
+ const tag = localName(el);
18434
+ if (tag === "TABLE") {
18435
+ parseTable2(el, blocks, paraShapeMap, sectionNum, warnings);
18436
+ continue;
18437
+ }
18438
+ if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
18439
+ walkTablesInP(el, blocks, paraShapeMap, sectionNum, warnings, depth + 1);
18440
+ }
18441
+ }
18423
18442
  function parseParagraph3(el, blocks, paraShapeMap, sectionNum) {
18424
18443
  const paraShapeId = el.getAttribute("ParaShape") ?? "";
18425
18444
  const shapeInfo = paraShapeMap.get(paraShapeId);
@@ -18515,13 +18534,29 @@ function collectCellText(node, parts, depth) {
18515
18534
  if (tag === "P") {
18516
18535
  const t = extractParagraphText(el);
18517
18536
  if (t) parts.push(t);
18537
+ collectNestedTableText(el, parts, depth + 1);
18518
18538
  } else if (tag === "TABLE") {
18519
- parts.push("[\uC911\uCCA9 \uD14C\uC774\uBE14]");
18539
+ collectCellText(el, parts, depth + 1);
18520
18540
  } else {
18521
18541
  collectCellText(el, parts, depth + 1);
18522
18542
  }
18523
18543
  }
18524
18544
  }
18545
+ function collectNestedTableText(node, parts, depth) {
18546
+ if (depth > 20) return;
18547
+ const children = node.childNodes;
18548
+ for (let i = 0; i < children.length; i++) {
18549
+ const el = children[i];
18550
+ if (el.nodeType !== 1) continue;
18551
+ const tag = localName(el);
18552
+ if (tag === "TABLE") {
18553
+ collectCellText(el, parts, depth + 1);
18554
+ continue;
18555
+ }
18556
+ if (tag === "FOOTNOTE" || tag === "ENDNOTE" || tag === "HEADER" || tag === "FOOTER") continue;
18557
+ collectNestedTableText(el, parts, depth + 1);
18558
+ }
18559
+ }
18525
18560
  function localName(el) {
18526
18561
  return (el.tagName || el.localName || "").replace(/^[^:]+:/, "");
18527
18562
  }
@@ -20404,53 +20439,112 @@ function mmToHwpunit(mm) {
20404
20439
  return Math.round(mm * 7200 / 25.4);
20405
20440
  }
20406
20441
 
20407
- // src/diff/text-diff.ts
20408
- function similarity(a, b) {
20409
- if (a === b) return 1;
20410
- if (!a || !b) return 0;
20411
- const maxLen = Math.max(a.length, b.length);
20412
- if (maxLen === 0) return 1;
20413
- return 1 - levenshtein(a, b) / maxLen;
20442
+ // src/hwpx/gen-ids.ts
20443
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
20444
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
20445
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
20446
+ var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
20447
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
20448
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
20449
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
20450
+ var CHAR_NORMAL = 0;
20451
+ var CHAR_BOLD = 1;
20452
+ var CHAR_ITALIC = 2;
20453
+ var CHAR_BOLD_ITALIC = 3;
20454
+ var CHAR_CODE = 4;
20455
+ var CHAR_H1 = 5;
20456
+ var CHAR_H2 = 6;
20457
+ var CHAR_H3 = 7;
20458
+ var CHAR_H4 = 8;
20459
+ var CHAR_TABLE_HEADER = 9;
20460
+ var CHAR_QUOTE = 10;
20461
+ var PARA_NORMAL = 0;
20462
+ var PARA_H1 = 1;
20463
+ var PARA_H2 = 2;
20464
+ var PARA_H3 = 3;
20465
+ var PARA_H4 = 4;
20466
+ var PARA_CODE = 5;
20467
+ var PARA_QUOTE = 6;
20468
+ var PARA_LIST = 7;
20469
+ var DEFAULT_TEXT_COLOR = "#000000";
20470
+ function resolveTheme(theme) {
20471
+ return {
20472
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
20473
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
20474
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20475
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20476
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20477
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
20478
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
20479
+ hasQuoteOption: theme?.quoteColor !== void 0,
20480
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20481
+ tableHeaderBold: !!theme?.tableHeaderBold
20482
+ };
20414
20483
  }
20415
- function normalizedSimilarity(a, b) {
20416
- return similarity(normalize(a), normalize(b));
20484
+ function escapeXml(text) {
20485
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20417
20486
  }
20418
- function normalize(s) {
20419
- return s.replace(/\s+/g, " ").trim();
20487
+ function headingParaPrId(level) {
20488
+ if (level === 1) return PARA_H1;
20489
+ if (level === 2) return PARA_H2;
20490
+ if (level === 3) return PARA_H3;
20491
+ return PARA_H4;
20420
20492
  }
20421
- var MAX_LEVENSHTEIN_LEN = 1e4;
20422
- function levenshtein(a, b) {
20423
- if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20424
- const sampleLen = Math.min(500, a.length, b.length);
20425
- let diffs = 0;
20426
- for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20427
- const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20428
- return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20429
- }
20430
- if (a.length > b.length) [a, b] = [b, a];
20431
- const m = a.length;
20432
- const n = b.length;
20433
- let prev = Array.from({ length: m + 1 }, (_, i) => i);
20434
- let curr = new Array(m + 1);
20435
- for (let j = 1; j <= n; j++) {
20436
- curr[0] = j;
20437
- for (let i = 1; i <= m; i++) {
20438
- if (a[i - 1] === b[j - 1]) {
20439
- curr[i] = prev[i - 1];
20440
- } else {
20441
- curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20442
- }
20443
- }
20444
- ;
20445
- [prev, curr] = [curr, prev];
20446
- }
20447
- return prev[m];
20493
+ function headingCharPrId(level) {
20494
+ if (level === 1) return CHAR_H1;
20495
+ if (level === 2) return CHAR_H2;
20496
+ if (level === 3) return CHAR_H3;
20497
+ return CHAR_H4;
20498
+ }
20499
+ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100) {
20500
+ const boldAttr = bold ? ` bold="1"` : "";
20501
+ const italicAttr = italic ? ` italic="1"` : "";
20502
+ const effFont = bold ? 2 : fontId;
20503
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
20504
+ <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
20505
+ <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
20506
+ <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20507
+ <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
20508
+ <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
20509
+ </hh:charPr>`;
20510
+ }
20511
+ function paraPr(id, opts = {}) {
20512
+ const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, keepWord = false } = opts;
20513
+ const breakNonLatin = keepWord ? "KEEP_WORD" : "BREAK_WORD";
20514
+ const snapGrid = keepWord ? "0" : "1";
20515
+ return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="${snapGrid}" suppressLineNumbers="0" checked="0" textDir="AUTO">
20516
+ <hh:align horizontal="${align}" vertical="BASELINE"/>
20517
+ <hh:heading type="NONE" idRef="0" level="0"/>
20518
+ <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
20519
+ <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20520
+ <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>
20521
+ <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20522
+ <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20523
+ </hh:paraPr>`;
20448
20524
  }
20525
+ var GONGMUN_LIST_BASE = 8;
20526
+ var GONGMUN_LIST_LEVELS = 8;
20527
+ var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
20528
+ var CHAR_VARIANT_BASE = 11;
20529
+ var GONGMUN_BODY_RATIO = 95;
20449
20530
 
20450
- // src/roundtrip/markdown-units.ts
20451
- function splitMarkdownUnits(md2) {
20531
+ // src/hwpx/md-runs.ts
20532
+ function buildPrvText(blocks) {
20533
+ const lines = [];
20534
+ let bytes = 0;
20535
+ for (const b of blocks) {
20536
+ let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
20537
+ if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
20538
+ if (!text) continue;
20539
+ lines.push(text);
20540
+ bytes += text.length * 3;
20541
+ if (bytes > 1024) break;
20542
+ }
20543
+ return lines.join("\n").slice(0, 1024);
20544
+ }
20545
+ function parseMarkdownToBlocks(md2) {
20452
20546
  const lines = md2.split("\n");
20453
- const units = [];
20547
+ const blocks = [];
20454
20548
  let i = 0;
20455
20549
  while (i < lines.length) {
20456
20550
  const line = lines[i];
@@ -20458,442 +20552,451 @@ function splitMarkdownUnits(md2) {
20458
20552
  i++;
20459
20553
  continue;
20460
20554
  }
20461
- if (line.trim().startsWith("<table>")) {
20462
- const collected2 = [];
20555
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20556
+ if (fenceMatch) {
20557
+ const fence = fenceMatch[1];
20558
+ const lang = fenceMatch[2].trim();
20559
+ const codeLines = [];
20560
+ i++;
20561
+ while (i < lines.length && !lines[i].startsWith(fence)) {
20562
+ codeLines.push(lines[i]);
20563
+ i++;
20564
+ }
20565
+ if (i < lines.length) i++;
20566
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20567
+ continue;
20568
+ }
20569
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20570
+ blocks.push({ type: "hr" });
20571
+ i++;
20572
+ continue;
20573
+ }
20574
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20575
+ if (headingMatch) {
20576
+ blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20577
+ i++;
20578
+ continue;
20579
+ }
20580
+ if (/^<table[\s>]/i.test(line.trimStart())) {
20581
+ const htmlLines = [];
20463
20582
  let depth = 0;
20464
20583
  while (i < lines.length) {
20465
20584
  const l = lines[i];
20466
- collected2.push(l);
20467
- depth += (l.match(/<table>/g) || []).length;
20468
- depth -= (l.match(/<\/table>/g) || []).length;
20585
+ htmlLines.push(l);
20586
+ depth += (l.match(/<table[\s>]/gi) ?? []).length;
20587
+ depth -= (l.match(/<\/table>/gi) ?? []).length;
20469
20588
  i++;
20470
20589
  if (depth <= 0) break;
20471
20590
  }
20472
- units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20591
+ blocks.push({ type: "html_table", text: htmlLines.join("\n") });
20473
20592
  continue;
20474
20593
  }
20475
20594
  if (line.trimStart().startsWith("|")) {
20476
- const collected2 = [];
20595
+ const tableRows = [];
20477
20596
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20478
- collected2.push(lines[i]);
20597
+ const row = lines[i];
20598
+ if (/^[\s|:\-]+$/.test(row)) {
20599
+ i++;
20600
+ continue;
20601
+ }
20602
+ const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20603
+ if (cells.length > 0) tableRows.push(cells);
20479
20604
  i++;
20480
20605
  }
20481
- units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20606
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
20482
20607
  continue;
20483
20608
  }
20484
- if (/^-{3,}\s*$/.test(line.trim())) {
20485
- units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20486
- i++;
20609
+ if (line.trimStart().startsWith("> ")) {
20610
+ const quoteLines = [];
20611
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20612
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
20613
+ i++;
20614
+ }
20615
+ for (const ql of quoteLines) {
20616
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
20617
+ }
20487
20618
  continue;
20488
20619
  }
20489
- if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20490
- units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20620
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20621
+ if (listMatch) {
20622
+ const indent = Math.floor(listMatch[1].length / 2);
20623
+ const ordered = /\d/.test(listMatch[2]);
20624
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
20491
20625
  i++;
20492
20626
  continue;
20493
20627
  }
20494
- const collected = [];
20495
- while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20496
- collected.push(lines[i].trim());
20497
- i++;
20498
- }
20499
- units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20628
+ blocks.push({ type: "paragraph", text: line.trim() });
20629
+ i++;
20500
20630
  }
20501
- return units;
20631
+ return blocks;
20502
20632
  }
20503
- function alignUnits(a, b) {
20504
- const m = a.length, n = b.length;
20505
- if (m * n > 4e6) {
20506
- const result2 = [];
20507
- let pre = 0;
20508
- while (pre < m && pre < n && a[pre] === b[pre]) {
20509
- result2.push([pre, pre]);
20510
- pre++;
20511
- }
20512
- let suf = 0;
20513
- while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
20514
- const aMid = m - pre - suf, bMid = n - pre - suf;
20515
- if (aMid === bMid) {
20516
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
20517
- } else {
20518
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
20519
- for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
20633
+ function parseInlineMarkdown(text) {
20634
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
20635
+ text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
20636
+ text = text.replace(/~~([^~]+)~~/g, "$1");
20637
+ const spans = [];
20638
+ const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
20639
+ let lastIdx = 0;
20640
+ for (const match of text.matchAll(regex)) {
20641
+ const idx = match.index;
20642
+ if (idx > lastIdx) {
20643
+ spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
20520
20644
  }
20521
- for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
20522
- return result2;
20523
- }
20524
- const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
20525
- for (let i2 = 1; i2 <= m; i2++) {
20526
- for (let j2 = 1; j2 <= n; j2++) {
20527
- 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]);
20645
+ const raw = match[0];
20646
+ if (raw.startsWith("`")) {
20647
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
20648
+ } else if (raw.startsWith("***") || raw.startsWith("___")) {
20649
+ spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
20650
+ } else if (raw.startsWith("**") || raw.startsWith("__")) {
20651
+ spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
20652
+ } else {
20653
+ spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
20528
20654
  }
20655
+ lastIdx = idx + raw.length;
20529
20656
  }
20530
- const matches = [];
20531
- let i = m, j = n;
20532
- while (i > 0 && j > 0) {
20533
- if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
20534
- matches.push([i - 1, j - 1]);
20535
- i--;
20536
- j--;
20537
- } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
20538
- else j--;
20657
+ if (lastIdx < text.length) {
20658
+ spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
20539
20659
  }
20540
- matches.reverse();
20541
- const result = [];
20542
- let ai = 0, bi = 0;
20543
- const flushGap = (aEnd, bEnd) => {
20544
- if (aEnd - ai === bEnd - bi) {
20545
- while (ai < aEnd) result.push([ai++, bi++]);
20546
- return;
20547
- }
20548
- while (ai < aEnd && bi < bEnd) {
20549
- const sim = normalizedSimilarity(a[ai], b[bi]);
20550
- if (sim >= 0.4) {
20551
- if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
20552
- result.push([ai++, null]);
20553
- } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
20554
- result.push([null, bi++]);
20555
- } else {
20556
- result.push([ai++, bi++]);
20557
- }
20558
- } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
20559
- else result.push([null, bi++]);
20560
- }
20561
- while (ai < aEnd) result.push([ai++, null]);
20562
- while (bi < bEnd) result.push([null, bi++]);
20563
- };
20564
- for (const [pi, pj] of matches) {
20565
- flushGap(pi, pj);
20566
- result.push([ai++, bi++]);
20660
+ if (spans.length === 0) {
20661
+ spans.push({ text, bold: false, italic: false, code: false });
20567
20662
  }
20568
- flushGap(m, n);
20569
- return result;
20663
+ return spans;
20570
20664
  }
20571
- function bestSimInRange(arr, from, to, target) {
20572
- let best = 0;
20573
- for (let k = from; k <= to && k < arr.length; k++) {
20574
- const s = normalizedSimilarity(arr[k], target);
20575
- if (s > best) best = s;
20576
- }
20577
- return best;
20665
+ function spanToCharPrId(span) {
20666
+ if (span.code) return CHAR_CODE;
20667
+ if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
20668
+ if (span.bold) return CHAR_BOLD;
20669
+ if (span.italic) return CHAR_ITALIC;
20670
+ return CHAR_NORMAL;
20578
20671
  }
20579
- function escapeGfm(text) {
20580
- return text.replace(/~/g, "\\~");
20672
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
20673
+ const spans = parseInlineMarkdown(text);
20674
+ return spans.map((span) => {
20675
+ let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
20676
+ if (mapCharId) charId = mapCharId(charId);
20677
+ return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
20678
+ }).join("");
20581
20679
  }
20582
- var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
20583
- function sanitizeText(text) {
20584
- let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
20585
- if (result.length <= 30 && result.includes(" ")) {
20586
- const tokens = result.split(" ");
20587
- const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
20588
- if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
20589
- result = tokens.join("");
20590
- }
20680
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
20681
+ if (paraPrId === PARA_CODE) {
20682
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
20591
20683
  }
20592
- return result;
20593
- }
20594
- function normForMatch(text) {
20595
- return sanitizeText(text).replace(/\s+/g, " ").trim();
20684
+ const runs = generateRuns(text, charPrId, mapCharId);
20685
+ return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
20596
20686
  }
20597
- function unescapeGfm(text) {
20598
- return text.replace(/\\~/g, "~");
20687
+
20688
+ // src/hwpx/gen-header.ts
20689
+ function generateContainerXml() {
20690
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20691
+ <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
20692
+ <ocf:rootfiles>
20693
+ <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
20694
+ </ocf:rootfiles>
20695
+ </ocf:container>`;
20599
20696
  }
20600
- function summarize(text) {
20601
- const t = text.replace(/\s+/g, " ").trim();
20602
- return t.length > 80 ? t.slice(0, 77) + "..." : t;
20697
+ function generateManifest() {
20698
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20699
+ <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
20700
+ <opf:manifest>
20701
+ <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
20702
+ <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
20703
+ </opf:manifest>
20704
+ <opf:spine>
20705
+ <opf:itemref idref="header" linear="no"/>
20706
+ <opf:itemref idref="section0" linear="yes"/>
20707
+ </opf:spine>
20708
+ </opf:package>`;
20603
20709
  }
20604
- function replicateGfmTable(table) {
20605
- const { cells, rows: numRows, cols: numCols } = table;
20606
- if (numRows === 0 || numCols === 0) return null;
20607
- if (numRows === 1 && numCols === 1) return null;
20608
- if (numCols === 1) return null;
20609
- const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
20610
- const skip = /* @__PURE__ */ new Set();
20611
- for (let r = 0; r < numRows; r++) {
20612
- for (let c = 0; c < numCols; c++) {
20613
- if (skip.has(`${r},${c}`)) continue;
20614
- const cell = cells[r]?.[c];
20615
- if (!cell) continue;
20616
- display[r][c] = {
20617
- text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
20618
- gridR: r,
20619
- gridC: c
20620
- };
20621
- for (let dr = 0; dr < cell.rowSpan; dr++) {
20622
- for (let dc = 0; dc < cell.colSpan; dc++) {
20623
- if (dr === 0 && dc === 0) continue;
20624
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20625
- }
20626
- }
20627
- c += cell.colSpan - 1;
20628
- }
20710
+ function buildCharProperties(theme, gongmun, ratioVariants = []) {
20711
+ let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
20712
+ if (gongmun) {
20713
+ body = gongmun.bodyHeight;
20714
+ code = Math.max(body - 200, 900);
20715
+ h1 = gongmun.preset === "report" || gongmun.preset === "plan" ? 2e3 : 1700;
20716
+ h2 = 1600;
20717
+ h3 = body;
20718
+ h4 = Math.max(body - 100, 1300);
20629
20719
  }
20630
- const uniqueRows = [];
20631
- let pendingLabelRow = null;
20632
- for (let r = 0; r < display.length; r++) {
20633
- const row = display[r];
20634
- if (row.every((cell) => cell.text === "")) continue;
20635
- const nonEmptyCols = row.filter((cell) => cell.text !== "");
20636
- const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
20637
- if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
20638
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20639
- pendingLabelRow = row;
20640
- continue;
20641
- }
20642
- if (pendingLabelRow) {
20643
- if (row[0].text === "") row[0] = pendingLabelRow[0];
20644
- else uniqueRows.push(pendingLabelRow);
20645
- pendingLabelRow = null;
20646
- }
20647
- uniqueRows.push(row);
20720
+ const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
20721
+ const rows = [
20722
+ charPr(0, body, false, false, 0, theme.body, bodyRatio),
20723
+ charPr(1, body, true, false, 0, theme.body, bodyRatio),
20724
+ charPr(2, body, false, true, 0, theme.body, bodyRatio),
20725
+ charPr(3, body, true, true, 0, theme.body, bodyRatio),
20726
+ charPr(4, code, false, false, 1),
20727
+ charPr(5, h1, true, false, 1, theme.h1),
20728
+ charPr(6, h2, true, false, 1, theme.h2),
20729
+ charPr(7, h3, true, false, 1, theme.h3),
20730
+ charPr(8, h4, true, false, 1, theme.h4),
20731
+ charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
20732
+ charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
20733
+ ];
20734
+ for (const r of ratioVariants) {
20735
+ rows.push(
20736
+ charPr(rows.length, body, false, false, 0, theme.body, r),
20737
+ charPr(rows.length + 1, body, true, false, 0, theme.body, r),
20738
+ charPr(rows.length + 2, body, false, true, 0, theme.body, r),
20739
+ charPr(rows.length + 3, body, true, true, 0, theme.body, r)
20740
+ );
20648
20741
  }
20649
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20650
- return uniqueRows.length > 0 ? uniqueRows : null;
20742
+ return `<hh:charProperties itemCnt="${rows.length}">
20743
+ ${rows.join("\n")}
20744
+ </hh:charProperties>`;
20651
20745
  }
20652
- function parseGfmTable(lines) {
20653
- const rows = [];
20654
- for (const line of lines) {
20655
- const trimmed = line.trim();
20656
- if (!trimmed.startsWith("|")) continue;
20657
- const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
20658
- if (cells.length === 0) continue;
20659
- if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
20660
- rows.push(cells);
20746
+ function buildParaProperties(gongmun) {
20747
+ if (!gongmun) {
20748
+ const base2 = [
20749
+ paraPr(0),
20750
+ paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 }),
20751
+ paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 }),
20752
+ paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 }),
20753
+ paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 }),
20754
+ paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 }),
20755
+ paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 }),
20756
+ paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })
20757
+ ];
20758
+ return `<hh:paraProperties itemCnt="${base2.length}">
20759
+ ${base2.join("\n")}
20760
+ </hh:paraProperties>`;
20661
20761
  }
20662
- return rows;
20663
- }
20664
- function unescapeGfmCell(text) {
20665
- return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
20666
- }
20667
- function replicateCellInnerHtml(cell) {
20668
- if (cell.blocks?.length) {
20669
- return cell.blocks.map((b) => {
20670
- if (b.type === "table" && b.table) {
20671
- const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
20672
- return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
20673
- }
20674
- if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
20675
- const t = sanitizeText(b.text ?? "");
20676
- return t ? t.replace(/\n/g, "<br>") : "";
20677
- }).filter(Boolean).join("<br>");
20762
+ const ls = gongmun.lineSpacing;
20763
+ const titleAlign = gongmun.centerTitle ? "CENTER" : "LEFT";
20764
+ const base = [
20765
+ paraPr(0, { lineSpacing: ls, keepWord: true }),
20766
+ paraPr(1, { align: titleAlign, spaceBefore: 400, spaceAfter: 400, lineSpacing: ls, keepWord: true }),
20767
+ paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: ls, keepWord: true }),
20768
+ paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
20769
+ paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
20770
+ paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400, keepWord: true }),
20771
+ paraPr(6, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true }),
20772
+ paraPr(7, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true })
20773
+ ];
20774
+ for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
20775
+ const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering);
20776
+ const sectionGap = gongmun.numbering === "report" && d === 0 ? Math.round(gongmun.bodyHeight * 0.5) : 0;
20777
+ base.push(paraPr(GONGMUN_LIST_BASE + d, { align: "JUSTIFY", lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true }));
20678
20778
  }
20679
- return sanitizeText(cell.text).replace(/\n/g, "<br>");
20779
+ base.push(paraPr(GONGMUN_CENTER, { align: "CENTER", lineSpacing: ls, keepWord: true }));
20780
+ return `<hh:paraProperties itemCnt="${base.length}">
20781
+ ${base.join("\n")}
20782
+ </hh:paraProperties>`;
20680
20783
  }
20681
- function replicateTableToHtml(table) {
20682
- const rows = replicateHtmlTable(table);
20683
- const lines = ["<table>"];
20684
- for (let r = 0; r < rows.length; r++) {
20685
- const tag = rows[r].tag;
20686
- const rowHtml = rows[r].cells.map((cell) => {
20687
- const attrs = [];
20688
- if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
20689
- if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
20690
- const attrStr = attrs.length ? " " + attrs.join(" ") : "";
20691
- return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
20692
- });
20693
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
20694
- }
20695
- lines.push("</table>");
20696
- return lines.join("\n");
20784
+ function generateHeaderXml(theme, gongmun, ratioVariants = []) {
20785
+ const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
20786
+ const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
20787
+ const paraPropsXml = buildParaProperties(gongmun);
20788
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20789
+ <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
20790
+ <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
20791
+ <hh:refList>
20792
+ <hh:fontfaces itemCnt="7">
20793
+ <hh:fontface lang="HANGUL" fontCnt="3">
20794
+ <hh:font id="0" face="${bodyFace}" type="TTF" isEmbedded="0">
20795
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20796
+ </hh:font>
20797
+ <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
20798
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20799
+ </hh:font>
20800
+ <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
20801
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20802
+ </hh:font>
20803
+ </hh:fontface>
20804
+ <hh:fontface lang="LATIN" fontCnt="3">
20805
+ <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
20806
+ <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
20807
+ </hh:font>
20808
+ <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
20809
+ <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
20810
+ </hh:font>
20811
+ <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
20812
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
20813
+ </hh:font>
20814
+ </hh:fontface>
20815
+ <hh:fontface lang="HANJA" fontCnt="1">
20816
+ <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
20817
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20818
+ </hh:font>
20819
+ </hh:fontface>
20820
+ <hh:fontface lang="JAPANESE" fontCnt="1">
20821
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20822
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20823
+ </hh:font>
20824
+ </hh:fontface>
20825
+ <hh:fontface lang="OTHER" fontCnt="1">
20826
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20827
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20828
+ </hh:font>
20829
+ </hh:fontface>
20830
+ <hh:fontface lang="SYMBOL" fontCnt="1">
20831
+ <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
20832
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20833
+ </hh:font>
20834
+ </hh:fontface>
20835
+ <hh:fontface lang="USER" fontCnt="1">
20836
+ <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
20837
+ <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
20838
+ </hh:font>
20839
+ </hh:fontface>
20840
+ </hh:fontfaces>
20841
+ <hh:borderFills itemCnt="2">
20842
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20843
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20844
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20845
+ <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
20846
+ <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
20847
+ <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
20848
+ <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
20849
+ </hh:borderFill>
20850
+ <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20851
+ <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20852
+ <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20853
+ <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
20854
+ <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
20855
+ <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
20856
+ <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
20857
+ </hh:borderFill>
20858
+ </hh:borderFills>
20859
+ ${charPropsXml}
20860
+ <hh:tabProperties itemCnt="0"/>
20861
+ <hh:numberings itemCnt="0"/>
20862
+ <hh:bullets itemCnt="0"/>
20863
+ ${paraPropsXml}
20864
+ <hh:styles itemCnt="1">
20865
+ <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
20866
+ </hh:styles>
20867
+ </hh:refList>
20868
+ <hh:compatibleDocument targetProgram="HWP2018"><hh:layoutCompatibility/></hh:compatibleDocument>
20869
+ </hh:head>`;
20697
20870
  }
20698
- function replicateHtmlTable(table) {
20699
- const { cells, rows: numRows, cols: numCols } = table;
20700
- const skip = /* @__PURE__ */ new Set();
20701
- const result = [];
20702
- for (let r = 0; r < numRows; r++) {
20703
- const tag = r === 0 ? "th" : "td";
20704
- const rowCells = [];
20705
- for (let c = 0; c < numCols; c++) {
20706
- if (skip.has(`${r},${c}`)) continue;
20707
- const cell = cells[r]?.[c];
20708
- if (!cell) continue;
20709
- for (let dr = 0; dr < cell.rowSpan; dr++) {
20710
- for (let dc = 0; dc < cell.colSpan; dc++) {
20711
- if (dr === 0 && dc === 0) continue;
20712
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20713
- }
20714
- }
20715
- rowCells.push({
20716
- inner: replicateCellInnerHtml(cell),
20717
- colSpan: cell.colSpan,
20718
- rowSpan: cell.rowSpan,
20719
- gridR: r,
20720
- gridC: c
20721
- });
20871
+
20872
+ // src/hwpx/gen-gongmun-fit.ts
20873
+ function plainRenderText(text) {
20874
+ return parseInlineMarkdown(text).map((s) => s.text).join("");
20875
+ }
20876
+ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
20877
+ const minRatio = gongmun.autoFitMinRatio;
20878
+ if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
20879
+ const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
20880
+ const ratioByBlock = /* @__PURE__ */ new Map();
20881
+ const variants = [];
20882
+ for (let i = 0; i < blocks.length; i++) {
20883
+ const block = blocks[i];
20884
+ let text;
20885
+ let firstW;
20886
+ let contW;
20887
+ if (block.type === "list_item" && gongmunList.has(i)) {
20888
+ const { marker, depth } = gongmunList.get(i);
20889
+ const content = plainRenderText(block.text || "");
20890
+ text = marker ? `${marker} ${content}` : content;
20891
+ const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
20892
+ firstW = pageW - left - Math.max(indent, 0);
20893
+ contW = pageW - left - Math.max(-indent, 0);
20894
+ } else if (block.type === "paragraph") {
20895
+ const raw = (block.text || "").trim();
20896
+ if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
20897
+ text = plainRenderText(raw);
20898
+ firstW = contW = pageW;
20899
+ } else {
20900
+ continue;
20722
20901
  }
20723
- if (rowCells.length) result.push({ tag, cells: rowCells });
20902
+ if (!text) continue;
20903
+ const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
20904
+ if (r === null) continue;
20905
+ ratioByBlock.set(i, r);
20906
+ if (!variants.includes(r)) variants.push(r);
20724
20907
  }
20725
- return result;
20908
+ return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
20726
20909
  }
20727
- function parseHtmlTable(raw) {
20728
- const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
20729
- let depth = 0;
20730
- let currentRow = null;
20731
- let cellStart = -1;
20732
- let cellInfo = null;
20733
- const rows = [];
20734
- let m;
20735
- while ((m = re.exec(raw)) !== null) {
20736
- const isClose = m[1] === "/";
20737
- const tag = m[2].toLowerCase();
20738
- const attrs = m[3] || "";
20739
- if (tag === "table") {
20740
- depth += isClose ? -1 : 1;
20741
- if (depth < 0) return null;
20910
+ function variantMapper(fit, blockIdx) {
20911
+ const r = fit.ratioByBlock.get(blockIdx);
20912
+ if (r === void 0) return void 0;
20913
+ const vi = fit.variants.indexOf(r);
20914
+ return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
20915
+ }
20916
+ function precomputeGongmunList(blocks, gongmun) {
20917
+ const result = /* @__PURE__ */ new Map();
20918
+ let i = 0;
20919
+ while (i < blocks.length) {
20920
+ if (blocks[i].type !== "list_item") {
20921
+ i++;
20742
20922
  continue;
20743
20923
  }
20744
- if (depth !== 1) continue;
20745
- if (tag === "tr") {
20746
- if (!isClose) currentRow = [];
20747
- else if (currentRow) {
20748
- rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
20749
- currentRow = null;
20924
+ const run = [];
20925
+ while (i < blocks.length) {
20926
+ const t = blocks[i].type;
20927
+ if (t === "list_item") {
20928
+ run.push(i);
20929
+ i++;
20930
+ continue;
20750
20931
  }
20751
- } else {
20752
- if (!isClose) {
20753
- const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20754
- const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20755
- cellStart = m.index + m[0].length;
20756
- cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
20757
- } else if (cellStart >= 0 && cellInfo && currentRow) {
20758
- currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
20759
- cellStart = -1;
20760
- cellInfo = null;
20932
+ if (t === "table" || t === "html_table") {
20933
+ let j = i + 1;
20934
+ while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
20935
+ if (j < blocks.length && blocks[j].type === "list_item") {
20936
+ i = j;
20937
+ continue;
20938
+ }
20761
20939
  }
20940
+ break;
20762
20941
  }
20942
+ const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
20943
+ const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
20944
+ const numberer = new GongmunNumberer(gongmun.numbering);
20945
+ run.forEach((bi, k) => {
20946
+ const marker = numberer.next(depths[k], suppress[k]);
20947
+ result.set(bi, { marker, depth: depths[k] });
20948
+ });
20763
20949
  }
20764
- if (depth !== 0) return null;
20765
- return rows;
20950
+ return result;
20766
20951
  }
20767
- var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
20768
- function htmlCellInnerToLines(inner) {
20769
- let hadNonText = false;
20770
- let work = inner;
20771
- if (/<table[\s>]/i.test(work)) {
20772
- hadNonText = true;
20773
- work = removeNestedTables(work);
20774
- }
20775
- if (/<img\s/i.test(work)) {
20776
- hadNonText = true;
20777
- work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
20778
- }
20779
- const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
20780
- return { lines, hadNonText };
20952
+
20953
+ // src/diff/text-diff.ts
20954
+ function similarity(a, b) {
20955
+ if (a === b) return 1;
20956
+ if (!a || !b) return 0;
20957
+ const maxLen = Math.max(a.length, b.length);
20958
+ if (maxLen === 0) return 1;
20959
+ return 1 - levenshtein(a, b) / maxLen;
20781
20960
  }
20782
- function extractTopLevelTables(html) {
20783
- const result = [];
20784
- let depth = 0;
20785
- let start = -1;
20786
- const re = /<(\/?)table(?:[\s>]|>)/gi;
20787
- let m;
20788
- while ((m = re.exec(html)) !== null) {
20789
- if (m[1] !== "/") {
20790
- if (depth === 0) start = m.index;
20791
- depth++;
20792
- } else {
20793
- depth--;
20794
- if (depth === 0 && start >= 0) {
20795
- result.push(html.slice(start, m.index + m[0].length));
20796
- start = -1;
20797
- }
20798
- if (depth < 0) depth = 0;
20799
- }
20800
- }
20801
- return result;
20961
+ function normalizedSimilarity(a, b) {
20962
+ return similarity(normalize(a), normalize(b));
20802
20963
  }
20803
- function removeNestedTables(html) {
20804
- let result = "";
20805
- let depth = 0;
20806
- const re = /<(\/?)table(?:[\s>]|>)/gi;
20807
- let last = 0;
20808
- let m;
20809
- while ((m = re.exec(html)) !== null) {
20810
- if (m[1] !== "/") {
20811
- if (depth === 0) result += html.slice(last, m.index);
20812
- depth++;
20813
- } else {
20814
- depth--;
20815
- if (depth === 0) last = m.index + m[0].length;
20816
- if (depth < 0) depth = 0;
20964
+ function normalize(s) {
20965
+ return s.replace(/\s+/g, " ").trim();
20966
+ }
20967
+ var MAX_LEVENSHTEIN_LEN = 1e4;
20968
+ function levenshtein(a, b) {
20969
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20970
+ const sampleLen = Math.min(500, a.length, b.length);
20971
+ let diffs = 0;
20972
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20973
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20974
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20975
+ }
20976
+ if (a.length > b.length) [a, b] = [b, a];
20977
+ const m = a.length;
20978
+ const n = b.length;
20979
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
20980
+ let curr = new Array(m + 1);
20981
+ for (let j = 1; j <= n; j++) {
20982
+ curr[0] = j;
20983
+ for (let i = 1; i <= m; i++) {
20984
+ if (a[i - 1] === b[j - 1]) {
20985
+ curr[i] = prev[i - 1];
20986
+ } else {
20987
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20988
+ }
20817
20989
  }
20990
+ ;
20991
+ [prev, curr] = [curr, prev];
20818
20992
  }
20819
- if (depth === 0) result += html.slice(last);
20820
- return result;
20993
+ return prev[m];
20821
20994
  }
20822
20995
 
20823
- // src/hwpx/generator.ts
20824
- var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
20825
- var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
20826
- var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
20827
- var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
20828
- var NS_OPF = "http://www.idpf.org/2007/opf/";
20829
- var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
20830
- var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
20831
- var CHAR_NORMAL = 0;
20832
- var CHAR_BOLD = 1;
20833
- var CHAR_ITALIC = 2;
20834
- var CHAR_BOLD_ITALIC = 3;
20835
- var CHAR_CODE = 4;
20836
- var CHAR_H1 = 5;
20837
- var CHAR_H2 = 6;
20838
- var CHAR_H3 = 7;
20839
- var CHAR_H4 = 8;
20840
- var CHAR_TABLE_HEADER = 9;
20841
- var CHAR_QUOTE = 10;
20842
- var PARA_NORMAL = 0;
20843
- var PARA_H1 = 1;
20844
- var PARA_H2 = 2;
20845
- var PARA_H3 = 3;
20846
- var PARA_H4 = 4;
20847
- var PARA_CODE = 5;
20848
- var PARA_QUOTE = 6;
20849
- var PARA_LIST = 7;
20850
- var DEFAULT_TEXT_COLOR = "#000000";
20851
- function resolveTheme(theme) {
20852
- return {
20853
- h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
20854
- h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
20855
- h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20856
- h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20857
- body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20858
- quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
20859
- /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
20860
- hasQuoteOption: theme?.quoteColor !== void 0,
20861
- tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20862
- tableHeaderBold: !!theme?.tableHeaderBold
20863
- };
20864
- }
20865
- async function markdownToHwpx(markdown, options) {
20866
- const theme = resolveTheme(options?.theme);
20867
- const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
20868
- const blocks = parseMarkdownToBlocks(markdown);
20869
- const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
20870
- const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
20871
- const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
20872
- const zip = new JSZip6();
20873
- zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
20874
- zip.file("META-INF/container.xml", generateContainerXml());
20875
- zip.file("Contents/content.hpf", generateManifest());
20876
- zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
20877
- zip.file("Contents/section0.xml", sectionXml);
20878
- zip.file("Preview/PrvText.txt", buildPrvText(blocks));
20879
- return await zip.generateAsync({ type: "arraybuffer" });
20880
- }
20881
- function buildPrvText(blocks) {
20882
- const lines = [];
20883
- let bytes = 0;
20884
- for (const b of blocks) {
20885
- let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
20886
- if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
20887
- if (!text) continue;
20888
- lines.push(text);
20889
- bytes += text.length * 3;
20890
- if (bytes > 1024) break;
20891
- }
20892
- return lines.join("\n").slice(0, 1024);
20893
- }
20894
- function parseMarkdownToBlocks(md2) {
20996
+ // src/roundtrip/markdown-units.ts
20997
+ function splitMarkdownUnits(md2) {
20895
20998
  const lines = md2.split("\n");
20896
- const blocks = [];
20999
+ const units = [];
20897
21000
  let i = 0;
20898
21001
  while (i < lines.length) {
20899
21002
  const line = lines[i];
@@ -20901,420 +21004,369 @@ function parseMarkdownToBlocks(md2) {
20901
21004
  i++;
20902
21005
  continue;
20903
21006
  }
20904
- const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20905
- if (fenceMatch) {
20906
- const fence = fenceMatch[1];
20907
- const lang = fenceMatch[2].trim();
20908
- const codeLines = [];
20909
- i++;
20910
- while (i < lines.length && !lines[i].startsWith(fence)) {
20911
- codeLines.push(lines[i]);
20912
- i++;
20913
- }
20914
- if (i < lines.length) i++;
20915
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20916
- continue;
20917
- }
20918
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20919
- blocks.push({ type: "hr" });
20920
- i++;
20921
- continue;
20922
- }
20923
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20924
- if (headingMatch) {
20925
- blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20926
- i++;
20927
- continue;
20928
- }
20929
- if (/^<table[\s>]/i.test(line.trimStart())) {
20930
- const htmlLines = [];
21007
+ if (line.trim().startsWith("<table>")) {
21008
+ const collected2 = [];
20931
21009
  let depth = 0;
20932
21010
  while (i < lines.length) {
20933
21011
  const l = lines[i];
20934
- htmlLines.push(l);
20935
- depth += (l.match(/<table[\s>]/gi) ?? []).length;
20936
- depth -= (l.match(/<\/table>/gi) ?? []).length;
21012
+ collected2.push(l);
21013
+ depth += (l.match(/<table>/g) || []).length;
21014
+ depth -= (l.match(/<\/table>/g) || []).length;
20937
21015
  i++;
20938
21016
  if (depth <= 0) break;
20939
21017
  }
20940
- blocks.push({ type: "html_table", text: htmlLines.join("\n") });
21018
+ units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20941
21019
  continue;
20942
21020
  }
20943
21021
  if (line.trimStart().startsWith("|")) {
20944
- const tableRows = [];
21022
+ const collected2 = [];
20945
21023
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20946
- const row = lines[i];
20947
- if (/^[\s|:\-]+$/.test(row)) {
20948
- i++;
20949
- continue;
20950
- }
20951
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20952
- if (cells.length > 0) tableRows.push(cells);
21024
+ collected2.push(lines[i]);
20953
21025
  i++;
20954
21026
  }
20955
- if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
21027
+ units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20956
21028
  continue;
20957
21029
  }
20958
- if (line.trimStart().startsWith("> ")) {
20959
- const quoteLines = [];
20960
- while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20961
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
20962
- i++;
20963
- }
20964
- for (const ql of quoteLines) {
20965
- blocks.push({ type: "blockquote", text: ql.trim() || "" });
20966
- }
21030
+ if (/^-{3,}\s*$/.test(line.trim())) {
21031
+ units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
21032
+ i++;
20967
21033
  continue;
20968
21034
  }
20969
- const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20970
- if (listMatch) {
20971
- const indent = Math.floor(listMatch[1].length / 2);
20972
- const ordered = /\d/.test(listMatch[2]);
20973
- blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
21035
+ if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
21036
+ units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20974
21037
  i++;
20975
21038
  continue;
20976
21039
  }
20977
- blocks.push({ type: "paragraph", text: line.trim() });
20978
- i++;
21040
+ const collected = [];
21041
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
21042
+ collected.push(lines[i].trim());
21043
+ i++;
21044
+ }
21045
+ units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20979
21046
  }
20980
- return blocks;
21047
+ return units;
20981
21048
  }
20982
- function parseInlineMarkdown(text) {
20983
- text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
20984
- text = text.replace(/\[([^\]]*)\]\(([^)]*)\)/g, (_, t, u) => t || u);
20985
- text = text.replace(/~~([^~]+)~~/g, "$1");
20986
- const spans = [];
20987
- const regex = /(`[^`]+`|\*{3}[^*]+\*{3}|\*{2}[^*]+\*{2}|\*[^*]+\*|_{2}[^_]+_{2}|_[^_]+_)/g;
20988
- let lastIdx = 0;
20989
- for (const match of text.matchAll(regex)) {
20990
- const idx = match.index;
20991
- if (idx > lastIdx) {
20992
- spans.push({ text: text.slice(lastIdx, idx), bold: false, italic: false, code: false });
20993
- }
20994
- const raw = match[0];
20995
- if (raw.startsWith("`")) {
20996
- spans.push({ text: raw.slice(1, -1), bold: false, italic: false, code: true });
20997
- } else if (raw.startsWith("***") || raw.startsWith("___")) {
20998
- spans.push({ text: raw.slice(3, -3), bold: true, italic: true, code: false });
20999
- } else if (raw.startsWith("**") || raw.startsWith("__")) {
21000
- spans.push({ text: raw.slice(2, -2), bold: true, italic: false, code: false });
21049
+ function alignUnits(a, b) {
21050
+ const m = a.length, n = b.length;
21051
+ if (m * n > 4e6) {
21052
+ const result2 = [];
21053
+ let pre = 0;
21054
+ while (pre < m && pre < n && a[pre] === b[pre]) {
21055
+ result2.push([pre, pre]);
21056
+ pre++;
21057
+ }
21058
+ let suf = 0;
21059
+ while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
21060
+ const aMid = m - pre - suf, bMid = n - pre - suf;
21061
+ if (aMid === bMid) {
21062
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
21001
21063
  } else {
21002
- spans.push({ text: raw.slice(1, -1), bold: false, italic: true, code: false });
21064
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
21065
+ for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
21003
21066
  }
21004
- lastIdx = idx + raw.length;
21067
+ for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
21068
+ return result2;
21005
21069
  }
21006
- if (lastIdx < text.length) {
21007
- spans.push({ text: text.slice(lastIdx), bold: false, italic: false, code: false });
21070
+ const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
21071
+ for (let i2 = 1; i2 <= m; i2++) {
21072
+ for (let j2 = 1; j2 <= n; j2++) {
21073
+ 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]);
21074
+ }
21008
21075
  }
21009
- if (spans.length === 0) {
21010
- spans.push({ text, bold: false, italic: false, code: false });
21076
+ const matches = [];
21077
+ let i = m, j = n;
21078
+ while (i > 0 && j > 0) {
21079
+ if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
21080
+ matches.push([i - 1, j - 1]);
21081
+ i--;
21082
+ j--;
21083
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
21084
+ else j--;
21011
21085
  }
21012
- return spans;
21013
- }
21014
- function spanToCharPrId(span) {
21015
- if (span.code) return CHAR_CODE;
21016
- if (span.bold && span.italic) return CHAR_BOLD_ITALIC;
21017
- if (span.bold) return CHAR_BOLD;
21018
- if (span.italic) return CHAR_ITALIC;
21019
- return CHAR_NORMAL;
21086
+ matches.reverse();
21087
+ const result = [];
21088
+ let ai = 0, bi = 0;
21089
+ const flushGap = (aEnd, bEnd) => {
21090
+ if (aEnd - ai === bEnd - bi) {
21091
+ while (ai < aEnd) result.push([ai++, bi++]);
21092
+ return;
21093
+ }
21094
+ while (ai < aEnd && bi < bEnd) {
21095
+ const sim = normalizedSimilarity(a[ai], b[bi]);
21096
+ if (sim >= 0.4) {
21097
+ if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
21098
+ result.push([ai++, null]);
21099
+ } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
21100
+ result.push([null, bi++]);
21101
+ } else {
21102
+ result.push([ai++, bi++]);
21103
+ }
21104
+ } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
21105
+ else result.push([null, bi++]);
21106
+ }
21107
+ while (ai < aEnd) result.push([ai++, null]);
21108
+ while (bi < bEnd) result.push([null, bi++]);
21109
+ };
21110
+ for (const [pi, pj] of matches) {
21111
+ flushGap(pi, pj);
21112
+ result.push([ai++, bi++]);
21113
+ }
21114
+ flushGap(m, n);
21115
+ return result;
21020
21116
  }
21021
- function escapeXml(text) {
21022
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
21117
+ function bestSimInRange(arr, from, to, target) {
21118
+ let best = 0;
21119
+ for (let k = from; k <= to && k < arr.length; k++) {
21120
+ const s = normalizedSimilarity(arr[k], target);
21121
+ if (s > best) best = s;
21122
+ }
21123
+ return best;
21023
21124
  }
21024
- function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
21025
- const spans = parseInlineMarkdown(text);
21026
- return spans.map((span) => {
21027
- let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
21028
- if (mapCharId) charId = mapCharId(charId);
21029
- return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
21030
- }).join("");
21125
+ function escapeGfm(text) {
21126
+ return text.replace(/~/g, "\\~");
21031
21127
  }
21032
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
21033
- if (paraPrId === PARA_CODE) {
21034
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
21128
+ var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
21129
+ function sanitizeText(text) {
21130
+ let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
21131
+ if (result.length <= 30 && result.includes(" ")) {
21132
+ const tokens = result.split(" ");
21133
+ const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
21134
+ if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
21135
+ result = tokens.join("");
21136
+ }
21035
21137
  }
21036
- const runs = generateRuns(text, charPrId, mapCharId);
21037
- return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
21038
- }
21039
- function headingParaPrId(level) {
21040
- if (level === 1) return PARA_H1;
21041
- if (level === 2) return PARA_H2;
21042
- if (level === 3) return PARA_H3;
21043
- return PARA_H4;
21138
+ return result;
21044
21139
  }
21045
- function headingCharPrId(level) {
21046
- if (level === 1) return CHAR_H1;
21047
- if (level === 2) return CHAR_H2;
21048
- if (level === 3) return CHAR_H3;
21049
- return CHAR_H4;
21140
+ function normForMatch(text) {
21141
+ return sanitizeText(text).replace(/\s+/g, " ").trim();
21050
21142
  }
21051
- function generateContainerXml() {
21052
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21053
- <ocf:container xmlns:ocf="${NS_OCF}" xmlns:hpf="${NS_HPF}">
21054
- <ocf:rootfiles>
21055
- <ocf:rootfile full-path="Contents/content.hpf" media-type="application/hwpml-package+xml"/>
21056
- </ocf:rootfiles>
21057
- </ocf:container>`;
21143
+ function unescapeGfm(text) {
21144
+ return text.replace(/\\~/g, "~");
21058
21145
  }
21059
- function generateManifest() {
21060
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21061
- <opf:package xmlns:opf="${NS_OPF}" xmlns:hpf="${NS_HPF}" xmlns:hh="${NS_HEAD}">
21062
- <opf:manifest>
21063
- <opf:item id="header" href="Contents/header.xml" media-type="application/xml"/>
21064
- <opf:item id="section0" href="Contents/section0.xml" media-type="application/xml"/>
21065
- </opf:manifest>
21066
- <opf:spine>
21067
- <opf:itemref idref="header" linear="no"/>
21068
- <opf:itemref idref="section0" linear="yes"/>
21069
- </opf:spine>
21070
- </opf:package>`;
21146
+ function summarize(text) {
21147
+ const t = text.replace(/\s+/g, " ").trim();
21148
+ return t.length > 80 ? t.slice(0, 77) + "..." : t;
21071
21149
  }
21072
- function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_COLOR, ratioPct = 100) {
21073
- const boldAttr = bold ? ` bold="1"` : "";
21074
- const italicAttr = italic ? ` italic="1"` : "";
21075
- const effFont = bold ? 2 : fontId;
21076
- return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
21077
- <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
21078
- <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
21079
- <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
21080
- <hh:relSz hangul="100" latin="100" hanja="100" japanese="100" other="100" symbol="100" user="100"/>
21081
- <hh:offset hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
21082
- </hh:charPr>`;
21150
+ function replicateGfmTable(table) {
21151
+ const { cells, rows: numRows, cols: numCols } = table;
21152
+ if (numRows === 0 || numCols === 0) return null;
21153
+ if (numRows === 1 && numCols === 1) return null;
21154
+ if (numCols === 1) return null;
21155
+ const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
21156
+ const skip = /* @__PURE__ */ new Set();
21157
+ for (let r = 0; r < numRows; r++) {
21158
+ for (let c = 0; c < numCols; c++) {
21159
+ if (skip.has(`${r},${c}`)) continue;
21160
+ const cell = cells[r]?.[c];
21161
+ if (!cell) continue;
21162
+ display[r][c] = {
21163
+ text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
21164
+ gridR: r,
21165
+ gridC: c
21166
+ };
21167
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
21168
+ for (let dc = 0; dc < cell.colSpan; dc++) {
21169
+ if (dr === 0 && dc === 0) continue;
21170
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
21171
+ }
21172
+ }
21173
+ c += cell.colSpan - 1;
21174
+ }
21175
+ }
21176
+ const uniqueRows = [];
21177
+ let pendingLabelRow = null;
21178
+ for (let r = 0; r < display.length; r++) {
21179
+ const row = display[r];
21180
+ if (row.every((cell) => cell.text === "")) continue;
21181
+ const nonEmptyCols = row.filter((cell) => cell.text !== "");
21182
+ const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
21183
+ if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
21184
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
21185
+ pendingLabelRow = row;
21186
+ continue;
21187
+ }
21188
+ if (pendingLabelRow) {
21189
+ if (row[0].text === "") row[0] = pendingLabelRow[0];
21190
+ else uniqueRows.push(pendingLabelRow);
21191
+ pendingLabelRow = null;
21192
+ }
21193
+ uniqueRows.push(row);
21194
+ }
21195
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
21196
+ return uniqueRows.length > 0 ? uniqueRows : null;
21083
21197
  }
21084
- function paraPr(id, opts = {}) {
21085
- const { align = "JUSTIFY", spaceBefore = 0, spaceAfter = 0, lineSpacing = 160, indent = 0, left = 0, keepWord = false } = opts;
21086
- const breakNonLatin = keepWord ? "KEEP_WORD" : "BREAK_WORD";
21087
- const snapGrid = keepWord ? "0" : "1";
21088
- return ` <hh:paraPr id="${id}" tabPrIDRef="0" condense="0" fontLineHeight="0" snapToGrid="${snapGrid}" suppressLineNumbers="0" checked="0" textDir="AUTO">
21089
- <hh:align horizontal="${align}" vertical="BASELINE"/>
21090
- <hh:heading type="NONE" idRef="0" level="0"/>
21091
- <hh:breakSetting breakLatinWord="KEEP_WORD" breakNonLatinWord="${breakNonLatin}" widowOrphan="0" keepWithNext="0" keepLines="0" pageBreakBefore="0" lineWrap="BREAK"/>
21092
- <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
21093
- <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>
21094
- <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
21095
- <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
21096
- </hh:paraPr>`;
21198
+ function parseGfmTable(lines) {
21199
+ const rows = [];
21200
+ for (const line of lines) {
21201
+ const trimmed = line.trim();
21202
+ if (!trimmed.startsWith("|")) continue;
21203
+ const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
21204
+ if (cells.length === 0) continue;
21205
+ if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
21206
+ rows.push(cells);
21207
+ }
21208
+ return rows;
21097
21209
  }
21098
- var GONGMUN_LIST_BASE = 8;
21099
- var GONGMUN_LIST_LEVELS = 8;
21100
- var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
21101
- var CHAR_VARIANT_BASE = 11;
21102
- var GONGMUN_BODY_RATIO = 95;
21103
- function plainRenderText(text) {
21104
- return parseInlineMarkdown(text).map((s) => s.text).join("");
21210
+ function unescapeGfmCell(text) {
21211
+ return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
21105
21212
  }
21106
- function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
21107
- const minRatio = gongmun.autoFitMinRatio;
21108
- if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
21109
- const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
21110
- const ratioByBlock = /* @__PURE__ */ new Map();
21111
- const variants = [];
21112
- for (let i = 0; i < blocks.length; i++) {
21113
- const block = blocks[i];
21114
- let text;
21115
- let firstW;
21116
- let contW;
21117
- if (block.type === "list_item" && gongmunList.has(i)) {
21118
- const { marker, depth } = gongmunList.get(i);
21119
- const content = plainRenderText(block.text || "");
21120
- text = marker ? `${marker} ${content}` : content;
21121
- const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
21122
- firstW = pageW - left - Math.max(indent, 0);
21123
- contW = pageW - left - Math.max(-indent, 0);
21124
- } else if (block.type === "paragraph") {
21125
- const raw = (block.text || "").trim();
21126
- if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
21127
- text = plainRenderText(raw);
21128
- firstW = contW = pageW;
21129
- } else {
21130
- continue;
21131
- }
21132
- if (!text) continue;
21133
- const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
21134
- if (r === null) continue;
21135
- ratioByBlock.set(i, r);
21136
- if (!variants.includes(r)) variants.push(r);
21213
+ function replicateCellInnerHtml(cell) {
21214
+ if (cell.blocks?.length) {
21215
+ return cell.blocks.map((b) => {
21216
+ if (b.type === "table" && b.table) {
21217
+ const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
21218
+ return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
21219
+ }
21220
+ if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
21221
+ const t = sanitizeText(b.text ?? "");
21222
+ return t ? t.replace(/\n/g, "<br>") : "";
21223
+ }).filter(Boolean).join("<br>");
21137
21224
  }
21138
- return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
21225
+ return sanitizeText(cell.text).replace(/\n/g, "<br>");
21139
21226
  }
21140
- function variantMapper(fit, blockIdx) {
21141
- const r = fit.ratioByBlock.get(blockIdx);
21142
- if (r === void 0) return void 0;
21143
- const vi = fit.variants.indexOf(r);
21144
- return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
21227
+ function replicateTableToHtml(table) {
21228
+ const rows = replicateHtmlTable(table);
21229
+ const lines = ["<table>"];
21230
+ for (let r = 0; r < rows.length; r++) {
21231
+ const tag = rows[r].tag;
21232
+ const rowHtml = rows[r].cells.map((cell) => {
21233
+ const attrs = [];
21234
+ if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
21235
+ if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
21236
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
21237
+ return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
21238
+ });
21239
+ if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
21240
+ }
21241
+ lines.push("</table>");
21242
+ return lines.join("\n");
21145
21243
  }
21146
- function buildCharProperties(theme, gongmun, ratioVariants = []) {
21147
- let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
21148
- if (gongmun) {
21149
- body = gongmun.bodyHeight;
21150
- code = Math.max(body - 200, 900);
21151
- h1 = gongmun.preset === "report" || gongmun.preset === "plan" ? 2e3 : 1700;
21152
- h2 = 1600;
21153
- h3 = body;
21154
- h4 = Math.max(body - 100, 1300);
21244
+ function replicateHtmlTable(table) {
21245
+ const { cells, rows: numRows, cols: numCols } = table;
21246
+ const skip = /* @__PURE__ */ new Set();
21247
+ const result = [];
21248
+ for (let r = 0; r < numRows; r++) {
21249
+ const tag = r === 0 ? "th" : "td";
21250
+ const rowCells = [];
21251
+ for (let c = 0; c < numCols; c++) {
21252
+ if (skip.has(`${r},${c}`)) continue;
21253
+ const cell = cells[r]?.[c];
21254
+ if (!cell) continue;
21255
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
21256
+ for (let dc = 0; dc < cell.colSpan; dc++) {
21257
+ if (dr === 0 && dc === 0) continue;
21258
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
21259
+ }
21260
+ }
21261
+ rowCells.push({
21262
+ inner: replicateCellInnerHtml(cell),
21263
+ colSpan: cell.colSpan,
21264
+ rowSpan: cell.rowSpan,
21265
+ gridR: r,
21266
+ gridC: c
21267
+ });
21268
+ }
21269
+ if (rowCells.length) result.push({ tag, cells: rowCells });
21155
21270
  }
21156
- const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
21157
- const rows = [
21158
- charPr(0, body, false, false, 0, theme.body, bodyRatio),
21159
- charPr(1, body, true, false, 0, theme.body, bodyRatio),
21160
- charPr(2, body, false, true, 0, theme.body, bodyRatio),
21161
- charPr(3, body, true, true, 0, theme.body, bodyRatio),
21162
- charPr(4, code, false, false, 1),
21163
- charPr(5, h1, true, false, 1, theme.h1),
21164
- charPr(6, h2, true, false, 1, theme.h2),
21165
- charPr(7, h3, true, false, 1, theme.h3),
21166
- charPr(8, h4, true, false, 1, theme.h4),
21167
- charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
21168
- charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
21169
- ];
21170
- for (const r of ratioVariants) {
21171
- rows.push(
21172
- charPr(rows.length, body, false, false, 0, theme.body, r),
21173
- charPr(rows.length + 1, body, true, false, 0, theme.body, r),
21174
- charPr(rows.length + 2, body, false, true, 0, theme.body, r),
21175
- charPr(rows.length + 3, body, true, true, 0, theme.body, r)
21176
- );
21271
+ return result;
21272
+ }
21273
+ function parseHtmlTable(raw) {
21274
+ const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
21275
+ let depth = 0;
21276
+ let currentRow = null;
21277
+ let cellStart = -1;
21278
+ let cellInfo = null;
21279
+ const rows = [];
21280
+ let m;
21281
+ while ((m = re.exec(raw)) !== null) {
21282
+ const isClose = m[1] === "/";
21283
+ const tag = m[2].toLowerCase();
21284
+ const attrs = m[3] || "";
21285
+ if (tag === "table") {
21286
+ depth += isClose ? -1 : 1;
21287
+ if (depth < 0) return null;
21288
+ continue;
21289
+ }
21290
+ if (depth !== 1) continue;
21291
+ if (tag === "tr") {
21292
+ if (!isClose) currentRow = [];
21293
+ else if (currentRow) {
21294
+ rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
21295
+ currentRow = null;
21296
+ }
21297
+ } else {
21298
+ if (!isClose) {
21299
+ const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
21300
+ const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
21301
+ cellStart = m.index + m[0].length;
21302
+ cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
21303
+ } else if (cellStart >= 0 && cellInfo && currentRow) {
21304
+ currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
21305
+ cellStart = -1;
21306
+ cellInfo = null;
21307
+ }
21308
+ }
21177
21309
  }
21178
- return `<hh:charProperties itemCnt="${rows.length}">
21179
- ${rows.join("\n")}
21180
- </hh:charProperties>`;
21310
+ if (depth !== 0) return null;
21311
+ return rows;
21181
21312
  }
21182
- function buildParaProperties(gongmun) {
21183
- if (!gongmun) {
21184
- const base2 = [
21185
- paraPr(0),
21186
- paraPr(1, { align: "LEFT", spaceBefore: 800, spaceAfter: 200, lineSpacing: 180 }),
21187
- paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: 170 }),
21188
- paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: 160 }),
21189
- paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: 160 }),
21190
- paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400 }),
21191
- paraPr(6, { align: "LEFT", lineSpacing: 150, indent: 600 }),
21192
- paraPr(7, { align: "LEFT", lineSpacing: 160, indent: 600 })
21193
- ];
21194
- return `<hh:paraProperties itemCnt="${base2.length}">
21195
- ${base2.join("\n")}
21196
- </hh:paraProperties>`;
21313
+ var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
21314
+ function htmlCellInnerToLines(inner) {
21315
+ let hadNonText = false;
21316
+ let work = inner;
21317
+ if (/<table[\s>]/i.test(work)) {
21318
+ hadNonText = true;
21319
+ work = removeNestedTables(work);
21197
21320
  }
21198
- const ls = gongmun.lineSpacing;
21199
- const titleAlign = gongmun.centerTitle ? "CENTER" : "LEFT";
21200
- const base = [
21201
- paraPr(0, { lineSpacing: ls, keepWord: true }),
21202
- paraPr(1, { align: titleAlign, spaceBefore: 400, spaceAfter: 400, lineSpacing: ls, keepWord: true }),
21203
- paraPr(2, { align: "LEFT", spaceBefore: 600, spaceAfter: 150, lineSpacing: ls, keepWord: true }),
21204
- paraPr(3, { align: "LEFT", spaceBefore: 400, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
21205
- paraPr(4, { align: "LEFT", spaceBefore: 300, spaceAfter: 100, lineSpacing: ls, keepWord: true }),
21206
- paraPr(5, { align: "LEFT", lineSpacing: 130, indent: 400, keepWord: true }),
21207
- paraPr(6, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true }),
21208
- paraPr(7, { align: "LEFT", lineSpacing: ls, indent: 600, keepWord: true })
21209
- ];
21210
- for (let d = 0; d < GONGMUN_LIST_LEVELS; d++) {
21211
- const { left, indent } = levelIndent(d, gongmun.bodyHeight, gongmun.numbering);
21212
- const sectionGap = gongmun.numbering === "report" && d === 0 ? Math.round(gongmun.bodyHeight * 0.5) : 0;
21213
- base.push(paraPr(GONGMUN_LIST_BASE + d, { align: "JUSTIFY", lineSpacing: ls, left, indent, spaceBefore: sectionGap, keepWord: true }));
21321
+ if (/<img\s/i.test(work)) {
21322
+ hadNonText = true;
21323
+ work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
21214
21324
  }
21215
- base.push(paraPr(GONGMUN_CENTER, { align: "CENTER", lineSpacing: ls, keepWord: true }));
21216
- return `<hh:paraProperties itemCnt="${base.length}">
21217
- ${base.join("\n")}
21218
- </hh:paraProperties>`;
21325
+ const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
21326
+ return { lines, hadNonText };
21219
21327
  }
21220
- function generateHeaderXml(theme, gongmun, ratioVariants = []) {
21221
- const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
21222
- const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
21223
- const paraPropsXml = buildParaProperties(gongmun);
21224
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21225
- <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
21226
- <hh:beginNum page="1" footnote="1" endnote="1" pic="1" tbl="1" equation="1"/>
21227
- <hh:refList>
21228
- <hh:fontfaces itemCnt="7">
21229
- <hh:fontface lang="HANGUL" fontCnt="3">
21230
- <hh:font id="0" face="${bodyFace}" type="TTF" isEmbedded="0">
21231
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21232
- </hh:font>
21233
- <hh:font id="1" face="\uD568\uCD08\uB86C\uB3CB\uC6C0" type="TTF" isEmbedded="0">
21234
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21235
- </hh:font>
21236
- <hh:font id="2" face="HY\uACAC\uACE0\uB515" type="TTF" isEmbedded="0">
21237
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21238
- </hh:font>
21239
- </hh:fontface>
21240
- <hh:fontface lang="LATIN" fontCnt="3">
21241
- <hh:font id="0" face="Times New Roman" type="TTF" isEmbedded="0">
21242
- <hh:typeInfo familyType="FCAT_OLDSTYLE" weight="5" proportion="4" contrast="2" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="4"/>
21243
- </hh:font>
21244
- <hh:font id="1" face="Consolas" type="TTF" isEmbedded="0">
21245
- <hh:typeInfo familyType="FCAT_MODERN" weight="5" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
21246
- </hh:font>
21247
- <hh:font id="2" face="Arial Black" type="TTF" isEmbedded="0">
21248
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="9" proportion="0" contrast="0" strokeVariation="0" armStyle="0" letterform="0" midline="0" xHeight="0"/>
21249
- </hh:font>
21250
- </hh:fontface>
21251
- <hh:fontface lang="HANJA" fontCnt="1">
21252
- <hh:font id="0" face="\uD568\uCD08\uB86C\uBC14\uD0D5" type="TTF" isEmbedded="0">
21253
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="4" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21254
- </hh:font>
21255
- </hh:fontface>
21256
- <hh:fontface lang="JAPANESE" fontCnt="1">
21257
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21258
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21259
- </hh:font>
21260
- </hh:fontface>
21261
- <hh:fontface lang="OTHER" fontCnt="1">
21262
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21263
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21264
- </hh:font>
21265
- </hh:fontface>
21266
- <hh:fontface lang="SYMBOL" fontCnt="1">
21267
- <hh:font id="0" face="Symbol" type="TTF" isEmbedded="0">
21268
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21269
- </hh:font>
21270
- </hh:fontface>
21271
- <hh:fontface lang="USER" fontCnt="1">
21272
- <hh:font id="0" face="\uAD74\uB9BC" type="TTF" isEmbedded="0">
21273
- <hh:typeInfo familyType="FCAT_GOTHIC" weight="6" proportion="0" contrast="0" strokeVariation="1" armStyle="1" letterform="1" midline="1" xHeight="1"/>
21274
- </hh:font>
21275
- </hh:fontface>
21276
- </hh:fontfaces>
21277
- <hh:borderFills itemCnt="2">
21278
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
21279
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
21280
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
21281
- <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
21282
- <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
21283
- <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
21284
- <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
21285
- </hh:borderFill>
21286
- <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
21287
- <hh:slash type="NONE" Crooked="0" isCounter="0"/>
21288
- <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
21289
- <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
21290
- <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
21291
- <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
21292
- <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
21293
- </hh:borderFill>
21294
- </hh:borderFills>
21295
- ${charPropsXml}
21296
- <hh:tabProperties itemCnt="0"/>
21297
- <hh:numberings itemCnt="0"/>
21298
- <hh:bullets itemCnt="0"/>
21299
- ${paraPropsXml}
21300
- <hh:styles itemCnt="1">
21301
- <hh:style id="0" type="PARA" name="\uBC14\uD0D5\uAE00" engName="Normal" paraPrIDRef="0" charPrIDRef="0" nextStyleIDRef="0" langIDRef="1042" lockForm="0"/>
21302
- </hh:styles>
21303
- </hh:refList>
21304
- <hh:compatibleDocument targetProgram="HWP2018"><hh:layoutCompatibility/></hh:compatibleDocument>
21305
- </hh:head>`;
21328
+ function extractTopLevelTables(html) {
21329
+ const result = [];
21330
+ let depth = 0;
21331
+ let start = -1;
21332
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
21333
+ let m;
21334
+ while ((m = re.exec(html)) !== null) {
21335
+ if (m[1] !== "/") {
21336
+ if (depth === 0) start = m.index;
21337
+ depth++;
21338
+ } else {
21339
+ depth--;
21340
+ if (depth === 0 && start >= 0) {
21341
+ result.push(html.slice(start, m.index + m[0].length));
21342
+ start = -1;
21343
+ }
21344
+ if (depth < 0) depth = 0;
21345
+ }
21346
+ }
21347
+ return result;
21306
21348
  }
21307
- function generateSecPr(gongmun) {
21308
- const m = gongmun ? {
21309
- top: mmToHwpunit(gongmun.margins.top),
21310
- bottom: mmToHwpunit(gongmun.margins.bottom),
21311
- left: mmToHwpunit(gongmun.margins.left),
21312
- right: mmToHwpunit(gongmun.margins.right),
21313
- header: 0,
21314
- footer: 0
21315
- } : { top: 8504, bottom: 4252, left: 5670, right: 4252, header: 2835, footer: 2835 };
21316
- 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>`;
21349
+ function removeNestedTables(html) {
21350
+ let result = "";
21351
+ let depth = 0;
21352
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
21353
+ let last = 0;
21354
+ let m;
21355
+ while ((m = re.exec(html)) !== null) {
21356
+ if (m[1] !== "/") {
21357
+ if (depth === 0) result += html.slice(last, m.index);
21358
+ depth++;
21359
+ } else {
21360
+ depth--;
21361
+ if (depth === 0) last = m.index + m[0].length;
21362
+ if (depth < 0) depth = 0;
21363
+ }
21364
+ }
21365
+ if (depth === 0) result += html.slice(last);
21366
+ return result;
21317
21367
  }
21368
+
21369
+ // src/hwpx/gen-table.ts
21318
21370
  var TABLE_ID_BASE = 1e3;
21319
21371
  var tableIdCounter = TABLE_ID_BASE;
21320
21372
  function nextTableId() {
@@ -21404,41 +21456,18 @@ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3) {
21404
21456
  }
21405
21457
  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>`;
21406
21458
  }
21407
- function precomputeGongmunList(blocks, gongmun) {
21408
- const result = /* @__PURE__ */ new Map();
21409
- let i = 0;
21410
- while (i < blocks.length) {
21411
- if (blocks[i].type !== "list_item") {
21412
- i++;
21413
- continue;
21414
- }
21415
- const run = [];
21416
- while (i < blocks.length) {
21417
- const t = blocks[i].type;
21418
- if (t === "list_item") {
21419
- run.push(i);
21420
- i++;
21421
- continue;
21422
- }
21423
- if (t === "table" || t === "html_table") {
21424
- let j = i + 1;
21425
- while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
21426
- if (j < blocks.length && blocks[j].type === "list_item") {
21427
- i = j;
21428
- continue;
21429
- }
21430
- }
21431
- break;
21432
- }
21433
- const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
21434
- const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
21435
- const numberer = new GongmunNumberer(gongmun.numbering);
21436
- run.forEach((bi, k) => {
21437
- const marker = numberer.next(depths[k], suppress[k]);
21438
- result.set(bi, { marker, depth: depths[k] });
21439
- });
21440
- }
21441
- return result;
21459
+
21460
+ // src/hwpx/gen-section.ts
21461
+ function generateSecPr(gongmun) {
21462
+ const m = gongmun ? {
21463
+ top: mmToHwpunit(gongmun.margins.top),
21464
+ bottom: mmToHwpunit(gongmun.margins.bottom),
21465
+ left: mmToHwpunit(gongmun.margins.left),
21466
+ right: mmToHwpunit(gongmun.margins.right),
21467
+ header: 0,
21468
+ footer: 0
21469
+ } : { top: 8504, bottom: 4252, left: 5670, right: 4252, header: 2835, footer: 2835 };
21470
+ 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>`;
21442
21471
  }
21443
21472
  function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null) {
21444
21473
  const paraXmls = [];
@@ -21561,6 +21590,24 @@ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? prec
21561
21590
  </hs:sec>`;
21562
21591
  }
21563
21592
 
21593
+ // src/hwpx/generator.ts
21594
+ async function markdownToHwpx(markdown, options) {
21595
+ const theme = resolveTheme(options?.theme);
21596
+ const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
21597
+ const blocks = parseMarkdownToBlocks(markdown);
21598
+ const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
21599
+ const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
21600
+ const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
21601
+ const zip = new JSZip6();
21602
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
21603
+ zip.file("META-INF/container.xml", generateContainerXml());
21604
+ zip.file("Contents/content.hpf", generateManifest());
21605
+ zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
21606
+ zip.file("Contents/section0.xml", sectionXml);
21607
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
21608
+ return await zip.generateAsync({ type: "arraybuffer" });
21609
+ }
21610
+
21564
21611
  // src/diff/compare.ts
21565
21612
  var SIMILARITY_THRESHOLD = 0.4;
21566
21613
  async function compare(bufferA, bufferB, options) {
@@ -24285,7 +24332,7 @@ async function parseHwp(buffer, options) {
24285
24332
  async function parsePdf(buffer, options) {
24286
24333
  let parsePdfDocument;
24287
24334
  try {
24288
- const mod = await import("./parser-FFEBMLSH.js");
24335
+ const mod = await import("./parser-6GP535ZB.js");
24289
24336
  parsePdfDocument = mod.parsePdfDocument;
24290
24337
  } catch {
24291
24338
  return {
@@ -24420,4 +24467,4 @@ export {
24420
24467
  parseHwpml,
24421
24468
  fillForm
24422
24469
  };
24423
- //# sourceMappingURL=chunk-KT2BCHXI.js.map
24470
+ //# sourceMappingURL=chunk-B27QMVL7.js.map