kordoc 3.5.3 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +16 -1
  2. package/dist/{-N2SJBI4R.js → -7UC4ZWBS.js} +21 -5
  3. package/dist/chunk-DCZVOIEO.cjs.map +1 -1
  4. package/dist/chunk-GS7T56RP.cjs.map +1 -1
  5. package/dist/{chunk-3Z2RAKIL.js → chunk-IJJMVTU5.js} +2 -2
  6. package/dist/chunk-IJJMVTU5.js.map +1 -0
  7. package/dist/{chunk-JOLDAN3Q.js → chunk-MEVKYW55.js} +2 -2
  8. package/dist/chunk-MEVKYW55.js.map +1 -0
  9. package/dist/{chunk-GUDFEDFK.js → chunk-NXRABCWW.js} +1588 -695
  10. package/dist/chunk-NXRABCWW.js.map +1 -0
  11. package/dist/{chunk-53XNKPN6.cjs → chunk-QZCP3UWU.cjs} +2 -2
  12. package/dist/chunk-QZCP3UWU.cjs.map +1 -0
  13. package/dist/cli.js +4 -4
  14. package/dist/formula-5NKVS2LR.cjs.map +1 -1
  15. package/dist/index.cjs +1787 -894
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +130 -8
  18. package/dist/index.d.ts +130 -8
  19. package/dist/index.js +1588 -695
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp.js +3 -3
  22. package/dist/page-range-P7SDW6LR.cjs.map +1 -1
  23. package/dist/{parser-IAROZTY3.js → parser-DR5CTZ74.js} +2 -2
  24. package/dist/{parser-CK5C6FF4.cjs → parser-RFLPUZ7P.cjs} +14 -14
  25. package/dist/parser-RFLPUZ7P.cjs.map +1 -0
  26. package/dist/{parser-CPFIB47T.js → parser-ZHJFQR44.js} +2 -2
  27. package/dist/provider-G4C2V2PD.cjs.map +1 -1
  28. package/dist/{watch-ZYNP3QFT.js → watch-UIX447QV.js} +3 -3
  29. package/package.json +1 -1
  30. package/dist/chunk-3Z2RAKIL.js.map +0 -1
  31. package/dist/chunk-53XNKPN6.cjs.map +0 -1
  32. package/dist/chunk-GUDFEDFK.js.map +0 -1
  33. package/dist/chunk-JOLDAN3Q.js.map +0 -1
  34. package/dist/parser-CK5C6FF4.cjs.map +0 -1
  35. /package/dist/{-N2SJBI4R.js.map → -7UC4ZWBS.js.map} +0 -0
  36. /package/dist/{parser-IAROZTY3.js.map → parser-DR5CTZ74.js.map} +0 -0
  37. /package/dist/{parser-CPFIB47T.js.map → parser-ZHJFQR44.js.map} +0 -0
  38. /package/dist/{watch-ZYNP3QFT.js.map → watch-UIX447QV.js.map} +0 -0
@@ -25,7 +25,7 @@ import {
25
25
  sanitizeHref,
26
26
  stripDtd,
27
27
  toArrayBuffer
28
- } from "./chunk-3Z2RAKIL.js";
28
+ } from "./chunk-IJJMVTU5.js";
29
29
  import {
30
30
  parsePageRange
31
31
  } from "./chunk-MOL7MDBG.js";
@@ -1218,9 +1218,32 @@ function detectHwpxHeadings(blocks, styleMap) {
1218
1218
  function buildTableWithCellMeta(state) {
1219
1219
  const table = buildTable(state.rows);
1220
1220
  if (state.caption) table.caption = state.caption;
1221
+ const anchors = [];
1222
+ {
1223
+ const covered = /* @__PURE__ */ new Set();
1224
+ for (let r = 0; r < table.rows; r++) {
1225
+ for (let c = 0; c < table.cols; c++) {
1226
+ if (covered.has(`${r},${c}`)) continue;
1227
+ const cell = table.cells[r]?.[c];
1228
+ if (!cell) continue;
1229
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
1230
+ for (let dc = 0; dc < cell.colSpan; dc++) {
1231
+ if (dr === 0 && dc === 0) continue;
1232
+ if (r + dr < table.rows && c + dc < table.cols) covered.add(`${r + dr},${c + dc}`);
1233
+ }
1234
+ }
1235
+ anchors.push(cell);
1236
+ c += cell.colSpan - 1;
1237
+ }
1238
+ }
1239
+ }
1240
+ const srcCount = state.rows.reduce((s, r) => s + r.length, 0);
1241
+ const ordinalReliable = anchors.length === srcCount;
1221
1242
  const claimed = /* @__PURE__ */ new Set();
1243
+ let flatIdx = -1;
1222
1244
  for (const row of state.rows) {
1223
1245
  for (const src of row) {
1246
+ flatIdx++;
1224
1247
  const needsBlocks = src.hasStructure && src.blocks && src.blocks.length > 0;
1225
1248
  if (!needsBlocks && !src.isHeader) continue;
1226
1249
  let target;
@@ -1239,6 +1262,10 @@ function buildTableWithCellMeta(state) {
1239
1262
  }
1240
1263
  }
1241
1264
  }
1265
+ if (!target && ordinalReliable) {
1266
+ const cand = anchors[flatIdx];
1267
+ if (cand && !claimed.has(cand)) target = cand;
1268
+ }
1242
1269
  if (!target) continue;
1243
1270
  claimed.add(target);
1244
1271
  if (needsBlocks) target.blocks = src.blocks;
@@ -18476,6 +18503,45 @@ function countSections(body) {
18476
18503
  }
18477
18504
 
18478
18505
  // src/form/match.ts
18506
+ var ValueCursor = class {
18507
+ constructor(values) {
18508
+ this.values = values;
18509
+ }
18510
+ nextIdx = /* @__PURE__ */ new Map();
18511
+ keys() {
18512
+ return this.values.keys();
18513
+ }
18514
+ has(key) {
18515
+ return this.values.has(key);
18516
+ }
18517
+ isArray(key) {
18518
+ return Array.isArray(this.values.get(key));
18519
+ }
18520
+ /** 남은 값이 있으면 true (스칼라는 항상 true) */
18521
+ available(key) {
18522
+ const v = this.values.get(key);
18523
+ if (v === void 0) return false;
18524
+ return typeof v === "string" || (this.nextIdx.get(key) ?? 0) < v.length;
18525
+ }
18526
+ /** 현재 값 미리보기 (소진 없음) */
18527
+ peek(key) {
18528
+ const v = this.values.get(key);
18529
+ if (v === void 0) return void 0;
18530
+ if (typeof v === "string") return v;
18531
+ const i = this.nextIdx.get(key) ?? 0;
18532
+ return i < v.length ? v[i] : void 0;
18533
+ }
18534
+ /** 값 소비 — 배열이면 커서 전진, 소진 시 undefined */
18535
+ consume(key) {
18536
+ const v = this.values.get(key);
18537
+ if (v === void 0) return void 0;
18538
+ if (typeof v === "string") return v;
18539
+ const i = this.nextIdx.get(key) ?? 0;
18540
+ if (i >= v.length) return void 0;
18541
+ this.nextIdx.set(key, i + 1);
18542
+ return v[i];
18543
+ }
18544
+ };
18479
18545
  function normalizeLabel(label) {
18480
18546
  return label.trim().replace(/[::\s()()·]/g, "");
18481
18547
  }
@@ -18514,9 +18580,9 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18514
18580
  (match, prefix, suffix) => {
18515
18581
  const label = prefix + suffix;
18516
18582
  const normalizedLabel = normalizeLabel(label);
18517
- const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18583
+ const matchKey = values.available(normalizedLabel) ? normalizedLabel : values.available(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18518
18584
  if (matchKey === void 0) return match;
18519
- const newValue = values.get(matchKey);
18585
+ const newValue = values.consume(matchKey);
18520
18586
  matchedLabels.add(matchKey);
18521
18587
  matches.push({ key: matchKey, label, value: newValue });
18522
18588
  return `${prefix}(${newValue})${suffix}`;
@@ -18526,11 +18592,12 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18526
18592
  /□([가-힣A-Za-z]+)/g,
18527
18593
  (match, keyword) => {
18528
18594
  const normalizedKw = normalizeLabel(keyword);
18529
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18595
+ const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
18530
18596
  if (matchKey === void 0) return match;
18531
- const val = values.get(matchKey);
18597
+ const val = values.peek(matchKey);
18532
18598
  const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18533
18599
  if (!isTruthy) return match;
18600
+ values.consume(matchKey);
18534
18601
  matchedLabels.add(matchKey);
18535
18602
  matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18536
18603
  return `\u2611${keyword}`;
@@ -18540,9 +18607,9 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18540
18607
  /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18541
18608
  (match, keyword) => {
18542
18609
  const normalizedKw = normalizeLabel(keyword);
18543
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18610
+ const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
18544
18611
  if (matchKey === void 0) return match;
18545
- const newValue = values.get(matchKey);
18612
+ const newValue = values.consume(matchKey);
18546
18613
  matchedLabels.add(matchKey);
18547
18614
  matches.push({ key: matchKey, label: keyword, value: newValue });
18548
18615
  return `(${keyword}\uFF1A${newValue})`;
@@ -18648,14 +18715,66 @@ var LABEL_KEYWORDS = /* @__PURE__ */ new Set([
18648
18715
  "\uCCAD\uAD6C\uC0AC\uC720",
18649
18716
  "\uC18C\uBA85\uC790\uB8CC"
18650
18717
  ]);
18718
+ var ENGLISH_LABEL_WORDS = /* @__PURE__ */ new Set([
18719
+ "name",
18720
+ "date",
18721
+ "address",
18722
+ "tel",
18723
+ "phone",
18724
+ "mobile",
18725
+ "fax",
18726
+ "email",
18727
+ "e-mail",
18728
+ "dept",
18729
+ "department",
18730
+ "division",
18731
+ "title",
18732
+ "position",
18733
+ "grade",
18734
+ "rank",
18735
+ "birth",
18736
+ "nationality",
18737
+ "sex",
18738
+ "gender",
18739
+ "signature",
18740
+ "sign",
18741
+ "seal",
18742
+ "remarks",
18743
+ "note",
18744
+ "period",
18745
+ "place",
18746
+ "purpose",
18747
+ "reason",
18748
+ "amount",
18749
+ "total",
18750
+ "sum",
18751
+ "qty",
18752
+ "quantity",
18753
+ "unit",
18754
+ "no",
18755
+ "id",
18756
+ "passport"
18757
+ ]);
18758
+ var ENGLISH_STOPWORDS = /* @__PURE__ */ new Set(["of", "the", "and", "or", "in"]);
18759
+ var NUMERIC_VALUE_RE = /^제?\d+(?:[.,]\d+)*[십백천만억조]*(?:원|명|건|개|회|부|매|장|점|호|번|년|월|일|시|분|초|개월|주년|차례|퍼센트)?$/;
18760
+ var SENTENCE_ENDING_RE = /(?:입니다|합니다|습니다|하세요|십시오|시오|바랍니다|바람|할 것|할것|하며|하고|한다|된다|됨|음|임)$/;
18651
18761
  function isLabelCell(text) {
18652
18762
  const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18653
18763
  if (!trimmed || trimmed.length > 30) return false;
18654
18764
  for (const kw of LABEL_KEYWORDS) {
18655
18765
  if (trimmed.includes(kw)) return true;
18656
18766
  }
18657
- if (/^[가-힣\s()()·::]+$/.test(trimmed) && trimmed.replace(/\s/g, "").length >= 2 && trimmed.replace(/\s/g, "").length <= 8 && !/\d/.test(trimmed)) return true;
18767
+ const compact = trimmed.replace(/\s/g, "");
18768
+ if (/^[가-힣0-9()()·::\-]+$/.test(compact) && compact.length >= 2 && compact.length <= 12 && (compact.match(/[가-힣]/g) ?? []).length >= 2 && (compact.length <= 8 || trimmed.split(/\s+/).length <= 2) && !NUMERIC_VALUE_RE.test(compact) && !SENTENCE_ENDING_RE.test(trimmed) && !/^[((]주[))]|^주식회사/.test(compact)) {
18769
+ return true;
18770
+ }
18658
18771
  if (/^[가-힣A-Za-z\s]+[::]$/.test(trimmed)) return true;
18772
+ if (/^[A-Za-z][A-Za-z\s./&-]*$/.test(trimmed) && trimmed.length <= 20) {
18773
+ const words = trimmed.toLowerCase().split(/[\s/&]+/).filter((w) => w && !ENGLISH_STOPWORDS.has(w));
18774
+ if (words.length >= 1 && words.length <= 3 && words.every((w) => ENGLISH_LABEL_WORDS.has(w.replace(/\.$/, "")))) {
18775
+ return true;
18776
+ }
18777
+ }
18659
18778
  return false;
18660
18779
  }
18661
18780
  function extractFormFields(blocks) {
@@ -18796,17 +18915,18 @@ function fillFormFields(blocks, values) {
18796
18915
  const filled = [];
18797
18916
  const matchedLabels = /* @__PURE__ */ new Set();
18798
18917
  const normalizedValues = normalizeValues(values);
18918
+ const cursor = new ValueCursor(normalizedValues);
18919
+ const allTables = collectIRTables(cloned, 0);
18799
18920
  const patternFilledCells = /* @__PURE__ */ new Set();
18800
- for (const block of cloned) {
18801
- if (block.type !== "table" || !block.table) continue;
18802
- for (let r = 0; r < block.table.rows; r++) {
18803
- for (let c = 0; c < block.table.cols; c++) {
18804
- const cell = block.table.cells[r]?.[c];
18921
+ for (const table of allTables) {
18922
+ for (let r = 0; r < table.rows; r++) {
18923
+ for (let c = 0; c < table.cols; c++) {
18924
+ const cell = table.cells[r]?.[c];
18805
18925
  if (!cell) continue;
18806
- const result = fillInCellPatterns(cell.text, normalizedValues, matchedLabels);
18926
+ const result = fillInCellPatterns(cell.text, cursor, matchedLabels);
18807
18927
  if (result) {
18808
18928
  cell.text = result.text;
18809
- patternFilledCells.add(`${r},${c}`);
18929
+ patternFilledCells.add(cell);
18810
18930
  for (const m of result.matches) {
18811
18931
  filled.push({ label: m.label, value: m.value, row: r, col: c });
18812
18932
  }
@@ -18814,33 +18934,71 @@ function fillFormFields(blocks, values) {
18814
18934
  }
18815
18935
  }
18816
18936
  }
18817
- for (const block of cloned) {
18818
- if (block.type !== "table" || !block.table) continue;
18819
- fillTable(block.table, normalizedValues, filled, matchedLabels, patternFilledCells);
18937
+ for (const table of allTables) {
18938
+ fillTable(table, cursor, filled, matchedLabels, patternFilledCells);
18820
18939
  }
18821
18940
  for (const block of cloned) {
18822
18941
  if (block.type !== "paragraph" || !block.text) continue;
18823
- const newText = fillInlineFields(block.text, normalizedValues, filled, matchedLabels);
18942
+ const newText = fillInlineFields(block.text, cursor, filled, matchedLabels);
18824
18943
  if (newText !== block.text) block.text = newText;
18825
18944
  }
18826
18945
  const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
18827
18946
  return { blocks: cloned, filled, unmatched };
18828
18947
  }
18948
+ function collectIRTables(blocks, depth) {
18949
+ if (depth > 16) return [];
18950
+ const out = [];
18951
+ for (const block of blocks) {
18952
+ if (block.type !== "table" || !block.table) continue;
18953
+ out.push(block.table);
18954
+ for (const row of block.table.cells) {
18955
+ for (const cell of row) {
18956
+ if (cell?.blocks?.length) out.push(...collectIRTables(cell.blocks, depth + 1));
18957
+ }
18958
+ }
18959
+ }
18960
+ return out;
18961
+ }
18962
+ function coveredPositions(table) {
18963
+ const covered = /* @__PURE__ */ new Set();
18964
+ for (let r = 0; r < table.rows; r++) {
18965
+ for (let c = 0; c < table.cols; c++) {
18966
+ if (covered.has(`${r},${c}`)) continue;
18967
+ const cell = table.cells[r]?.[c];
18968
+ if (!cell) continue;
18969
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
18970
+ for (let dc = 0; dc < cell.colSpan; dc++) {
18971
+ if (dr === 0 && dc === 0) continue;
18972
+ if (r + dr < table.rows && c + dc < table.cols) covered.add(`${r + dr},${c + dc}`);
18973
+ }
18974
+ }
18975
+ c += cell.colSpan - 1;
18976
+ }
18977
+ }
18978
+ return covered;
18979
+ }
18829
18980
  function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
18830
18981
  if (table.cols < 2) return;
18982
+ const covered = coveredPositions(table);
18831
18983
  for (let r = 0; r < table.rows; r++) {
18832
- for (let c = 0; c < table.cols - 1; c++) {
18984
+ for (let c = 0; c < table.cols; c++) {
18985
+ if (covered.has(`${r},${c}`)) continue;
18833
18986
  const labelCell = table.cells[r][c];
18834
- const valueCell = table.cells[r][c + 1];
18835
- if (!labelCell || !valueCell) continue;
18987
+ if (!labelCell) continue;
18836
18988
  if (!isLabelCell(labelCell.text)) continue;
18989
+ let vc = c + labelCell.colSpan;
18990
+ while (vc < table.cols && covered.has(`${r},${vc}`)) vc++;
18991
+ if (vc >= table.cols) continue;
18992
+ const valueCell = table.cells[r][vc];
18993
+ if (!valueCell) continue;
18837
18994
  if (isKeywordLabel(valueCell.text)) continue;
18838
18995
  const normalizedCellLabel = normalizeLabel(labelCell.text);
18839
18996
  if (!normalizedCellLabel) continue;
18840
18997
  const matchKey = findMatchingKey(normalizedCellLabel, values);
18841
18998
  if (matchKey === void 0) continue;
18842
- const newValue = values.get(matchKey);
18843
- if (patternFilledCells?.has(`${r},${c + 1}`)) {
18999
+ const newValue = values.consume(matchKey);
19000
+ if (newValue === void 0) continue;
19001
+ if (patternFilledCells?.has(valueCell)) {
18844
19002
  valueCell.text = newValue + " " + valueCell.text;
18845
19003
  } else {
18846
19004
  valueCell.text = newValue;
@@ -18863,14 +19021,16 @@ function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
18863
19021
  if (!allLabels) return;
18864
19022
  for (let r = 1; r < table.rows; r++) {
18865
19023
  for (let c = 0; c < table.cols; c++) {
19024
+ if (covered.has(`${r},${c}`)) continue;
18866
19025
  const headerCell = headerRow[c];
18867
19026
  const valueCell = table.cells[r]?.[c];
18868
19027
  if (!headerCell || !valueCell) continue;
18869
19028
  const headerLabel = normalizeLabel(headerCell.text);
18870
19029
  const matchKey = findMatchingKey(headerLabel, values);
18871
19030
  if (matchKey === void 0) continue;
18872
- if (matchedLabels.has(matchKey)) continue;
18873
- const newValue = values.get(matchKey);
19031
+ if (!values.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
19032
+ const newValue = values.consume(matchKey);
19033
+ if (newValue === void 0) continue;
18874
19034
  valueCell.text = newValue;
18875
19035
  matchedLabels.add(matchKey);
18876
19036
  filled.push({
@@ -18891,7 +19051,8 @@ function fillInlineFields(text, values, filled, matchedLabels) {
18891
19051
  for (const seg of segments) {
18892
19052
  const matchKey = findMatchingKey(normalizeLabel(seg.label), values);
18893
19053
  if (matchKey === void 0) continue;
18894
- const newValue = values.get(matchKey);
19054
+ const newValue = values.consume(matchKey);
19055
+ if (newValue === void 0) continue;
18895
19056
  matchedLabels.add(matchKey);
18896
19057
  filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
18897
19058
  out += text.slice(pos, seg.valueStart);
@@ -18998,6 +19159,7 @@ function scanSectionXml(xml, sectionIndex) {
18998
19159
  const paraStack = [];
18999
19160
  const tableStack = [];
19000
19161
  const rowStack = [];
19162
+ const trStartStack = [];
19001
19163
  const cellStack = [];
19002
19164
  let pendingT = null;
19003
19165
  const ctrlSubStack = [];
@@ -19062,11 +19224,17 @@ function scanSectionXml(xml, sectionIndex) {
19062
19224
  } else if (local2 === "tr") {
19063
19225
  const row = rowStack[rowStack.length - 1];
19064
19226
  const table = tableStack[tableStack.length - 1];
19065
- if (row && table && row.length > 0) table.rows.push(row);
19227
+ if (row && table && row.length > 0) {
19228
+ table.rows.push(row);
19229
+ const trStart = trStartStack[trStartStack.length - 1];
19230
+ if (trStart >= 0) table.rowRanges.push({ start: trStart, end: m.index + full.length });
19231
+ }
19066
19232
  if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19233
+ if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = -1;
19067
19234
  } else if (local2 === "tbl") {
19068
19235
  const table = tableStack.pop();
19069
19236
  rowStack.pop();
19237
+ trStartStack.pop();
19070
19238
  if (table) {
19071
19239
  finalizeTable(table);
19072
19240
  if (!table.topLevel) {
@@ -19109,6 +19277,7 @@ function scanSectionXml(xml, sectionIndex) {
19109
19277
  const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19110
19278
  if (!isNaN(ca)) cell.colAddr = ca;
19111
19279
  if (!isNaN(ra)) cell.rowAddr = ra;
19280
+ cell.addrTagRange = { start: m.index, end: m.index + full.length };
19112
19281
  }
19113
19282
  } else if (local === "cellSpan") {
19114
19283
  const cell = cellStack[cellStack.length - 1];
@@ -19159,6 +19328,7 @@ function scanSectionXml(xml, sectionIndex) {
19159
19328
  start: m.index,
19160
19329
  topLevel: false,
19161
19330
  rows: [],
19331
+ rowRanges: [],
19162
19332
  cellByAnchor: /* @__PURE__ */ new Map()
19163
19333
  };
19164
19334
  stack.pop();
@@ -19166,9 +19336,11 @@ function scanSectionXml(xml, sectionIndex) {
19166
19336
  stack.push({ local, qname, contentStart });
19167
19337
  tableStack.push(table);
19168
19338
  rowStack.push([]);
19339
+ trStartStack.push(-1);
19169
19340
  if (table.topLevel) tables.push(table);
19170
19341
  } else if (local === "tr") {
19171
19342
  if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19343
+ if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = m.index;
19172
19344
  } else if (local === "tc") {
19173
19345
  cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
19174
19346
  } else if (local === "cellAddr" || local === "cellSpan") {
@@ -19179,6 +19351,7 @@ function scanSectionXml(xml, sectionIndex) {
19179
19351
  const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19180
19352
  if (!isNaN(ca)) cell.colAddr = ca;
19181
19353
  if (!isNaN(ra)) cell.rowAddr = ra;
19354
+ cell.addrTagRange = { start: m.index, end: contentStart };
19182
19355
  } else {
19183
19356
  const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19184
19357
  const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
@@ -19571,6 +19744,7 @@ async function fillHwpx(hwpxBuffer, values) {
19571
19744
  throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19572
19745
  }
19573
19746
  const normalizedValues = normalizeValues(values);
19747
+ const cursor = new ValueCursor(normalizedValues);
19574
19748
  const matchedLabels = /* @__PURE__ */ new Set();
19575
19749
  const filled = [];
19576
19750
  const failedKeys = /* @__PURE__ */ new Set();
@@ -19606,7 +19780,7 @@ async function fillHwpx(hwpxBuffer, values) {
19606
19780
  for (const cell of row) {
19607
19781
  for (const para of cell.paragraphs) {
19608
19782
  const text = matchText(para);
19609
- const result = fillInCellPatterns(text, normalizedValues, matchedLabels);
19783
+ const result = fillInCellPatterns(text, cursor, matchedLabels);
19610
19784
  if (!result) continue;
19611
19785
  const l = led(para);
19612
19786
  if (l.fullText !== void 0) continue;
@@ -19640,21 +19814,30 @@ async function fillHwpx(hwpxBuffer, values) {
19640
19814
  if (isKeywordLabel(cellLabelText(valueCell))) continue;
19641
19815
  const normalizedCellLabel = normalizeLabel(labelText);
19642
19816
  if (!normalizedCellLabel) continue;
19643
- const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
19817
+ const matchKey = findMatchingKey(normalizedCellLabel, cursor);
19644
19818
  if (matchKey === void 0) continue;
19645
- const newValue = normalizedValues.get(matchKey);
19646
19819
  if (patternApplied.has(valueCell)) {
19647
19820
  const target = valueCell.paragraphs.find((p) => p.tRanges.length > 0) ?? valueCell.paragraphs[0];
19648
19821
  if (!target) continue;
19649
19822
  const l = led(target);
19650
- if (l.fullText === void 0) {
19651
- l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
19652
- l.filledIdx.push(filled.length);
19653
- l.matchKeys.push(matchKey);
19654
- }
19823
+ if (l.fullText !== void 0) continue;
19824
+ const newValue = cursor.consume(matchKey);
19825
+ if (newValue === void 0) continue;
19826
+ l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
19827
+ l.filledIdx.push(filled.length);
19828
+ l.matchKeys.push(matchKey);
19829
+ matchedLabels.add(matchKey);
19830
+ filled.push({
19831
+ label: labelText.trim().replace(/[::]\s*$/, ""),
19832
+ value: newValue,
19833
+ row: rowIdx,
19834
+ col: colIdx
19835
+ });
19655
19836
  } else {
19656
19837
  const paras = valueCell.paragraphs;
19657
19838
  if (paras.length === 0) continue;
19839
+ const newValue = cursor.consume(matchKey);
19840
+ if (newValue === void 0) continue;
19658
19841
  const l0 = led(paras[0]);
19659
19842
  l0.fullText = newValue;
19660
19843
  l0.ranges = [];
@@ -19665,14 +19848,14 @@ async function fillHwpx(hwpxBuffer, values) {
19665
19848
  lk.fullText = "";
19666
19849
  lk.ranges = [];
19667
19850
  }
19851
+ matchedLabels.add(matchKey);
19852
+ filled.push({
19853
+ label: labelText.trim().replace(/[::]\s*$/, ""),
19854
+ value: newValue,
19855
+ row: rowIdx,
19856
+ col: colIdx
19857
+ });
19668
19858
  }
19669
- matchedLabels.add(matchKey);
19670
- filled.push({
19671
- label: labelText.trim().replace(/[::]\s*$/, ""),
19672
- value: newValue,
19673
- row: rowIdx,
19674
- col: colIdx
19675
- });
19676
19859
  }
19677
19860
  }
19678
19861
  if (table.rows.length >= 2) {
@@ -19686,10 +19869,11 @@ async function fillHwpx(hwpxBuffer, values) {
19686
19869
  const dataCells = table.rows[rowIdx];
19687
19870
  for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
19688
19871
  const headerLabel = normalizeLabel(cellLabelText(headerCells[colIdx]));
19689
- const matchKey = findMatchingKey(headerLabel, normalizedValues);
19872
+ const matchKey = findMatchingKey(headerLabel, cursor);
19690
19873
  if (matchKey === void 0) continue;
19691
- if (matchedLabels.has(matchKey)) continue;
19692
- const newValue = normalizedValues.get(matchKey);
19874
+ if (!cursor.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
19875
+ const newValue = cursor.consume(matchKey);
19876
+ if (newValue === void 0) continue;
19693
19877
  const paras = dataCells[colIdx].paragraphs;
19694
19878
  if (paras.length === 0) continue;
19695
19879
  const l0 = led(paras[0]);
@@ -19719,9 +19903,10 @@ async function fillHwpx(hwpxBuffer, values) {
19719
19903
  if (existing?.fullText !== void 0) continue;
19720
19904
  const text = matchText(para);
19721
19905
  for (const seg of scanInlineSegments(text)) {
19722
- const matchKey = findMatchingKey(normalizeLabel(seg.label), normalizedValues);
19906
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), cursor);
19723
19907
  if (matchKey === void 0) continue;
19724
- const newValue = normalizedValues.get(matchKey);
19908
+ const newValue = cursor.consume(matchKey);
19909
+ if (newValue === void 0) continue;
19725
19910
  const replacement = seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
19726
19911
  const l = led(para);
19727
19912
  l.ranges.push({ start: seg.valueStart, end: seg.valueEnd, replacement });
@@ -19776,6 +19961,7 @@ async function fillHwpx(hwpxBuffer, values) {
19776
19961
  splices.push(...paraSplices);
19777
19962
  }
19778
19963
  if (splices.length > 0) {
19964
+ splices.push(...allLinesegRemovalSplices(xml));
19779
19965
  replacements.set(sectionPaths[si], encoder.encode(applySplices(xml, splices)));
19780
19966
  }
19781
19967
  }
@@ -19795,6 +19981,245 @@ async function fillHwpx(hwpxBuffer, values) {
19795
19981
  // src/hwpx/generator.ts
19796
19982
  import JSZip5 from "jszip";
19797
19983
 
19984
+ // src/hwpx/text-metrics.ts
19985
+ var ASCII_W = [
19986
+ 300,
19987
+ 320,
19988
+ 320,
19989
+ 610,
19990
+ 610,
19991
+ 830,
19992
+ 724,
19993
+ 320,
19994
+ 320,
19995
+ 320,
19996
+ 550,
19997
+ 550,
19998
+ 320,
19999
+ 550,
20000
+ 320,
20001
+ 550,
20002
+ // 0x20-0x2F
20003
+ 550,
20004
+ 550,
20005
+ 550,
20006
+ 550,
20007
+ 550,
20008
+ 550,
20009
+ 550,
20010
+ 550,
20011
+ 550,
20012
+ 550,
20013
+ 320,
20014
+ 320,
20015
+ 550,
20016
+ 550,
20017
+ 550,
20018
+ 550,
20019
+ // 0x30-0x3F
20020
+ 830,
20021
+ 706,
20022
+ 605,
20023
+ 685,
20024
+ 719,
20025
+ 627,
20026
+ 617,
20027
+ 683,
20028
+ 734,
20029
+ 305,
20030
+ 315,
20031
+ 660,
20032
+ 605,
20033
+ 839,
20034
+ 734,
20035
+ 732,
20036
+ // 0x40-0x4F
20037
+ 603,
20038
+ 705,
20039
+ 660,
20040
+ 627,
20041
+ 664,
20042
+ 731,
20043
+ 706,
20044
+ 910,
20045
+ 705,
20046
+ 705,
20047
+ 626,
20048
+ 320,
20049
+ 550,
20050
+ 320,
20051
+ 550,
20052
+ 550,
20053
+ // 0x50-0x5F
20054
+ 320,
20055
+ 569,
20056
+ 597,
20057
+ 552,
20058
+ 597,
20059
+ 536,
20060
+ 356,
20061
+ 562,
20062
+ 635,
20063
+ 287,
20064
+ 288,
20065
+ 582,
20066
+ 287,
20067
+ 907,
20068
+ 635,
20069
+ 588,
20070
+ // 0x60-0x6F
20071
+ 597,
20072
+ 579,
20073
+ 478,
20074
+ 496,
20075
+ 356,
20076
+ 635,
20077
+ 563,
20078
+ 720,
20079
+ 542,
20080
+ 543,
20081
+ 486,
20082
+ 320,
20083
+ 320,
20084
+ 320,
20085
+ 550,
20086
+ 0
20087
+ // 0x70-0x7E(+DEL)
20088
+ ];
20089
+ var SYM_W = {
20090
+ 160: 300,
20091
+ 163: 568,
20092
+ 165: 707,
20093
+ 167: 498,
20094
+ 171: 440,
20095
+ 172: 564,
20096
+ 176: 291,
20097
+ 177: 798,
20098
+ 182: 606,
20099
+ 183: 320,
20100
+ 187: 440,
20101
+ 215: 617,
20102
+ 247: 678,
20103
+ 8211: 625,
20104
+ 8212: 875,
20105
+ 8213: 875,
20106
+ 8216: 320,
20107
+ 8217: 320,
20108
+ 8220: 480,
20109
+ 8221: 480,
20110
+ 8224: 558,
20111
+ 8225: 438,
20112
+ 8229: 640,
20113
+ 8230: 960,
20114
+ 8240: 988,
20115
+ 8242: 335,
20116
+ 8243: 474,
20117
+ 8251: 770,
20118
+ 8364: 656,
20119
+ 9756: 1012,
20120
+ 9758: 1012
20121
+ };
20122
+ function charWidthEm1000(cp) {
20123
+ if (cp >= 32 && cp <= 126) return ASCII_W[cp - 32];
20124
+ const sym = SYM_W[cp];
20125
+ if (sym !== void 0) return sym;
20126
+ if (cp >= 44032 && cp <= 55203) return 970;
20127
+ if (cp >= 4352 && cp <= 4607) return 970;
20128
+ if (cp >= 12593 && cp <= 12686) return 970;
20129
+ if (cp >= 19968 && cp <= 40959 || cp >= 63744 && cp <= 64255) return 1e3;
20130
+ if (cp >= 12296 && cp <= 12305 || cp >= 12308 && cp <= 12315) return 500;
20131
+ if (cp === 12288) return 970;
20132
+ if (cp >= 8592 && cp <= 8959) return 970;
20133
+ if (cp >= 9312 && cp <= 9471) return 970;
20134
+ if (cp >= 9632 && cp <= 9983) return 970;
20135
+ if (cp >= 12800 && cp <= 13311) return 970;
20136
+ if (cp >= 65281 && cp <= 65376) return 970;
20137
+ return cp >= 11904 ? 970 : 550;
20138
+ }
20139
+ var SPACE_EM_FIXED = 500;
20140
+ var SPACE_EM_FONT = 300;
20141
+ function measureTextWidth(text, height, ratioPct, opts) {
20142
+ const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
20143
+ const spacing = opts?.spacingPct ?? 0;
20144
+ let em = 0;
20145
+ for (const ch of text) {
20146
+ const cp = ch.codePointAt(0);
20147
+ const w = cp === 32 ? spaceEm : charWidthEm1000(cp);
20148
+ em += w * (1 + spacing / 100);
20149
+ }
20150
+ return em / 1e3 * height * (ratioPct / 100);
20151
+ }
20152
+ var FORBID_START = /* @__PURE__ */ new Set([..."!%),.:;?]}\xA2\xB0\u2032\u2033\u2103\u3009\u300B\u300D\u300F\u3011\u3015!%),.:;?]}\u20A9~\u2026\xB7\u3001\u3002\u3003"]);
20153
+ var FORBID_END = /* @__PURE__ */ new Set([..."$([{\xA3\xA5\u3008\u300A\u300C\u300E\u3010\u3014$([{\u20A9"]);
20154
+ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "keep", opts) {
20155
+ const EPS = 0.5;
20156
+ const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
20157
+ const spacing = opts?.spacingPct ?? 0;
20158
+ const k = height * ratioPct / 100 / 1e3;
20159
+ const cwCp = (cp) => (cp === 32 ? spaceEm : charWidthEm1000(cp)) * (1 + spacing / 100) * k;
20160
+ const charW = (ch) => cwCp(ch.codePointAt(0));
20161
+ const rangeW = (from, to) => {
20162
+ let w = 0;
20163
+ for (const ch of text.slice(from, to)) w += charW(ch);
20164
+ return w;
20165
+ };
20166
+ const units = text.match(mode === "keep" ? / +|[^ ]+/g : / +|[^ ]/g) ?? [];
20167
+ const starts = [0];
20168
+ let lineW = 0;
20169
+ let avail = firstWidth;
20170
+ let pos = 0;
20171
+ const lineStart = () => starts[starts.length - 1];
20172
+ const breakBefore = (unitPos, w) => {
20173
+ let bp = unitPos;
20174
+ const u = text[unitPos];
20175
+ if (u !== void 0 && FORBID_START.has(u) && bp - 1 > lineStart() && text[bp - 1] !== " ") bp--;
20176
+ while (bp - 1 > lineStart() && FORBID_END.has(text[bp - 1])) bp--;
20177
+ if (bp <= lineStart()) bp = unitPos;
20178
+ starts.push(bp);
20179
+ avail = contWidth;
20180
+ lineW = rangeW(bp, unitPos) + w;
20181
+ };
20182
+ for (const u of units) {
20183
+ if (u[0] === " ") {
20184
+ lineW += charW(" ") * u.length;
20185
+ pos += u.length;
20186
+ continue;
20187
+ }
20188
+ const w = rangeW(pos, pos + u.length);
20189
+ if (lineW + w <= avail + EPS) {
20190
+ lineW += w;
20191
+ pos += u.length;
20192
+ continue;
20193
+ }
20194
+ if (lineW === 0 || w > contWidth + EPS) {
20195
+ let sub = 0;
20196
+ for (const ch of u) {
20197
+ const c = charW(ch);
20198
+ if (lineW + c > avail + EPS && lineW > 0) breakBefore(pos + sub, 0);
20199
+ lineW += c;
20200
+ sub += ch.length;
20201
+ }
20202
+ pos += u.length;
20203
+ continue;
20204
+ }
20205
+ breakBefore(pos, w);
20206
+ pos += u.length;
20207
+ }
20208
+ return { lines: starts.length, starts, lastLineWidth: lineW };
20209
+ }
20210
+ function simulateWrapKeepWord(text, firstWidth, contWidth, height, ratioPct, opts) {
20211
+ return simulateWrap(text, firstWidth, contWidth, height, ratioPct, "keep", opts);
20212
+ }
20213
+ function fitRatioForFewerLines(text, firstWidth, contWidth, height, baseRatio, minRatio, opts) {
20214
+ const base = simulateWrap(text, firstWidth, contWidth, height, baseRatio, "keep", opts);
20215
+ if (base.lines < 2) return null;
20216
+ for (let r = baseRatio - 1; r >= minRatio; r--) {
20217
+ const sim = simulateWrap(text, firstWidth, contWidth, height, r, "keep", opts);
20218
+ if (sim.lines < base.lines) return r;
20219
+ }
20220
+ return null;
20221
+ }
20222
+
19798
20223
  // src/hwpx/gongmun.ts
19799
20224
  var OFFICIAL_MARGINS = { top: 20, bottom: 10, left: 20, right: 20 };
19800
20225
  var PRESET_DEFAULTS = {
@@ -19830,6 +20255,7 @@ function resolveGongmun(opts) {
19830
20255
  const preset = normalizeGongmunPreset(opts.preset);
19831
20256
  const d = PRESET_DEFAULTS[preset];
19832
20257
  const bodyPt = opts.bodyPt ?? d.bodyPt;
20258
+ const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
19833
20259
  return {
19834
20260
  preset,
19835
20261
  bodyFont: opts.bodyFont ?? "myeongjo",
@@ -19837,7 +20263,8 @@ function resolveGongmun(opts) {
19837
20263
  lineSpacing: opts.lineSpacing ?? d.lineSpacing,
19838
20264
  numbering: opts.numbering ?? d.numbering,
19839
20265
  margins: opts.margins ?? OFFICIAL_MARGINS,
19840
- centerTitle: opts.centerTitle ?? true
20266
+ centerTitle: opts.centerTitle ?? true,
20267
+ autoFitMinRatio
19841
20268
  };
19842
20269
  }
19843
20270
  var HANGUL_INITIALS = [0, 2, 3, 5, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18];
@@ -19880,11 +20307,14 @@ function standardMarker(depth, n) {
19880
20307
  function reportMarker(depth) {
19881
20308
  return REPORT_BULLETS[Math.min(depth, REPORT_BULLETS.length - 1)];
19882
20309
  }
20310
+ function markerWidth(marker, bodyHeight) {
20311
+ let em = SPACE_EM_FIXED;
20312
+ for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
20313
+ return Math.round(em / 1e3 * bodyHeight);
20314
+ }
19883
20315
  function levelIndent(depth, bodyHeight, numbering) {
19884
- const ta = bodyHeight / 2;
19885
- const markerTa = numbering === "standard" && (depth === 4 || depth === 5) ? 3 : 2;
19886
- const hang = Math.round((markerTa + 1) * ta);
19887
- return { left: Math.round(depth * bodyHeight), indent: -hang };
20316
+ const marker = numbering === "report" ? reportMarker(depth) : standardMarker(depth, 0);
20317
+ return { left: Math.round(depth * bodyHeight), indent: -markerWidth(marker, bodyHeight) };
19888
20318
  }
19889
20319
  function computeSuppression(depths) {
19890
20320
  const counts = /* @__PURE__ */ new Map();
@@ -19920,77 +20350,53 @@ function mmToHwpunit(mm) {
19920
20350
  return Math.round(mm * 7200 / 25.4);
19921
20351
  }
19922
20352
 
19923
- // src/hwpx/generator.ts
19924
- var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
19925
- var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
19926
- var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
19927
- var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
19928
- var NS_OPF = "http://www.idpf.org/2007/opf/";
19929
- var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
19930
- var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
19931
- var CHAR_NORMAL = 0;
19932
- var CHAR_BOLD = 1;
19933
- var CHAR_ITALIC = 2;
19934
- var CHAR_BOLD_ITALIC = 3;
19935
- var CHAR_CODE = 4;
19936
- var CHAR_H1 = 5;
19937
- var CHAR_H2 = 6;
19938
- var CHAR_H3 = 7;
19939
- var CHAR_H4 = 8;
19940
- var CHAR_TABLE_HEADER = 9;
19941
- var CHAR_QUOTE = 10;
19942
- var PARA_NORMAL = 0;
19943
- var PARA_H1 = 1;
19944
- var PARA_H2 = 2;
19945
- var PARA_H3 = 3;
19946
- var PARA_H4 = 4;
19947
- var PARA_CODE = 5;
19948
- var PARA_QUOTE = 6;
19949
- var PARA_LIST = 7;
19950
- var DEFAULT_TEXT_COLOR = "#000000";
19951
- function resolveTheme(theme) {
19952
- return {
19953
- h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
19954
- h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
19955
- h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19956
- h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
19957
- body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19958
- quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
19959
- /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
19960
- hasQuoteOption: theme?.quoteColor !== void 0,
19961
- tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
19962
- tableHeaderBold: !!theme?.tableHeaderBold
19963
- };
20353
+ // src/diff/text-diff.ts
20354
+ function similarity(a, b) {
20355
+ if (a === b) return 1;
20356
+ if (!a || !b) return 0;
20357
+ const maxLen = Math.max(a.length, b.length);
20358
+ if (maxLen === 0) return 1;
20359
+ return 1 - levenshtein(a, b) / maxLen;
19964
20360
  }
19965
- async function markdownToHwpx(markdown, options) {
19966
- const theme = resolveTheme(options?.theme);
19967
- const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
19968
- const blocks = parseMarkdownToBlocks(markdown);
19969
- const sectionXml = blocksToSectionXml(blocks, theme, gongmun);
19970
- const zip = new JSZip5();
19971
- zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
19972
- zip.file("META-INF/container.xml", generateContainerXml());
19973
- zip.file("Contents/content.hpf", generateManifest());
19974
- zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun));
19975
- zip.file("Contents/section0.xml", sectionXml);
19976
- zip.file("Preview/PrvText.txt", buildPrvText(blocks));
19977
- return await zip.generateAsync({ type: "arraybuffer" });
20361
+ function normalizedSimilarity(a, b) {
20362
+ return similarity(normalize(a), normalize(b));
19978
20363
  }
19979
- function buildPrvText(blocks) {
19980
- const lines = [];
19981
- let bytes = 0;
19982
- for (const b of blocks) {
19983
- const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
19984
- if (!text) continue;
19985
- lines.push(text);
19986
- bytes += text.length * 3;
19987
- if (bytes > 1024) break;
20364
+ function normalize(s) {
20365
+ return s.replace(/\s+/g, " ").trim();
20366
+ }
20367
+ var MAX_LEVENSHTEIN_LEN = 1e4;
20368
+ function levenshtein(a, b) {
20369
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20370
+ const sampleLen = Math.min(500, a.length, b.length);
20371
+ let diffs = 0;
20372
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20373
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20374
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
19988
20375
  }
19989
- return lines.join("\n").slice(0, 1024);
20376
+ if (a.length > b.length) [a, b] = [b, a];
20377
+ const m = a.length;
20378
+ const n = b.length;
20379
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
20380
+ let curr = new Array(m + 1);
20381
+ for (let j = 1; j <= n; j++) {
20382
+ curr[0] = j;
20383
+ for (let i = 1; i <= m; i++) {
20384
+ if (a[i - 1] === b[j - 1]) {
20385
+ curr[i] = prev[i - 1];
20386
+ } else {
20387
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20388
+ }
20389
+ }
20390
+ ;
20391
+ [prev, curr] = [curr, prev];
20392
+ }
20393
+ return prev[m];
19990
20394
  }
19991
- function parseMarkdownToBlocks(md2) {
20395
+
20396
+ // src/roundtrip/markdown-units.ts
20397
+ function splitMarkdownUnits(md2) {
19992
20398
  const lines = md2.split("\n");
19993
- const blocks = [];
20399
+ const units = [];
19994
20400
  let i = 0;
19995
20401
  while (i < lines.length) {
19996
20402
  const line = lines[i];
@@ -19998,66 +20404,523 @@ function parseMarkdownToBlocks(md2) {
19998
20404
  i++;
19999
20405
  continue;
20000
20406
  }
20001
- const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20002
- if (fenceMatch) {
20003
- const fence = fenceMatch[1];
20004
- const lang = fenceMatch[2].trim();
20005
- const codeLines = [];
20006
- i++;
20007
- while (i < lines.length && !lines[i].startsWith(fence)) {
20008
- codeLines.push(lines[i]);
20407
+ if (line.trim().startsWith("<table>")) {
20408
+ const collected2 = [];
20409
+ let depth = 0;
20410
+ while (i < lines.length) {
20411
+ const l = lines[i];
20412
+ collected2.push(l);
20413
+ depth += (l.match(/<table>/g) || []).length;
20414
+ depth -= (l.match(/<\/table>/g) || []).length;
20009
20415
  i++;
20416
+ if (depth <= 0) break;
20010
20417
  }
20011
- if (i < lines.length) i++;
20012
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20013
- continue;
20014
- }
20015
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20016
- blocks.push({ type: "hr" });
20017
- i++;
20018
- continue;
20019
- }
20020
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20021
- if (headingMatch) {
20022
- blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20023
- i++;
20418
+ units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20024
20419
  continue;
20025
20420
  }
20026
20421
  if (line.trimStart().startsWith("|")) {
20027
- const tableRows = [];
20422
+ const collected2 = [];
20028
20423
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20029
- const row = lines[i];
20030
- if (/^[\s|:\-]+$/.test(row)) {
20031
- i++;
20032
- continue;
20033
- }
20034
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20035
- if (cells.length > 0) tableRows.push(cells);
20424
+ collected2.push(lines[i]);
20036
20425
  i++;
20037
20426
  }
20038
- if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
20427
+ units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20039
20428
  continue;
20040
20429
  }
20041
- if (line.trimStart().startsWith("> ")) {
20042
- const quoteLines = [];
20043
- while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20044
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
20045
- i++;
20046
- }
20047
- for (const ql of quoteLines) {
20048
- blocks.push({ type: "blockquote", text: ql.trim() || "" });
20049
- }
20430
+ if (/^-{3,}\s*$/.test(line.trim())) {
20431
+ units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20432
+ i++;
20050
20433
  continue;
20051
20434
  }
20052
- const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20053
- if (listMatch) {
20054
- const indent = Math.floor(listMatch[1].length / 2);
20055
- const ordered = /\d/.test(listMatch[2]);
20056
- blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
20435
+ if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20436
+ units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20057
20437
  i++;
20058
20438
  continue;
20059
20439
  }
20060
- blocks.push({ type: "paragraph", text: line.trim() });
20440
+ const collected = [];
20441
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20442
+ collected.push(lines[i].trim());
20443
+ i++;
20444
+ }
20445
+ units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20446
+ }
20447
+ return units;
20448
+ }
20449
+ function alignUnits(a, b) {
20450
+ const m = a.length, n = b.length;
20451
+ if (m * n > 4e6) {
20452
+ const result2 = [];
20453
+ let pre = 0;
20454
+ while (pre < m && pre < n && a[pre] === b[pre]) {
20455
+ result2.push([pre, pre]);
20456
+ pre++;
20457
+ }
20458
+ let suf = 0;
20459
+ while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
20460
+ const aMid = m - pre - suf, bMid = n - pre - suf;
20461
+ if (aMid === bMid) {
20462
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
20463
+ } else {
20464
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
20465
+ for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
20466
+ }
20467
+ for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
20468
+ return result2;
20469
+ }
20470
+ const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
20471
+ for (let i2 = 1; i2 <= m; i2++) {
20472
+ for (let j2 = 1; j2 <= n; j2++) {
20473
+ 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]);
20474
+ }
20475
+ }
20476
+ const matches = [];
20477
+ let i = m, j = n;
20478
+ while (i > 0 && j > 0) {
20479
+ if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
20480
+ matches.push([i - 1, j - 1]);
20481
+ i--;
20482
+ j--;
20483
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
20484
+ else j--;
20485
+ }
20486
+ matches.reverse();
20487
+ const result = [];
20488
+ let ai = 0, bi = 0;
20489
+ const flushGap = (aEnd, bEnd) => {
20490
+ if (aEnd - ai === bEnd - bi) {
20491
+ while (ai < aEnd) result.push([ai++, bi++]);
20492
+ return;
20493
+ }
20494
+ while (ai < aEnd && bi < bEnd) {
20495
+ const sim = normalizedSimilarity(a[ai], b[bi]);
20496
+ if (sim >= 0.4) {
20497
+ if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
20498
+ result.push([ai++, null]);
20499
+ } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
20500
+ result.push([null, bi++]);
20501
+ } else {
20502
+ result.push([ai++, bi++]);
20503
+ }
20504
+ } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
20505
+ else result.push([null, bi++]);
20506
+ }
20507
+ while (ai < aEnd) result.push([ai++, null]);
20508
+ while (bi < bEnd) result.push([null, bi++]);
20509
+ };
20510
+ for (const [pi, pj] of matches) {
20511
+ flushGap(pi, pj);
20512
+ result.push([ai++, bi++]);
20513
+ }
20514
+ flushGap(m, n);
20515
+ return result;
20516
+ }
20517
+ function bestSimInRange(arr, from, to, target) {
20518
+ let best = 0;
20519
+ for (let k = from; k <= to && k < arr.length; k++) {
20520
+ const s = normalizedSimilarity(arr[k], target);
20521
+ if (s > best) best = s;
20522
+ }
20523
+ return best;
20524
+ }
20525
+ function escapeGfm(text) {
20526
+ return text.replace(/~/g, "\\~");
20527
+ }
20528
+ var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
20529
+ function sanitizeText(text) {
20530
+ let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
20531
+ if (result.length <= 30 && result.includes(" ")) {
20532
+ const tokens = result.split(" ");
20533
+ const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
20534
+ if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
20535
+ result = tokens.join("");
20536
+ }
20537
+ }
20538
+ return result;
20539
+ }
20540
+ function normForMatch(text) {
20541
+ return sanitizeText(text).replace(/\s+/g, " ").trim();
20542
+ }
20543
+ function unescapeGfm(text) {
20544
+ return text.replace(/\\~/g, "~");
20545
+ }
20546
+ function summarize(text) {
20547
+ const t = text.replace(/\s+/g, " ").trim();
20548
+ return t.length > 80 ? t.slice(0, 77) + "..." : t;
20549
+ }
20550
+ function replicateGfmTable(table) {
20551
+ const { cells, rows: numRows, cols: numCols } = table;
20552
+ if (numRows === 0 || numCols === 0) return null;
20553
+ if (numRows === 1 && numCols === 1) return null;
20554
+ if (numCols === 1) return null;
20555
+ const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
20556
+ const skip = /* @__PURE__ */ new Set();
20557
+ for (let r = 0; r < numRows; r++) {
20558
+ for (let c = 0; c < numCols; c++) {
20559
+ if (skip.has(`${r},${c}`)) continue;
20560
+ const cell = cells[r]?.[c];
20561
+ if (!cell) continue;
20562
+ display[r][c] = {
20563
+ text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
20564
+ gridR: r,
20565
+ gridC: c
20566
+ };
20567
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
20568
+ for (let dc = 0; dc < cell.colSpan; dc++) {
20569
+ if (dr === 0 && dc === 0) continue;
20570
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20571
+ }
20572
+ }
20573
+ c += cell.colSpan - 1;
20574
+ }
20575
+ }
20576
+ const uniqueRows = [];
20577
+ let pendingLabelRow = null;
20578
+ for (let r = 0; r < display.length; r++) {
20579
+ const row = display[r];
20580
+ if (row.every((cell) => cell.text === "")) continue;
20581
+ const nonEmptyCols = row.filter((cell) => cell.text !== "");
20582
+ const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
20583
+ if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
20584
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20585
+ pendingLabelRow = row;
20586
+ continue;
20587
+ }
20588
+ if (pendingLabelRow) {
20589
+ if (row[0].text === "") row[0] = pendingLabelRow[0];
20590
+ else uniqueRows.push(pendingLabelRow);
20591
+ pendingLabelRow = null;
20592
+ }
20593
+ uniqueRows.push(row);
20594
+ }
20595
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20596
+ return uniqueRows.length > 0 ? uniqueRows : null;
20597
+ }
20598
+ function parseGfmTable(lines) {
20599
+ const rows = [];
20600
+ for (const line of lines) {
20601
+ const trimmed = line.trim();
20602
+ if (!trimmed.startsWith("|")) continue;
20603
+ const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
20604
+ if (cells.length === 0) continue;
20605
+ if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
20606
+ rows.push(cells);
20607
+ }
20608
+ return rows;
20609
+ }
20610
+ function unescapeGfmCell(text) {
20611
+ return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
20612
+ }
20613
+ function replicateCellInnerHtml(cell) {
20614
+ if (cell.blocks?.length) {
20615
+ return cell.blocks.map((b) => {
20616
+ if (b.type === "table" && b.table) {
20617
+ const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
20618
+ return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
20619
+ }
20620
+ if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
20621
+ const t = sanitizeText(b.text ?? "");
20622
+ return t ? t.replace(/\n/g, "<br>") : "";
20623
+ }).filter(Boolean).join("<br>");
20624
+ }
20625
+ return sanitizeText(cell.text).replace(/\n/g, "<br>");
20626
+ }
20627
+ function replicateTableToHtml(table) {
20628
+ const rows = replicateHtmlTable(table);
20629
+ const lines = ["<table>"];
20630
+ for (let r = 0; r < rows.length; r++) {
20631
+ const tag = rows[r].tag;
20632
+ const rowHtml = rows[r].cells.map((cell) => {
20633
+ const attrs = [];
20634
+ if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
20635
+ if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
20636
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
20637
+ return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
20638
+ });
20639
+ if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
20640
+ }
20641
+ lines.push("</table>");
20642
+ return lines.join("\n");
20643
+ }
20644
+ function replicateHtmlTable(table) {
20645
+ const { cells, rows: numRows, cols: numCols } = table;
20646
+ const skip = /* @__PURE__ */ new Set();
20647
+ const result = [];
20648
+ for (let r = 0; r < numRows; r++) {
20649
+ const tag = r === 0 ? "th" : "td";
20650
+ const rowCells = [];
20651
+ for (let c = 0; c < numCols; c++) {
20652
+ if (skip.has(`${r},${c}`)) continue;
20653
+ const cell = cells[r]?.[c];
20654
+ if (!cell) continue;
20655
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
20656
+ for (let dc = 0; dc < cell.colSpan; dc++) {
20657
+ if (dr === 0 && dc === 0) continue;
20658
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20659
+ }
20660
+ }
20661
+ rowCells.push({
20662
+ inner: replicateCellInnerHtml(cell),
20663
+ colSpan: cell.colSpan,
20664
+ rowSpan: cell.rowSpan,
20665
+ gridR: r,
20666
+ gridC: c
20667
+ });
20668
+ }
20669
+ if (rowCells.length) result.push({ tag, cells: rowCells });
20670
+ }
20671
+ return result;
20672
+ }
20673
+ function parseHtmlTable(raw) {
20674
+ const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
20675
+ let depth = 0;
20676
+ let currentRow = null;
20677
+ let cellStart = -1;
20678
+ let cellInfo = null;
20679
+ const rows = [];
20680
+ let m;
20681
+ while ((m = re.exec(raw)) !== null) {
20682
+ const isClose = m[1] === "/";
20683
+ const tag = m[2].toLowerCase();
20684
+ const attrs = m[3] || "";
20685
+ if (tag === "table") {
20686
+ depth += isClose ? -1 : 1;
20687
+ if (depth < 0) return null;
20688
+ continue;
20689
+ }
20690
+ if (depth !== 1) continue;
20691
+ if (tag === "tr") {
20692
+ if (!isClose) currentRow = [];
20693
+ else if (currentRow) {
20694
+ rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
20695
+ currentRow = null;
20696
+ }
20697
+ } else {
20698
+ if (!isClose) {
20699
+ const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20700
+ const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20701
+ cellStart = m.index + m[0].length;
20702
+ cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
20703
+ } else if (cellStart >= 0 && cellInfo && currentRow) {
20704
+ currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
20705
+ cellStart = -1;
20706
+ cellInfo = null;
20707
+ }
20708
+ }
20709
+ }
20710
+ if (depth !== 0) return null;
20711
+ return rows;
20712
+ }
20713
+ var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
20714
+ function htmlCellInnerToLines(inner) {
20715
+ let hadNonText = false;
20716
+ let work = inner;
20717
+ if (/<table[\s>]/i.test(work)) {
20718
+ hadNonText = true;
20719
+ work = removeNestedTables(work);
20720
+ }
20721
+ if (/<img\s/i.test(work)) {
20722
+ hadNonText = true;
20723
+ work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
20724
+ }
20725
+ const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
20726
+ return { lines, hadNonText };
20727
+ }
20728
+ function extractTopLevelTables(html) {
20729
+ const result = [];
20730
+ let depth = 0;
20731
+ let start = -1;
20732
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
20733
+ let m;
20734
+ while ((m = re.exec(html)) !== null) {
20735
+ if (m[1] !== "/") {
20736
+ if (depth === 0) start = m.index;
20737
+ depth++;
20738
+ } else {
20739
+ depth--;
20740
+ if (depth === 0 && start >= 0) {
20741
+ result.push(html.slice(start, m.index + m[0].length));
20742
+ start = -1;
20743
+ }
20744
+ if (depth < 0) depth = 0;
20745
+ }
20746
+ }
20747
+ return result;
20748
+ }
20749
+ function removeNestedTables(html) {
20750
+ let result = "";
20751
+ let depth = 0;
20752
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
20753
+ let last = 0;
20754
+ let m;
20755
+ while ((m = re.exec(html)) !== null) {
20756
+ if (m[1] !== "/") {
20757
+ if (depth === 0) result += html.slice(last, m.index);
20758
+ depth++;
20759
+ } else {
20760
+ depth--;
20761
+ if (depth === 0) last = m.index + m[0].length;
20762
+ if (depth < 0) depth = 0;
20763
+ }
20764
+ }
20765
+ if (depth === 0) result += html.slice(last);
20766
+ return result;
20767
+ }
20768
+
20769
+ // src/hwpx/generator.ts
20770
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
20771
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
20772
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
20773
+ var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
20774
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
20775
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
20776
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
20777
+ var CHAR_NORMAL = 0;
20778
+ var CHAR_BOLD = 1;
20779
+ var CHAR_ITALIC = 2;
20780
+ var CHAR_BOLD_ITALIC = 3;
20781
+ var CHAR_CODE = 4;
20782
+ var CHAR_H1 = 5;
20783
+ var CHAR_H2 = 6;
20784
+ var CHAR_H3 = 7;
20785
+ var CHAR_H4 = 8;
20786
+ var CHAR_TABLE_HEADER = 9;
20787
+ var CHAR_QUOTE = 10;
20788
+ var PARA_NORMAL = 0;
20789
+ var PARA_H1 = 1;
20790
+ var PARA_H2 = 2;
20791
+ var PARA_H3 = 3;
20792
+ var PARA_H4 = 4;
20793
+ var PARA_CODE = 5;
20794
+ var PARA_QUOTE = 6;
20795
+ var PARA_LIST = 7;
20796
+ var DEFAULT_TEXT_COLOR = "#000000";
20797
+ function resolveTheme(theme) {
20798
+ return {
20799
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
20800
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
20801
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20802
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20803
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20804
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
20805
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
20806
+ hasQuoteOption: theme?.quoteColor !== void 0,
20807
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20808
+ tableHeaderBold: !!theme?.tableHeaderBold
20809
+ };
20810
+ }
20811
+ async function markdownToHwpx(markdown, options) {
20812
+ const theme = resolveTheme(options?.theme);
20813
+ const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
20814
+ const blocks = parseMarkdownToBlocks(markdown);
20815
+ const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
20816
+ const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
20817
+ const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
20818
+ const zip = new JSZip5();
20819
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
20820
+ zip.file("META-INF/container.xml", generateContainerXml());
20821
+ zip.file("Contents/content.hpf", generateManifest());
20822
+ zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
20823
+ zip.file("Contents/section0.xml", sectionXml);
20824
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
20825
+ return await zip.generateAsync({ type: "arraybuffer" });
20826
+ }
20827
+ function buildPrvText(blocks) {
20828
+ const lines = [];
20829
+ let bytes = 0;
20830
+ for (const b of blocks) {
20831
+ let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
20832
+ if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
20833
+ if (!text) continue;
20834
+ lines.push(text);
20835
+ bytes += text.length * 3;
20836
+ if (bytes > 1024) break;
20837
+ }
20838
+ return lines.join("\n").slice(0, 1024);
20839
+ }
20840
+ function parseMarkdownToBlocks(md2) {
20841
+ const lines = md2.split("\n");
20842
+ const blocks = [];
20843
+ let i = 0;
20844
+ while (i < lines.length) {
20845
+ const line = lines[i];
20846
+ if (!line.trim()) {
20847
+ i++;
20848
+ continue;
20849
+ }
20850
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20851
+ if (fenceMatch) {
20852
+ const fence = fenceMatch[1];
20853
+ const lang = fenceMatch[2].trim();
20854
+ const codeLines = [];
20855
+ i++;
20856
+ while (i < lines.length && !lines[i].startsWith(fence)) {
20857
+ codeLines.push(lines[i]);
20858
+ i++;
20859
+ }
20860
+ if (i < lines.length) i++;
20861
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20862
+ continue;
20863
+ }
20864
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20865
+ blocks.push({ type: "hr" });
20866
+ i++;
20867
+ continue;
20868
+ }
20869
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20870
+ if (headingMatch) {
20871
+ blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20872
+ i++;
20873
+ continue;
20874
+ }
20875
+ if (/^<table[\s>]/i.test(line.trimStart())) {
20876
+ const htmlLines = [];
20877
+ let depth = 0;
20878
+ while (i < lines.length) {
20879
+ const l = lines[i];
20880
+ htmlLines.push(l);
20881
+ depth += (l.match(/<table[\s>]/gi) ?? []).length;
20882
+ depth -= (l.match(/<\/table>/gi) ?? []).length;
20883
+ i++;
20884
+ if (depth <= 0) break;
20885
+ }
20886
+ blocks.push({ type: "html_table", text: htmlLines.join("\n") });
20887
+ continue;
20888
+ }
20889
+ if (line.trimStart().startsWith("|")) {
20890
+ const tableRows = [];
20891
+ while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20892
+ const row = lines[i];
20893
+ if (/^[\s|:\-]+$/.test(row)) {
20894
+ i++;
20895
+ continue;
20896
+ }
20897
+ const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20898
+ if (cells.length > 0) tableRows.push(cells);
20899
+ i++;
20900
+ }
20901
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
20902
+ continue;
20903
+ }
20904
+ if (line.trimStart().startsWith("> ")) {
20905
+ const quoteLines = [];
20906
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20907
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
20908
+ i++;
20909
+ }
20910
+ for (const ql of quoteLines) {
20911
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
20912
+ }
20913
+ continue;
20914
+ }
20915
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20916
+ if (listMatch) {
20917
+ const indent = Math.floor(listMatch[1].length / 2);
20918
+ const ordered = /\d/.test(listMatch[2]);
20919
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
20920
+ i++;
20921
+ continue;
20922
+ }
20923
+ blocks.push({ type: "paragraph", text: line.trim() });
20061
20924
  i++;
20062
20925
  }
20063
20926
  return blocks;
@@ -20104,18 +20967,19 @@ function spanToCharPrId(span) {
20104
20967
  function escapeXml(text) {
20105
20968
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20106
20969
  }
20107
- function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
20970
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
20108
20971
  const spans = parseInlineMarkdown(text);
20109
20972
  return spans.map((span) => {
20110
- const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
20973
+ let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
20974
+ if (mapCharId) charId = mapCharId(charId);
20111
20975
  return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
20112
20976
  }).join("");
20113
20977
  }
20114
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
20978
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
20115
20979
  if (paraPrId === PARA_CODE) {
20116
20980
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
20117
20981
  }
20118
- const runs = generateRuns(text, charPrId);
20982
+ const runs = generateRuns(text, charPrId, mapCharId);
20119
20983
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
20120
20984
  }
20121
20985
  function headingParaPrId(level) {
@@ -20155,7 +21019,7 @@ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_C
20155
21019
  const boldAttr = bold ? ` bold="1"` : "";
20156
21020
  const italicAttr = italic ? ` italic="1"` : "";
20157
21021
  const effFont = bold ? 2 : fontId;
20158
- return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
21022
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
20159
21023
  <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
20160
21024
  <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
20161
21025
  <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
@@ -20174,13 +21038,58 @@ function paraPr(id, opts = {}) {
20174
21038
  <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20175
21039
  <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>
20176
21040
  <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20177
- <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
21041
+ <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20178
21042
  </hh:paraPr>`;
20179
21043
  }
20180
21044
  var GONGMUN_LIST_BASE = 8;
20181
21045
  var GONGMUN_LIST_LEVELS = 8;
20182
21046
  var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
20183
- function buildCharProperties(theme, gongmun) {
21047
+ var CHAR_VARIANT_BASE = 11;
21048
+ var GONGMUN_BODY_RATIO = 95;
21049
+ function plainRenderText(text) {
21050
+ return parseInlineMarkdown(text).map((s) => s.text).join("");
21051
+ }
21052
+ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
21053
+ const minRatio = gongmun.autoFitMinRatio;
21054
+ if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
21055
+ const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
21056
+ const ratioByBlock = /* @__PURE__ */ new Map();
21057
+ const variants = [];
21058
+ for (let i = 0; i < blocks.length; i++) {
21059
+ const block = blocks[i];
21060
+ let text;
21061
+ let firstW;
21062
+ let contW;
21063
+ if (block.type === "list_item" && gongmunList.has(i)) {
21064
+ const { marker, depth } = gongmunList.get(i);
21065
+ const content = plainRenderText(block.text || "");
21066
+ text = marker ? `${marker} ${content}` : content;
21067
+ const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
21068
+ firstW = pageW - left - Math.max(indent, 0);
21069
+ contW = pageW - left - Math.max(-indent, 0);
21070
+ } else if (block.type === "paragraph") {
21071
+ const raw = (block.text || "").trim();
21072
+ if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
21073
+ text = plainRenderText(raw);
21074
+ firstW = contW = pageW;
21075
+ } else {
21076
+ continue;
21077
+ }
21078
+ if (!text) continue;
21079
+ const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
21080
+ if (r === null) continue;
21081
+ ratioByBlock.set(i, r);
21082
+ if (!variants.includes(r)) variants.push(r);
21083
+ }
21084
+ return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
21085
+ }
21086
+ function variantMapper(fit, blockIdx) {
21087
+ const r = fit.ratioByBlock.get(blockIdx);
21088
+ if (r === void 0) return void 0;
21089
+ const vi = fit.variants.indexOf(r);
21090
+ return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
21091
+ }
21092
+ function buildCharProperties(theme, gongmun, ratioVariants = []) {
20184
21093
  let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
20185
21094
  if (gongmun) {
20186
21095
  body = gongmun.bodyHeight;
@@ -20190,7 +21099,7 @@ function buildCharProperties(theme, gongmun) {
20190
21099
  h3 = body;
20191
21100
  h4 = Math.max(body - 100, 1300);
20192
21101
  }
20193
- const bodyRatio = gongmun ? 95 : 100;
21102
+ const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
20194
21103
  const rows = [
20195
21104
  charPr(0, body, false, false, 0, theme.body, bodyRatio),
20196
21105
  charPr(1, body, true, false, 0, theme.body, bodyRatio),
@@ -20204,6 +21113,14 @@ function buildCharProperties(theme, gongmun) {
20204
21113
  charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
20205
21114
  charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
20206
21115
  ];
21116
+ for (const r of ratioVariants) {
21117
+ rows.push(
21118
+ charPr(rows.length, body, false, false, 0, theme.body, r),
21119
+ charPr(rows.length + 1, body, true, false, 0, theme.body, r),
21120
+ charPr(rows.length + 2, body, false, true, 0, theme.body, r),
21121
+ charPr(rows.length + 3, body, true, true, 0, theme.body, r)
21122
+ );
21123
+ }
20207
21124
  return `<hh:charProperties itemCnt="${rows.length}">
20208
21125
  ${rows.join("\n")}
20209
21126
  </hh:charProperties>`;
@@ -20246,9 +21163,9 @@ ${base2.join("\n")}
20246
21163
  ${base.join("\n")}
20247
21164
  </hh:paraProperties>`;
20248
21165
  }
20249
- function generateHeaderXml(theme, gongmun) {
21166
+ function generateHeaderXml(theme, gongmun, ratioVariants = []) {
20250
21167
  const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
20251
- const charPropsXml = buildCharProperties(theme, gongmun);
21168
+ const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
20252
21169
  const paraPropsXml = buildParaProperties(gongmun);
20253
21170
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20254
21171
  <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
@@ -20304,25 +21221,21 @@ function generateHeaderXml(theme, gongmun) {
20304
21221
  </hh:fontface>
20305
21222
  </hh:fontfaces>
20306
21223
  <hh:borderFills itemCnt="2">
20307
- <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
21224
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20308
21225
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20309
21226
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20310
21227
  <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
20311
21228
  <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
20312
21229
  <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
20313
21230
  <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
20314
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20315
- <hh:fillInfo/>
20316
21231
  </hh:borderFill>
20317
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
21232
+ <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20318
21233
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20319
21234
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20320
21235
  <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
20321
21236
  <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
20322
21237
  <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
20323
21238
  <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
20324
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20325
- <hh:fillInfo/>
20326
21239
  </hh:borderFill>
20327
21240
  </hh:borderFills>
20328
21241
  ${charPropsXml}
@@ -20369,14 +21282,74 @@ function generateTable(rows, theme) {
20369
21282
  const tdElements = cells.map((cell, colIdx) => {
20370
21283
  const runs = generateRuns(cell, headerCharPr);
20371
21284
  const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
20372
- return `<hp:tc name="" header="${isHeaderRow ? 1 : 0}" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="1"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="TOP" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${p}</hp:subList><hp:cellAddr colAddr="${colIdx}" rowAddr="${rowIdx}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="${cellW}" height="${cellH}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
21285
+ return `<hp:tc name="" header="${isHeaderRow ? 1 : 0}" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="2"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="TOP" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${p}</hp:subList><hp:cellAddr colAddr="${colIdx}" rowAddr="${rowIdx}"/><hp:cellSpan colSpan="1" rowSpan="1"/><hp:cellSz width="${cellW}" height="${cellH}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
20373
21286
  }).join("");
20374
21287
  return `<hp:tr>${tdElements}</hp:tr>`;
20375
21288
  }).join("");
20376
21289
  const tblInner = `<hp:sz width="${tblW}" widthRelTo="ABSOLUTE" height="${tblH}" 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"/>` + trElements;
20377
- const tbl = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="1" noShading="0">${tblInner}</hp:tbl>`;
21290
+ const tbl = `<hp:tbl id="${tblId}" zOrder="0" numberingType="TABLE" pageBreak="CELL" repeatHeader="0" rowCnt="${rowCnt}" colCnt="${colCnt}" cellSpacing="0" borderFillIDRef="2" noShading="0">${tblInner}</hp:tbl>`;
20378
21291
  return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
20379
21292
  }
21293
+ function layoutHtmlRows(rows) {
21294
+ const occupied = /* @__PURE__ */ new Set();
21295
+ const placed = [];
21296
+ let colCnt = 0;
21297
+ for (let r = 0; r < rows.length; r++) {
21298
+ let c = 0;
21299
+ for (const cell of rows[r].cells) {
21300
+ while (occupied.has(`${r},${c}`)) c++;
21301
+ const colSpan = Math.max(1, cell.colSpan);
21302
+ const rowSpan = Math.max(1, cell.rowSpan);
21303
+ placed.push({ r, c, colSpan, rowSpan, inner: cell.inner, isHeader: rows[r].tag === "th" });
21304
+ for (let dr = 0; dr < rowSpan; dr++) {
21305
+ for (let dc = 0; dc < colSpan; dc++) occupied.add(`${r + dr},${c + dc}`);
21306
+ }
21307
+ c += colSpan;
21308
+ colCnt = Math.max(colCnt, c);
21309
+ }
21310
+ }
21311
+ return { placed, rowCnt: rows.length, colCnt };
21312
+ }
21313
+ function unescapeHtml(s) {
21314
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
21315
+ }
21316
+ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3) {
21317
+ const rows = parseHtmlTable(rawHtml);
21318
+ if (!rows || rows.length === 0) return null;
21319
+ const { placed, rowCnt, colCnt } = layoutHtmlRows(rows);
21320
+ if (rowCnt === 0 || colCnt === 0) return null;
21321
+ const colW = Math.floor(totalWidth / colCnt);
21322
+ const cellH = 1500;
21323
+ const tblW = colW * colCnt;
21324
+ const tblId = nextTableId();
21325
+ const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
21326
+ const tcXmls = placed.map((cell) => {
21327
+ const headerCharPr = cell.isHeader && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
21328
+ const { lines } = htmlCellInnerToLines(cell.inner);
21329
+ const paras = lines.map(
21330
+ (line) => `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="${headerCharPr}"><hp:t>${escapeXml(unescapeHtml(line))}</hp:t></hp:run></hp:p>`
21331
+ );
21332
+ let nestedH = 0;
21333
+ for (const nested of extractTopLevelTables(cell.inner)) {
21334
+ const nestedXml = generateHtmlTableXml(nested, theme, Math.max(colW * cell.colSpan - 1020, 4e3));
21335
+ if (nestedXml) {
21336
+ paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${nestedXml}</hp:run></hp:p>`);
21337
+ nestedH += (nested.match(/<tr[\s>]/gi) ?? []).length * cellH + 300;
21338
+ }
21339
+ }
21340
+ if (paras.length === 0) {
21341
+ paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="${headerCharPr}"><hp:t></hp:t></hp:run></hp:p>`);
21342
+ }
21343
+ const cellHeight = Math.max(cellH * cell.rowSpan, Math.max(lines.length, 1) * 800 + nestedH);
21344
+ return `<hp:tc name="" header="${cell.isHeader ? 1 : 0}" hasMargin="0" protect="0" editable="1" dirty="0" borderFillIDRef="2"><hp:subList id="" textDirection="HORIZONTAL" lineWrap="BREAK" vertAlign="TOP" linkListIDRef="0" linkListNextIDRef="0" textWidth="0" textHeight="0" hasTextRef="0" hasNumRef="0">${paras.join("")}</hp:subList><hp:cellAddr colAddr="${cell.c}" rowAddr="${cell.r}"/><hp:cellSpan colSpan="${cell.colSpan}" rowSpan="${cell.rowSpan}"/><hp:cellSz width="${colW * cell.colSpan}" height="${cellHeight}"/><hp:cellMargin left="141" right="141" top="141" bottom="141"/></hp:tc>`;
21345
+ });
21346
+ const trXmls = [];
21347
+ for (let r = 0; r < rowCnt; r++) {
21348
+ const rowTcs = tcXmls.filter((_, i) => placed[i].r === r);
21349
+ trXmls.push(`<hp:tr>${rowTcs.join("")}</hp:tr>`);
21350
+ }
21351
+ 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>`;
21352
+ }
20380
21353
  function precomputeGongmunList(blocks, gongmun) {
20381
21354
  const result = /* @__PURE__ */ new Map();
20382
21355
  let i = 0;
@@ -20400,12 +21373,11 @@ function precomputeGongmunList(blocks, gongmun) {
20400
21373
  }
20401
21374
  return result;
20402
21375
  }
20403
- function blocksToSectionXml(blocks, theme, gongmun) {
21376
+ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null) {
20404
21377
  const paraXmls = [];
20405
21378
  let isFirst = true;
20406
21379
  const orderedCounters = {};
20407
21380
  let prevWasOrdered = false;
20408
- const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
20409
21381
  for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
20410
21382
  const block = blocks[blockIdx];
20411
21383
  let xml = "";
@@ -20427,7 +21399,7 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20427
21399
  if (ctr) {
20428
21400
  xml = generateParagraph(ctr[1].trim(), GONGMUN_CENTER);
20429
21401
  } else {
20430
- xml = generateParagraph(block.text || "");
21402
+ xml = generateParagraph(block.text || "", PARA_NORMAL, CHAR_NORMAL, fit ? variantMapper(fit, blockIdx) : void 0);
20431
21403
  }
20432
21404
  break;
20433
21405
  }
@@ -20451,7 +21423,7 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20451
21423
  const content = block.text || "";
20452
21424
  const text = marker2 ? `${marker2} ${content}` : content;
20453
21425
  const listCharPr = gongmun.numbering === "report" && depth === 0 ? CHAR_BOLD : CHAR_NORMAL;
20454
- xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr);
21426
+ xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr, fit ? variantMapper(fit, blockIdx) : void 0);
20455
21427
  break;
20456
21428
  }
20457
21429
  const indent = block.indent || 0;
@@ -20476,78 +21448,50 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20476
21448
  }
20477
21449
  case "hr":
20478
21450
  xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0"><hp:t>\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500</hp:t></hp:run></hp:p>`;
20479
- break;
20480
- case "table":
20481
- if (block.rows) {
20482
- if (isFirst) {
20483
- const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
20484
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
20485
- isFirst = false;
20486
- }
20487
- xml = generateTable(block.rows, theme);
20488
- }
20489
- break;
20490
- }
20491
- if (!xml) continue;
20492
- if (isFirst && block.type !== "table") {
20493
- xml = xml.replace(
20494
- /<hp:run charPrIDRef="(\d+)">/,
20495
- `<hp:run charPrIDRef="$1">${generateSecPr(gongmun)}`
20496
- );
20497
- isFirst = false;
20498
- }
20499
- paraXmls.push(xml);
20500
- }
20501
- if (paraXmls.length === 0) {
20502
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run></hp:p>`);
20503
- }
20504
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20505
- <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
20506
- ${paraXmls.join("\n ")}
20507
- </hs:sec>`;
20508
- }
20509
-
20510
- // src/diff/text-diff.ts
20511
- function similarity(a, b) {
20512
- if (a === b) return 1;
20513
- if (!a || !b) return 0;
20514
- const maxLen = Math.max(a.length, b.length);
20515
- if (maxLen === 0) return 1;
20516
- return 1 - levenshtein(a, b) / maxLen;
20517
- }
20518
- function normalizedSimilarity(a, b) {
20519
- return similarity(normalize(a), normalize(b));
20520
- }
20521
- function normalize(s) {
20522
- return s.replace(/\s+/g, " ").trim();
20523
- }
20524
- var MAX_LEVENSHTEIN_LEN = 1e4;
20525
- function levenshtein(a, b) {
20526
- if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20527
- const sampleLen = Math.min(500, a.length, b.length);
20528
- let diffs = 0;
20529
- for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20530
- const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20531
- return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20532
- }
20533
- if (a.length > b.length) [a, b] = [b, a];
20534
- const m = a.length;
20535
- const n = b.length;
20536
- let prev = Array.from({ length: m + 1 }, (_, i) => i);
20537
- let curr = new Array(m + 1);
20538
- for (let j = 1; j <= n; j++) {
20539
- curr[0] = j;
20540
- for (let i = 1; i <= m; i++) {
20541
- if (a[i - 1] === b[j - 1]) {
20542
- curr[i] = prev[i - 1];
20543
- } else {
20544
- curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
21451
+ break;
21452
+ case "table":
21453
+ if (block.rows) {
21454
+ if (isFirst) {
21455
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
21456
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
21457
+ isFirst = false;
21458
+ }
21459
+ xml = generateTable(block.rows, theme);
21460
+ }
21461
+ break;
21462
+ case "html_table": {
21463
+ const tbl = generateHtmlTableXml(block.text || "", theme);
21464
+ if (tbl) {
21465
+ if (isFirst) {
21466
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
21467
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
21468
+ isFirst = false;
21469
+ }
21470
+ xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
21471
+ } else {
21472
+ const plain = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
21473
+ xml = plain ? generateParagraph(plain) : "";
21474
+ }
21475
+ break;
20545
21476
  }
20546
21477
  }
20547
- ;
20548
- [prev, curr] = [curr, prev];
21478
+ if (!xml) continue;
21479
+ if (isFirst && block.type !== "table") {
21480
+ xml = xml.replace(
21481
+ /<hp:run charPrIDRef="(\d+)">/,
21482
+ `<hp:run charPrIDRef="$1">${generateSecPr(gongmun)}`
21483
+ );
21484
+ isFirst = false;
21485
+ }
21486
+ paraXmls.push(xml);
20549
21487
  }
20550
- return prev[m];
21488
+ if (paraXmls.length === 0) {
21489
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run></hp:p>`);
21490
+ }
21491
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21492
+ <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
21493
+ ${paraXmls.join("\n ")}
21494
+ </hs:sec>`;
20551
21495
  }
20552
21496
 
20553
21497
  // src/diff/compare.ts
@@ -20665,322 +21609,253 @@ function tableSimilarity(a, b) {
20665
21609
  }
20666
21610
  function diffTableCells(a, b) {
20667
21611
  const maxRows = Math.max(a.rows, b.rows);
20668
- const maxCols = Math.max(a.cols, b.cols);
20669
- const result = [];
20670
- for (let r = 0; r < maxRows; r++) {
20671
- const row = [];
20672
- for (let c = 0; c < maxCols; c++) {
20673
- const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
20674
- const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
20675
- let type;
20676
- if (cellA === void 0) type = "added";
20677
- else if (cellB === void 0) type = "removed";
20678
- else if (cellA === cellB) type = "unchanged";
20679
- else type = "modified";
20680
- row.push({ type, before: cellA, after: cellB });
20681
- }
20682
- result.push(row);
20683
- }
20684
- return result;
20685
- }
20686
-
20687
- // src/roundtrip/patcher.ts
20688
- import JSZip6 from "jszip";
20689
-
20690
- // src/roundtrip/markdown-units.ts
20691
- function splitMarkdownUnits(md2) {
20692
- const lines = md2.split("\n");
20693
- const units = [];
20694
- let i = 0;
20695
- while (i < lines.length) {
20696
- const line = lines[i];
20697
- if (!line.trim()) {
20698
- i++;
20699
- continue;
20700
- }
20701
- if (line.trim().startsWith("<table>")) {
20702
- const collected2 = [];
20703
- let depth = 0;
20704
- while (i < lines.length) {
20705
- const l = lines[i];
20706
- collected2.push(l);
20707
- depth += (l.match(/<table>/g) || []).length;
20708
- depth -= (l.match(/<\/table>/g) || []).length;
20709
- i++;
20710
- if (depth <= 0) break;
20711
- }
20712
- units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20713
- continue;
20714
- }
20715
- if (line.trimStart().startsWith("|")) {
20716
- const collected2 = [];
20717
- while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20718
- collected2.push(lines[i]);
20719
- i++;
20720
- }
20721
- units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20722
- continue;
20723
- }
20724
- if (/^-{3,}\s*$/.test(line.trim())) {
20725
- units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20726
- i++;
20727
- continue;
20728
- }
20729
- if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20730
- units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20731
- i++;
20732
- continue;
20733
- }
20734
- const collected = [];
20735
- while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20736
- collected.push(lines[i].trim());
20737
- i++;
20738
- }
20739
- units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20740
- }
20741
- return units;
20742
- }
20743
- function escapeGfm(text) {
20744
- return text.replace(/~/g, "\\~");
20745
- }
20746
- var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
20747
- function sanitizeText(text) {
20748
- let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
20749
- if (result.length <= 30 && result.includes(" ")) {
20750
- const tokens = result.split(" ");
20751
- const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
20752
- if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
20753
- result = tokens.join("");
20754
- }
20755
- }
20756
- return result;
20757
- }
20758
- function normForMatch(text) {
20759
- return sanitizeText(text).replace(/\s+/g, " ").trim();
20760
- }
20761
- function unescapeGfm(text) {
20762
- return text.replace(/\\~/g, "~");
20763
- }
20764
- function summarize(text) {
20765
- const t = text.replace(/\s+/g, " ").trim();
20766
- return t.length > 80 ? t.slice(0, 77) + "..." : t;
20767
- }
20768
- function replicateGfmTable(table) {
20769
- const { cells, rows: numRows, cols: numCols } = table;
20770
- if (numRows === 0 || numCols === 0) return null;
20771
- if (numRows === 1 && numCols === 1) return null;
20772
- if (numCols === 1) return null;
20773
- const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
20774
- const skip = /* @__PURE__ */ new Set();
20775
- for (let r = 0; r < numRows; r++) {
20776
- for (let c = 0; c < numCols; c++) {
20777
- if (skip.has(`${r},${c}`)) continue;
20778
- const cell = cells[r]?.[c];
20779
- if (!cell) continue;
20780
- display[r][c] = {
20781
- text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
20782
- gridR: r,
20783
- gridC: c
20784
- };
20785
- for (let dr = 0; dr < cell.rowSpan; dr++) {
20786
- for (let dc = 0; dc < cell.colSpan; dc++) {
20787
- if (dr === 0 && dc === 0) continue;
20788
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20789
- }
20790
- }
20791
- c += cell.colSpan - 1;
20792
- }
20793
- }
20794
- const uniqueRows = [];
20795
- let pendingLabelRow = null;
20796
- for (let r = 0; r < display.length; r++) {
20797
- const row = display[r];
20798
- if (row.every((cell) => cell.text === "")) continue;
20799
- const nonEmptyCols = row.filter((cell) => cell.text !== "");
20800
- const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
20801
- if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
20802
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20803
- pendingLabelRow = row;
20804
- continue;
20805
- }
20806
- if (pendingLabelRow) {
20807
- if (row[0].text === "") row[0] = pendingLabelRow[0];
20808
- else uniqueRows.push(pendingLabelRow);
20809
- pendingLabelRow = null;
20810
- }
20811
- uniqueRows.push(row);
20812
- }
20813
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20814
- return uniqueRows.length > 0 ? uniqueRows : null;
20815
- }
20816
- function parseGfmTable(lines) {
20817
- const rows = [];
20818
- for (const line of lines) {
20819
- const trimmed = line.trim();
20820
- if (!trimmed.startsWith("|")) continue;
20821
- const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
20822
- if (cells.length === 0) continue;
20823
- if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
20824
- rows.push(cells);
20825
- }
20826
- return rows;
20827
- }
20828
- function unescapeGfmCell(text) {
20829
- return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
20830
- }
20831
- function replicateCellInnerHtml(cell) {
20832
- if (cell.blocks?.length) {
20833
- return cell.blocks.map((b) => {
20834
- if (b.type === "table" && b.table) {
20835
- const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
20836
- return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
20837
- }
20838
- if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
20839
- const t = sanitizeText(b.text ?? "");
20840
- return t ? t.replace(/\n/g, "<br>") : "";
20841
- }).filter(Boolean).join("<br>");
20842
- }
20843
- return sanitizeText(cell.text).replace(/\n/g, "<br>");
20844
- }
20845
- function replicateTableToHtml(table) {
20846
- const rows = replicateHtmlTable(table);
20847
- const lines = ["<table>"];
20848
- for (let r = 0; r < rows.length; r++) {
20849
- const tag = rows[r].tag;
20850
- const rowHtml = rows[r].cells.map((cell) => {
20851
- const attrs = [];
20852
- if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
20853
- if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
20854
- const attrStr = attrs.length ? " " + attrs.join(" ") : "";
20855
- return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
20856
- });
20857
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
20858
- }
20859
- lines.push("</table>");
20860
- return lines.join("\n");
20861
- }
20862
- function replicateHtmlTable(table) {
20863
- const { cells, rows: numRows, cols: numCols } = table;
20864
- const skip = /* @__PURE__ */ new Set();
20865
- const result = [];
20866
- for (let r = 0; r < numRows; r++) {
20867
- const tag = r === 0 ? "th" : "td";
20868
- const rowCells = [];
20869
- for (let c = 0; c < numCols; c++) {
20870
- if (skip.has(`${r},${c}`)) continue;
20871
- const cell = cells[r]?.[c];
20872
- if (!cell) continue;
20873
- for (let dr = 0; dr < cell.rowSpan; dr++) {
20874
- for (let dc = 0; dc < cell.colSpan; dc++) {
20875
- if (dr === 0 && dc === 0) continue;
20876
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20877
- }
20878
- }
20879
- rowCells.push({
20880
- inner: replicateCellInnerHtml(cell),
20881
- colSpan: cell.colSpan,
20882
- rowSpan: cell.rowSpan,
20883
- gridR: r,
20884
- gridC: c
20885
- });
21612
+ const maxCols = Math.max(a.cols, b.cols);
21613
+ const result = [];
21614
+ for (let r = 0; r < maxRows; r++) {
21615
+ const row = [];
21616
+ for (let c = 0; c < maxCols; c++) {
21617
+ const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
21618
+ const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
21619
+ let type;
21620
+ if (cellA === void 0) type = "added";
21621
+ else if (cellB === void 0) type = "removed";
21622
+ else if (cellA === cellB) type = "unchanged";
21623
+ else type = "modified";
21624
+ row.push({ type, before: cellA, after: cellB });
20886
21625
  }
20887
- if (rowCells.length) result.push({ tag, cells: rowCells });
21626
+ result.push(row);
20888
21627
  }
20889
21628
  return result;
20890
21629
  }
20891
- function parseHtmlTable(raw) {
20892
- const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
20893
- let depth = 0;
20894
- let currentRow = null;
20895
- let cellStart = -1;
20896
- let cellInfo = null;
20897
- const rows = [];
20898
- let m;
20899
- while ((m = re.exec(raw)) !== null) {
20900
- const isClose = m[1] === "/";
20901
- const tag = m[2].toLowerCase();
20902
- const attrs = m[3] || "";
20903
- if (tag === "table") {
20904
- depth += isClose ? -1 : 1;
20905
- if (depth < 0) return null;
20906
- continue;
21630
+
21631
+ // src/roundtrip/patcher.ts
21632
+ import JSZip6 from "jszip";
21633
+
21634
+ // src/roundtrip/table-rows.ts
21635
+ var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
21636
+ var TAG_AT_RE = /<[A-Za-z0-9_:]+(?:"[^"]*"|'[^']*'|[^>"'])*>/y;
21637
+ function patchTableRows(input) {
21638
+ const { table, scanTable, ctx, skip, origKeys, editedKeys } = input;
21639
+ const xml = ctx.scans[scanTable.sectionIndex]?.xml;
21640
+ if (!xml) return skip("\uC139\uC158 XML \uB9E4\uD551 \uC2E4\uD328");
21641
+ const numRows = table.rows;
21642
+ if (origKeys.length !== numRows) return skip("\uD45C \uD589 \uC88C\uD45C \uBD88\uC77C\uCE58 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21643
+ if (scanTable.rows.length !== numRows || scanTable.rowRanges.length !== numRows) {
21644
+ return skip("\uD45C \uD589 \uAD6C\uC870 \uC88C\uD45C \uBD88\uC77C\uCE58 (\uBE48 \uD589/\uBCD1\uD569 \uC18C\uC2E4) \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21645
+ }
21646
+ for (let r = 0; r < numRows; r++) {
21647
+ if (scanTable.rows[r].some((c) => c.rowAddr !== r)) {
21648
+ return skip("\uD589 \uC8FC\uC18C \uBE44\uC5F0\uC18D \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
20907
21649
  }
20908
- if (depth !== 1) continue;
20909
- if (tag === "tr") {
20910
- if (!isClose) currentRow = [];
20911
- else if (currentRow) {
20912
- rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
20913
- currentRow = null;
21650
+ }
21651
+ const allCells = scanTable.rows.flat();
21652
+ const explicitAddr = (c) => !!c.addrTagRange && /\browAddr\s*=\s*"/.test(xml.slice(c.addrTagRange.start, c.addrTagRange.end));
21653
+ const explicitCount = allCells.filter(explicitAddr).length;
21654
+ if (explicitCount !== 0 && explicitCount !== allCells.length) {
21655
+ return skip("\uC140 \uC8FC\uC18C(cellAddr) \uD45C\uAE30 \uD63C\uC7AC \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21656
+ }
21657
+ const hasExplicitAddr = explicitCount > 0;
21658
+ const pairs = alignUnits(origKeys, editedKeys);
21659
+ const seq = [];
21660
+ let lastOrig = -1;
21661
+ for (const [oi, ei] of pairs) {
21662
+ if (oi !== null && ei !== null) {
21663
+ seq.push({ kind: "keep", oi, ei });
21664
+ lastOrig = oi;
21665
+ } else if (oi !== null) {
21666
+ seq.push({ kind: "del", oi });
21667
+ lastOrig = oi;
21668
+ } else if (ei !== null) seq.push({ kind: "ins", ei, insertAt: lastOrig + 1 });
21669
+ }
21670
+ const dels = seq.filter((e) => e.kind === "del");
21671
+ const inss = seq.filter((e) => e.kind === "ins");
21672
+ const keeps = seq.filter((e) => e.kind === "keep");
21673
+ if (dels.length === 0 && inss.length === 0) {
21674
+ return skip("\uD45C \uD589 \uC815\uB82C \uC2E4\uD328 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21675
+ }
21676
+ if (keeps.length + inss.length === 0) return skip("\uBAA8\uB4E0 \uD589 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
21677
+ if (keeps.length === 0) return skip("\uD589 \uC804\uBA74 \uAD50\uCCB4 \u2014 \uC11C\uC2DD \uAE30\uC900 \uD589\uC774 \uC5C6\uC5B4 \uBBF8\uC9C0\uC6D0");
21678
+ const spans = allCells.filter((c) => (c.rowSpan ?? 1) > 1);
21679
+ for (const d of dels) {
21680
+ if (spans.some((s) => s.rowAddr <= d.oi && s.rowAddr + s.rowSpan > d.oi)) {
21681
+ return skip(`\uD45C ${d.oi + 1}\uD589 \uC0AD\uC81C\uAC00 \uC138\uB85C \uBCD1\uD569\uACFC \uACB9\uCE68 \u2014 \uBBF8\uC9C0\uC6D0`);
21682
+ }
21683
+ }
21684
+ for (const ins of inss) {
21685
+ const p = ins.insertAt;
21686
+ if (spans.some((s) => s.rowAddr < p && s.rowAddr + s.rowSpan > p)) {
21687
+ return skip("\uD589 \uC0BD\uC785 \uC704\uCE58\uAC00 \uC138\uB85C \uBCD1\uD569 \uB0B4\uBD80 \u2014 \uBBF8\uC9C0\uC6D0");
21688
+ }
21689
+ }
21690
+ for (const d of dels) {
21691
+ const rr = scanTable.rowRanges[d.oi];
21692
+ if (ROW_OBJECT_RE.test(xml.slice(rr.start, rr.end))) {
21693
+ return skip(`\uD45C ${d.oi + 1}\uD589\uC5D0 \uAC1C\uCCB4(\uC911\uCCA9\uD45C/\uC774\uBBF8\uC9C0/\uD544\uB4DC) \uD3EC\uD568 \u2014 \uD589 \uC0AD\uC81C \uBBF8\uC9C0\uC6D0`);
21694
+ }
21695
+ }
21696
+ const keptSet = new Set(keeps.map((k) => k.oi));
21697
+ const insertPlans = [];
21698
+ for (const ins of inss) {
21699
+ const p = ins.insertAt;
21700
+ const kept = [...keptSet].sort((a, b) => a - b);
21701
+ let template = -1;
21702
+ for (const k of kept) {
21703
+ if (k < p) template = k;
21704
+ }
21705
+ if (template <= 0 && p >= 1) {
21706
+ const following = kept.find((k) => k >= 1);
21707
+ if (following !== void 0) template = following;
21708
+ }
21709
+ if (template < 0) template = kept[0];
21710
+ const cells = input.editedCells(ins.ei);
21711
+ if (!cells) return skip("\uC0BD\uC785 \uD589\uC5D0 \uC774\uBBF8\uC9C0/\uC911\uCCA9\uD45C \uD3EC\uD568 \u2014 \uD589 \uCD94\uAC00 \uBBF8\uC9C0\uC6D0");
21712
+ for (const cell of cells) {
21713
+ const unstable = cell.lines.find((l) => sanitizeText(l) !== l);
21714
+ if (unstable !== void 0) return skip("\uC0BD\uC785 \uD589\uC5D0 \uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uBBF8\uC9C0\uC6D0");
21715
+ }
21716
+ const tmplCells = scanTable.rows[template];
21717
+ const rr = scanTable.rowRanges[template];
21718
+ if (ROW_OBJECT_RE.test(xml.slice(rr.start, rr.end))) {
21719
+ return skip("\uC11C\uC2DD \uAE30\uC900 \uD589\uC5D0 \uAC1C\uCCB4(\uC911\uCCA9\uD45C/\uC774\uBBF8\uC9C0/\uD544\uB4DC) \uD3EC\uD568 \u2014 \uD589 \uCD94\uAC00 \uBBF8\uC9C0\uC6D0");
21720
+ }
21721
+ if (tmplCells.some((c) => c.rowSpan > 1)) return skip("\uC11C\uC2DD \uAE30\uC900 \uD589\uC5D0 \uC138\uB85C \uBCD1\uD569 \u2014 \uD589 \uCD94\uAC00 \uBBF8\uC9C0\uC6D0");
21722
+ const tmplWidth = tmplCells.reduce((s, c) => s + c.colSpan, 0);
21723
+ if (tmplWidth !== table.cols) return skip("\uC11C\uC2DD \uAE30\uC900 \uD589\uC774 \uACA9\uC790 \uC804\uCCB4\uB97C \uB36E\uC9C0 \uC54A\uC74C \u2014 \uD589 \uCD94\uAC00 \uBBF8\uC9C0\uC6D0");
21724
+ if (cells.length !== tmplCells.length) {
21725
+ return skip(`\uC0BD\uC785 \uD589 \uC140 \uC218(${cells.length}) \u2260 \uAE30\uC900 \uD589 \uC140 \uC218(${tmplCells.length}) \u2014 \uBBF8\uC9C0\uC6D0`);
21726
+ }
21727
+ for (let i = 0; i < cells.length; i++) {
21728
+ if (cells[i].rowSpan !== 1 || cells[i].colSpan !== tmplCells[i].colSpan) {
21729
+ return skip("\uC0BD\uC785 \uD589 \uBCD1\uD569 \uAD6C\uC870\uAC00 \uAE30\uC900 \uD589\uACFC \uB2E4\uB984 \u2014 \uBBF8\uC9C0\uC6D0");
20914
21730
  }
20915
- } else {
20916
- if (!isClose) {
20917
- const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20918
- const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20919
- cellStart = m.index + m[0].length;
20920
- cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
20921
- } else if (cellStart >= 0 && cellInfo && currentRow) {
20922
- currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
20923
- cellStart = -1;
20924
- cellInfo = null;
21731
+ }
21732
+ insertPlans.push({ entry: ins, template, cells });
21733
+ }
21734
+ const net = inss.length - dels.length;
21735
+ TAG_AT_RE.lastIndex = scanTable.start;
21736
+ const tblOpen = TAG_AT_RE.exec(xml);
21737
+ if (!tblOpen || tblOpen.index !== scanTable.start) return skip("\uD45C \uC5EC\uB294 \uD0DC\uADF8 \uD574\uC11D \uC2E4\uD328");
21738
+ const rowCntM = tblOpen[0].match(/\browCnt\s*=\s*"(\d+)"/);
21739
+ if (!rowCntM || rowCntM.index === void 0) return skip("\uD45C rowCnt \uC18D\uC131 \uC5C6\uC74C \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21740
+ if (parseInt(rowCntM[1], 10) !== numRows) return skip("rowCnt\uC640 \uC2E4\uC81C \uD589 \uC218 \uBD88\uC77C\uCE58 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21741
+ const finalIndex = /* @__PURE__ */ new Map();
21742
+ {
21743
+ let fi = 0;
21744
+ for (const e of seq) {
21745
+ if (e.kind !== "del") finalIndex.set(e, fi++);
21746
+ }
21747
+ }
21748
+ const splices = [];
21749
+ let applied = 0;
21750
+ for (const d of dels) {
21751
+ const rr = scanTable.rowRanges[d.oi];
21752
+ splices.push({ start: rr.start, end: rr.end, replacement: "" });
21753
+ applied++;
21754
+ }
21755
+ const fragmentsByAnchor = /* @__PURE__ */ new Map();
21756
+ let heightDelta = 0;
21757
+ for (const plan of insertPlans) {
21758
+ const finalRow = finalIndex.get(plan.entry);
21759
+ const fragment = buildRowFragment(xml, scanTable, plan.template, plan.cells, finalRow, hasExplicitAddr, ctx);
21760
+ if (fragment === null) return skip("\uD589 \uBCF5\uC81C \uC2E4\uD328 (\uC140 \uBB38\uB2E8 \uAD6C\uC870 \uBBF8\uC9C0\uC6D0)");
21761
+ const p = plan.entry.insertAt;
21762
+ const anchor = p === 0 ? scanTable.rowRanges[0].start : scanTable.rowRanges[p - 1].end;
21763
+ let list = fragmentsByAnchor.get(anchor);
21764
+ if (!list) fragmentsByAnchor.set(anchor, list = []);
21765
+ list.push(fragment);
21766
+ heightDelta += rowHeightOf(fragment);
21767
+ applied++;
21768
+ }
21769
+ for (const [anchor, fragments] of fragmentsByAnchor) {
21770
+ splices.push({ start: anchor, end: anchor, replacement: fragments.join("") });
21771
+ }
21772
+ for (const d of dels) {
21773
+ const rr = scanTable.rowRanges[d.oi];
21774
+ heightDelta -= rowHeightOf(xml.slice(rr.start, rr.end));
21775
+ }
21776
+ if (hasExplicitAddr) {
21777
+ for (const k of keeps) {
21778
+ const fi = finalIndex.get(k);
21779
+ if (fi === k.oi) continue;
21780
+ for (const cell of scanTable.rows[k.oi]) {
21781
+ const sp = rowAddrRewrite(xml, cell, fi);
21782
+ if (sp) splices.push(sp);
20925
21783
  }
20926
21784
  }
20927
21785
  }
20928
- if (depth !== 0) return null;
20929
- return rows;
20930
- }
20931
- function htmlCellInnerToLines(inner) {
20932
- let hadNonText = false;
20933
- let work = inner;
20934
- if (/<table[\s>]/i.test(work)) {
20935
- hadNonText = true;
20936
- work = removeNestedTables(work);
21786
+ {
21787
+ const valStart = scanTable.start + rowCntM.index + rowCntM[0].indexOf('"') + 1;
21788
+ splices.push({ start: valStart, end: valStart + rowCntM[1].length, replacement: String(numRows + net) });
20937
21789
  }
20938
- if (/<img\s/i.test(work)) {
20939
- hadNonText = true;
20940
- work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
21790
+ if (heightDelta !== 0) {
21791
+ const sp = tableSzHeightSplice(xml, scanTable, tblOpen[0].length, heightDelta);
21792
+ if (sp) splices.push(sp);
20941
21793
  }
20942
- const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
20943
- return { lines, hadNonText };
21794
+ ctx.sectionSplices[scanTable.sectionIndex].push(...splices);
21795
+ for (const k of keeps) {
21796
+ if (origKeys[k.oi] !== editedKeys[k.ei]) applied += input.patchMatched(k.oi, k.ei);
21797
+ }
21798
+ return applied;
20944
21799
  }
20945
- function extractTopLevelTables(html) {
20946
- const result = [];
20947
- let depth = 0;
20948
- let start = -1;
20949
- const re = /<(\/?)table(?:[\s>]|>)/gi;
20950
- let m;
20951
- while ((m = re.exec(html)) !== null) {
20952
- if (m[1] !== "/") {
20953
- if (depth === 0) start = m.index;
20954
- depth++;
20955
- } else {
20956
- depth--;
20957
- if (depth === 0 && start >= 0) {
20958
- result.push(html.slice(start, m.index + m[0].length));
20959
- start = -1;
20960
- }
20961
- if (depth < 0) depth = 0;
21800
+ var FRAG_OPEN = "<hp:tbl>";
21801
+ var FRAG_CLOSE = "</hp:tbl>";
21802
+ function buildRowFragment(xml, scanTable, template, cells, finalRow, hasExplicitAddr, ctx) {
21803
+ const rr = scanTable.rowRanges[template];
21804
+ const wrapped = FRAG_OPEN + xml.slice(rr.start, rr.end) + FRAG_CLOSE;
21805
+ const scan = scanSectionXml(wrapped, 0);
21806
+ const row = scan.tables[0]?.rows[0];
21807
+ if (!row || row.length !== cells.length) return null;
21808
+ const splices = allLinesegRemovalSplices(wrapped);
21809
+ for (let i = 0; i < cells.length; i++) {
21810
+ const paras = row[i].paragraphs;
21811
+ let lines = cells[i].lines;
21812
+ if (lines.length > 0 && paras.length === 0) return null;
21813
+ if (lines.length > paras.length) {
21814
+ lines = [...lines.slice(0, paras.length - 1), lines.slice(paras.length - 1).join(" ")];
21815
+ ctx.skipped.push({ reason: "\uC0BD\uC785 \uD589 \uC140\uC758 \uC904 \uC218\uAC00 \uBB38\uB2E8 \uC218 \uCD08\uACFC \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(cells[i].lines.join(" ")), partial: true });
21816
+ }
21817
+ for (let p = 0; p < paras.length; p++) {
21818
+ const sp = buildParagraphSplices(paras[p], lines[p] ?? "", wrapped);
21819
+ if (sp === null) return null;
21820
+ splices.push(...sp);
21821
+ }
21822
+ if (hasExplicitAddr) {
21823
+ const sp = rowAddrRewrite(wrapped, row[i], finalRow);
21824
+ if (sp) splices.push(sp);
20962
21825
  }
20963
21826
  }
20964
- return result;
21827
+ const patched = applySplices(wrapped, splices);
21828
+ return patched.slice(FRAG_OPEN.length, patched.length - FRAG_CLOSE.length);
20965
21829
  }
20966
- function removeNestedTables(html) {
20967
- let result = "";
20968
- let depth = 0;
20969
- const re = /<(\/?)table(?:[\s>]|>)/gi;
20970
- let last = 0;
20971
- let m;
20972
- while ((m = re.exec(html)) !== null) {
20973
- if (m[1] !== "/") {
20974
- if (depth === 0) result += html.slice(last, m.index);
20975
- depth++;
20976
- } else {
20977
- depth--;
20978
- if (depth === 0) last = m.index + m[0].length;
20979
- if (depth < 0) depth = 0;
20980
- }
21830
+ function rowAddrRewrite(xml, cell, newRow) {
21831
+ if (!cell.addrTagRange) return null;
21832
+ const tag = xml.slice(cell.addrTagRange.start, cell.addrTagRange.end);
21833
+ const m = tag.match(/\browAddr\s*=\s*"(\d+)"/);
21834
+ if (!m || m.index === void 0) return null;
21835
+ if (parseInt(m[1], 10) === newRow) return null;
21836
+ const valStart = cell.addrTagRange.start + m.index + m[0].indexOf('"') + 1;
21837
+ return { start: valStart, end: valStart + m[1].length, replacement: String(newRow) };
21838
+ }
21839
+ function rowHeightOf(fragment) {
21840
+ let max = 0;
21841
+ for (const m of fragment.matchAll(/<(?:[A-Za-z0-9_]+:)?cellSz\b(?:"[^"]*"|'[^']*'|[^>"'])*>/g)) {
21842
+ const h = m[0].match(/\bheight\s*=\s*"(\d+)"/);
21843
+ if (h) max = Math.max(max, parseInt(h[1], 10));
20981
21844
  }
20982
- if (depth === 0) result += html.slice(last);
20983
- return result;
21845
+ return max;
21846
+ }
21847
+ function tableSzHeightSplice(xml, scanTable, tblOpenLen, delta) {
21848
+ const from = scanTable.start + tblOpenLen;
21849
+ const to = scanTable.rowRanges[0]?.start ?? from;
21850
+ const slice = xml.slice(from, to);
21851
+ const szM = slice.match(/<(?:[A-Za-z0-9_]+:)?sz\b(?:"[^"]*"|'[^']*'|[^>"'])*>/);
21852
+ if (!szM || szM.index === void 0) return null;
21853
+ const hM = szM[0].match(/\bheight\s*=\s*"(\d+)"/);
21854
+ if (!hM || hM.index === void 0) return null;
21855
+ const oldH = parseInt(hM[1], 10);
21856
+ const newH = Math.max(0, oldH + delta);
21857
+ const valStart = from + szM.index + hM.index + hM[0].indexOf('"') + 1;
21858
+ return { start: valStart, end: valStart + hM[1].length, replacement: String(newH) };
20984
21859
  }
20985
21860
 
20986
21861
  // src/roundtrip/table-patch.ts
@@ -20992,37 +21867,89 @@ function patchGfmTable(table, scanTable, orig, edited, ctx, skip) {
20992
21867
  if (replica.length !== origRows.length || replica.some((row, r) => row.length !== origRows[r].length || row.some((c, j) => c.text !== origRows[r][j]))) {
20993
21868
  return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
20994
21869
  }
20995
- if (editedRows.length !== origRows.length) return skip("\uD45C \uD589 \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
21870
+ if (editedRows.length !== origRows.length) {
21871
+ return patchGfmTableRows(table, scanTable, origRows, editedRows, replica, ctx, skip);
21872
+ }
20996
21873
  let applied = 0;
20997
21874
  for (let r = 0; r < origRows.length; r++) {
20998
- if (editedRows[r].length !== origRows[r].length) {
20999
- skip(`\uD45C ${r + 1}\uD589 \uC5F4 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21875
+ applied += patchGfmRowPair(table, scanTable, origRows, editedRows, replica, r, r, ctx, skip);
21876
+ }
21877
+ return applied;
21878
+ }
21879
+ function patchGfmRowPair(table, scanTable, origRows, editedRows, replica, r, er, ctx, skip) {
21880
+ if (editedRows[er].length !== origRows[r].length) {
21881
+ skip(`\uD45C ${r + 1}\uD589 \uC5F4 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21882
+ return 0;
21883
+ }
21884
+ let applied = 0;
21885
+ for (let c = 0; c < origRows[r].length; c++) {
21886
+ if (origRows[r][c] === editedRows[er][c]) continue;
21887
+ const { gridR, gridC } = replica[r][c];
21888
+ const origTokens = extractCellTokens(origRows[r][c]);
21889
+ const editedTokens = extractCellTokens(editedRows[er][c]);
21890
+ if (origTokens !== editedTokens) {
21891
+ skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21000
21892
  continue;
21001
21893
  }
21002
- for (let c = 0; c < origRows[r].length; c++) {
21003
- if (origRows[r][c] === editedRows[r][c]) continue;
21004
- const { gridR, gridC } = replica[r][c];
21005
- const origTokens = extractCellTokens(origRows[r][c]);
21006
- const editedTokens = extractCellTokens(editedRows[r][c]);
21007
- if (origTokens !== editedTokens) {
21008
- skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21009
- continue;
21010
- }
21011
- const newLines = unescapeGfmCell(stripCellTokens(editedRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
21012
- const origLines = unescapeGfmCell(stripCellTokens(origRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
21013
- const n = applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, origRows[r][c], editedRows[r][c], origLines.length);
21014
- if (n > 0 && origTokens) {
21015
- ctx.skipped.push({
21016
- reason: "\uC140 \uB0B4 \uC774\uBBF8\uC9C0\xB7\uD14D\uC2A4\uD2B8 \uD63C\uC7AC \u2014 \uD14D\uC2A4\uD2B8\uB9CC \uC801\uC6A9 (\uC774\uBBF8\uC9C0 \uC778\uC811 \uBC30\uCE58\uB294 <br> \uBD84\uB9AC\uB85C \uC7AC\uD604\uB428)",
21017
- before: summarize(origRows[r][c]),
21018
- after: summarize(editedRows[r][c])
21019
- });
21020
- }
21021
- applied += n;
21894
+ const newLines = unescapeGfmCell(stripCellTokens(editedRows[er][c])).split("\n").map((s) => s.trim()).filter(Boolean);
21895
+ const origLines = unescapeGfmCell(stripCellTokens(origRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
21896
+ const n = applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, origRows[r][c], editedRows[er][c], origLines.length);
21897
+ if (n > 0 && origTokens) {
21898
+ ctx.skipped.push({
21899
+ reason: "\uC140 \uB0B4 \uC774\uBBF8\uC9C0\xB7\uD14D\uC2A4\uD2B8 \uD63C\uC7AC \u2014 \uD14D\uC2A4\uD2B8\uB9CC \uC801\uC6A9 (\uC774\uBBF8\uC9C0 \uC778\uC811 \uBC30\uCE58\uB294 <br> \uBD84\uB9AC\uB85C \uC7AC\uD604\uB428)",
21900
+ before: summarize(origRows[r][c]),
21901
+ after: summarize(editedRows[er][c]),
21902
+ partial: true
21903
+ });
21022
21904
  }
21905
+ applied += n;
21023
21906
  }
21024
21907
  return applied;
21025
21908
  }
21909
+ function patchGfmTableRows(table, scanTable, origRows, editedRows, replica, ctx, skip) {
21910
+ if (replica.length !== table.rows || replica.some((row, r) => row.some((c) => c.gridR !== r))) {
21911
+ return skip("\uD45C \uB80C\uB354 \uD589\uACFC \uACA9\uC790 \uD589 \uBD88\uC77C\uCE58 (\uBE48 \uD589/\uBCD1\uD569) \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21912
+ }
21913
+ if (table.cells.some((row) => row.some((c) => c && (c.colSpan > 1 || c.rowSpan > 1)))) {
21914
+ return skip("\uBCD1\uD569 \uC140 \uD45C \u2014 GFM \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21915
+ }
21916
+ if (!gfmRenderStable(editedRows, table.cols)) {
21917
+ return skip("\uD589 \uBCC0\uACBD \uACB0\uACFC\uAC00 \uD45C \uB80C\uB354\uC5D0\uC11C \uBCC0\uD615\uB428 (\uBE48 \uD589/\uCCAB \uC5F4 \uC804\uD30C/\uC5F4 \uC218 \uBD88\uC77C\uCE58) \u2014 \uBBF8\uC9C0\uC6D0");
21918
+ }
21919
+ const key = (row) => row.join("\0");
21920
+ return patchTableRows({
21921
+ table,
21922
+ scanTable,
21923
+ ctx,
21924
+ skip,
21925
+ origKeys: origRows.map(key),
21926
+ editedKeys: editedRows.map(key),
21927
+ editedCells: (ei) => {
21928
+ const cells = [];
21929
+ for (const cellMd of editedRows[ei]) {
21930
+ if (extractCellTokens(cellMd)) return null;
21931
+ const lines = unescapeGfmCell(cellMd).split("\n").map((s) => s.trim()).filter(Boolean);
21932
+ cells.push({ lines, colSpan: 1, rowSpan: 1 });
21933
+ }
21934
+ return cells;
21935
+ },
21936
+ patchMatched: (oi, ei) => patchGfmRowPair(table, scanTable, origRows, editedRows, replica, oi, ei, ctx, skip)
21937
+ });
21938
+ }
21939
+ function gfmRenderStable(editedRows, cols) {
21940
+ const sim = {
21941
+ rows: editedRows.length,
21942
+ cols,
21943
+ hasHeader: editedRows.length > 1,
21944
+ cells: editedRows.map((row) => {
21945
+ const padded = row.length < cols ? [...row, ...Array(cols - row.length).fill("")] : row;
21946
+ return padded.map((md2) => ({ text: unescapeGfmCell(md2), colSpan: 1, rowSpan: 1 }));
21947
+ })
21948
+ };
21949
+ const replica = replicateGfmTable(sim);
21950
+ if (!replica || replica.length !== editedRows.length) return false;
21951
+ return replica.every((row, r) => row.length === editedRows[r].length && row.every((c, j) => c.text === editedRows[r][j]));
21952
+ }
21026
21953
  function patchHtmlTable(table, scanTable, orig, edited, ctx, skip) {
21027
21954
  return patchHtmlTableRaw(table, scanTable, orig.raw, edited.raw, ctx, skip, 0);
21028
21955
  }
@@ -21036,46 +21963,77 @@ function patchHtmlTableRaw(table, scanTable, origRaw, editedRaw, ctx, skip, dept
21036
21963
  }
21037
21964
  const editedRows = parseHtmlTable(editedRaw);
21038
21965
  if (!editedRows) return skip("\uD3B8\uC9D1\uB41C HTML \uD45C \uD30C\uC2F1 \uC2E4\uD328");
21039
- if (editedRows.length !== replica.length) return skip("\uD45C \uD589 \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
21966
+ if (editedRows.length !== replica.length) {
21967
+ return patchHtmlTableRows(table, scanTable, replica, editedRows, ctx, skip, depth);
21968
+ }
21040
21969
  let applied = 0;
21041
21970
  for (let r = 0; r < replica.length; r++) {
21042
- if (editedRows[r].cells.length !== replica[r].cells.length) {
21043
- skip(`\uD45C ${r + 1}\uD589 \uC140 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21971
+ applied += patchHtmlRowPair(table, scanTable, replica, editedRows, r, r, ctx, skip, depth);
21972
+ }
21973
+ return applied;
21974
+ }
21975
+ function patchHtmlRowPair(table, scanTable, replica, editedRows, r, er, ctx, skip, depth) {
21976
+ if (editedRows[er].cells.length !== replica[r].cells.length) {
21977
+ skip(`\uD45C ${r + 1}\uD589 \uC140 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21978
+ return 0;
21979
+ }
21980
+ let applied = 0;
21981
+ for (let c = 0; c < replica[r].cells.length; c++) {
21982
+ const oc = replica[r].cells[c];
21983
+ const ec = editedRows[er].cells[c];
21984
+ if (oc.colSpan !== ec.colSpan || oc.rowSpan !== ec.rowSpan) {
21985
+ skip(`\uC140 \uBCD1\uD569(colspan/rowspan) \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21044
21986
  continue;
21045
21987
  }
21046
- for (let c = 0; c < replica[r].cells.length; c++) {
21047
- const oc = replica[r].cells[c];
21048
- const ec = editedRows[r].cells[c];
21049
- if (oc.colSpan !== ec.colSpan || oc.rowSpan !== ec.rowSpan) {
21050
- skip(`\uC140 \uBCD1\uD569(colspan/rowspan) \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21988
+ if (oc.inner === ec.inner) continue;
21989
+ const origContent = htmlCellInnerToLines(oc.inner);
21990
+ const editedContent = htmlCellInnerToLines(ec.inner);
21991
+ if (origContent.hadNonText || editedContent.hadNonText) {
21992
+ if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
21993
+ skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21051
21994
  continue;
21052
21995
  }
21053
- if (oc.inner === ec.inner) continue;
21054
- const origContent = htmlCellInnerToLines(oc.inner);
21055
- const editedContent = htmlCellInnerToLines(ec.inner);
21056
- if (origContent.hadNonText || editedContent.hadNonText) {
21057
- if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
21058
- skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21059
- continue;
21060
- }
21061
- const origTables = extractTopLevelTables(oc.inner);
21062
- const editedTables = extractTopLevelTables(ec.inner);
21063
- if (origTables.length !== editedTables.length) {
21064
- skip("\uC140 \uB0B4 \uC911\uCCA9\uD45C \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
21065
- continue;
21066
- }
21067
- if (origTables.join("\n") !== editedTables.join("\n")) {
21068
- applied += patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth);
21069
- }
21996
+ const origTables = extractTopLevelTables(oc.inner);
21997
+ const editedTables = extractTopLevelTables(ec.inner);
21998
+ if (origTables.length !== editedTables.length) {
21999
+ skip("\uC140 \uB0B4 \uC911\uCCA9\uD45C \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
22000
+ continue;
21070
22001
  }
21071
- if (origContent.lines.join("\n") !== editedContent.lines.join("\n")) {
21072
- const newLines = editedContent.lines.map((l) => unescapeGfm(l));
21073
- applied += applyCellEdit(table, scanTable, oc.gridR, oc.gridC, newLines, ctx, oc.inner, ec.inner, origContent.lines.length);
22002
+ if (origTables.join("\n") !== editedTables.join("\n")) {
22003
+ applied += patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth);
21074
22004
  }
21075
22005
  }
22006
+ if (origContent.lines.join("\n") !== editedContent.lines.join("\n")) {
22007
+ const newLines = editedContent.lines.map((l) => unescapeGfm(l));
22008
+ applied += applyCellEdit(table, scanTable, oc.gridR, oc.gridC, newLines, ctx, oc.inner, ec.inner, origContent.lines.length);
22009
+ }
21076
22010
  }
21077
22011
  return applied;
21078
22012
  }
22013
+ function patchHtmlTableRows(table, scanTable, replica, editedRows, ctx, skip, depth) {
22014
+ if (replica.length !== table.rows || replica.some((row, r) => row.cells.some((c) => c.gridR !== r))) {
22015
+ return skip("\uD45C \uB80C\uB354 \uD589\uACFC \uACA9\uC790 \uD589 \uBD88\uC77C\uCE58 (\uBCD1\uD569 \uC18C\uC2E4 \uD589) \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
22016
+ }
22017
+ const key = (row) => row.cells.map((c) => `${c.colSpan}x${c.rowSpan}:${c.inner}`).join("\0");
22018
+ return patchTableRows({
22019
+ table,
22020
+ scanTable,
22021
+ ctx,
22022
+ skip,
22023
+ origKeys: replica.map(key),
22024
+ editedKeys: editedRows.map(key),
22025
+ editedCells: (ei) => {
22026
+ const cells = [];
22027
+ for (const cell of editedRows[ei].cells) {
22028
+ const content = htmlCellInnerToLines(cell.inner);
22029
+ if (content.hadNonText) return null;
22030
+ cells.push({ lines: content.lines.map((l) => unescapeGfm(l)), colSpan: cell.colSpan, rowSpan: cell.rowSpan });
22031
+ }
22032
+ return cells;
22033
+ },
22034
+ patchMatched: (oi, ei) => patchHtmlRowPair(table, scanTable, replica, editedRows, oi, ei, ctx, skip, depth)
22035
+ });
22036
+ }
21079
22037
  function patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth) {
21080
22038
  const irCell = table.cells[oc.gridR]?.[oc.gridC];
21081
22039
  const scanCell = scanTable.cellByAnchor.get(`${oc.gridR},${oc.gridC}`);
@@ -21175,7 +22133,7 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21175
22133
  splices.push(...sp);
21176
22134
  sectionIndex = target.sectionIndex;
21177
22135
  if (newLines.length > 1) {
21178
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uD55C \uBB38\uB2E8\uC73C\uB85C \uBCD1\uD569 \uC801\uC6A9", after: summarize(after) });
22136
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uD55C \uBB38\uB2E8\uC73C\uB85C \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
21179
22137
  }
21180
22138
  } else {
21181
22139
  const assigned = [];
@@ -21187,7 +22145,9 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21187
22145
  }
21188
22146
  }
21189
22147
  if (newLines.length > nonEmpty.length) {
21190
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after) });
22148
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
22149
+ } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
22150
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before), after: summarize(after), partial: true });
21191
22151
  }
21192
22152
  for (let i = 0; i < nonEmpty.length; i++) {
21193
22153
  if (assigned[i] === nonEmpty[i].text || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].text)) continue;
@@ -21360,7 +22320,8 @@ async function patchHwpx(original, editedMarkdown, options) {
21360
22320
  try {
21361
22321
  for (let i = 0; i < scans.length; i++) {
21362
22322
  if (sectionSplices[i].length === 0) continue;
21363
- sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml));
22323
+ const claimed = sectionSplices[i].filter((s) => s.end > s.start);
22324
+ sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml).filter((ls) => !claimed.some((c) => ls.start >= c.start && ls.end <= c.end)));
21364
22325
  const newXml = applySplices(scans[i].xml, sectionSplices[i]);
21365
22326
  replacements.set(sectionPaths[i], encoder.encode(newXml));
21366
22327
  }
@@ -21428,82 +22389,6 @@ function buildTableOrdinals(blocks) {
21428
22389
  }
21429
22390
  return map;
21430
22391
  }
21431
- function alignUnits(a, b) {
21432
- const m = a.length, n = b.length;
21433
- if (m * n > 4e6) {
21434
- const result2 = [];
21435
- let pre = 0;
21436
- while (pre < m && pre < n && a[pre] === b[pre]) {
21437
- result2.push([pre, pre]);
21438
- pre++;
21439
- }
21440
- let suf = 0;
21441
- while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
21442
- const aMid = m - pre - suf, bMid = n - pre - suf;
21443
- if (aMid === bMid) {
21444
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
21445
- } else {
21446
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
21447
- for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
21448
- }
21449
- for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
21450
- return result2;
21451
- }
21452
- const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
21453
- for (let i2 = 1; i2 <= m; i2++) {
21454
- for (let j2 = 1; j2 <= n; j2++) {
21455
- 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]);
21456
- }
21457
- }
21458
- const matches = [];
21459
- let i = m, j = n;
21460
- while (i > 0 && j > 0) {
21461
- if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
21462
- matches.push([i - 1, j - 1]);
21463
- i--;
21464
- j--;
21465
- } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
21466
- else j--;
21467
- }
21468
- matches.reverse();
21469
- const result = [];
21470
- let ai = 0, bi = 0;
21471
- const flushGap = (aEnd, bEnd) => {
21472
- if (aEnd - ai === bEnd - bi) {
21473
- while (ai < aEnd) result.push([ai++, bi++]);
21474
- return;
21475
- }
21476
- while (ai < aEnd && bi < bEnd) {
21477
- const sim = normalizedSimilarity(a[ai], b[bi]);
21478
- if (sim >= 0.4) {
21479
- if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
21480
- result.push([ai++, null]);
21481
- } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
21482
- result.push([null, bi++]);
21483
- } else {
21484
- result.push([ai++, bi++]);
21485
- }
21486
- } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
21487
- else result.push([null, bi++]);
21488
- }
21489
- while (ai < aEnd) result.push([ai++, null]);
21490
- while (bi < bEnd) result.push([null, bi++]);
21491
- };
21492
- for (const [pi, pj] of matches) {
21493
- flushGap(pi, pj);
21494
- result.push([ai++, bi++]);
21495
- }
21496
- flushGap(m, n);
21497
- return result;
21498
- }
21499
- function bestSimInRange(arr, from, to, target) {
21500
- let best = 0;
21501
- for (let k = from; k <= to && k < arr.length; k++) {
21502
- const s = normalizedSimilarity(arr[k], target);
21503
- if (s > best) best = s;
21504
- }
21505
- return best;
21506
- }
21507
22392
  function resolveParagraphMappings(blocks, scans) {
21508
22393
  const buckets = /* @__PURE__ */ new Map();
21509
22394
  for (const scan of scans) {
@@ -21646,7 +22531,7 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
21646
22531
  const origPrefix = block.text.split(" ", 1)[0];
21647
22532
  const sp = newPlain.indexOf(" ");
21648
22533
  const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
21649
- if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
22534
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
21650
22535
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
21651
22536
  } else {
21652
22537
  ctx.skipped.push({ reason: "\uC790\uB3D9\uBC88\uD638 \uC811\uB450 \uC2DD\uBCC4 \uC2E4\uD328 \u2014 \uBC88\uD638 \uD3EC\uD568 \uD14D\uC2A4\uD2B8\uB85C \uC801\uC6A9 (\uBDF0\uC5B4\uC5D0\uC11C \uC911\uBCF5 \uD45C\uC2DC \uAC00\uB2A5)", after: summarize(newPlain) });
@@ -22389,7 +23274,7 @@ function patchParagraph(block, orig, edited, ctx, skip) {
22389
23274
  const origPrefix = block.text.split(" ", 1)[0];
22390
23275
  const sp = newPlain.indexOf(" ");
22391
23276
  const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
22392
- if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
23277
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
22393
23278
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
22394
23279
  } else {
22395
23280
  ctx.skipped.push({ reason: "\uC790\uB3D9\uBC88\uD638 \uC811\uB450 \uC2DD\uBCC4 \uC2E4\uD328 \u2014 \uBC88\uD638 \uD3EC\uD568 \uD14D\uC2A4\uD2B8\uB85C \uC801\uC6A9 (\uBDF0\uC5B4\uC5D0\uC11C \uC911\uBCF5 \uD45C\uC2DC \uAC00\uB2A5)", after: summarize(newPlain) });
@@ -22454,9 +23339,6 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
22454
23339
  }
22455
23340
  return applied;
22456
23341
  }
22457
- function extractImgTags5(inner) {
22458
- return (inner.match(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi) || []).join(" ");
22459
- }
22460
23342
  function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
22461
23343
  if (replicateTableToHtml(table) !== orig.raw) return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
22462
23344
  const replica = replicateHtmlTable(table);
@@ -22484,7 +23366,7 @@ function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
22484
23366
  const origContent = htmlCellInnerToLines(oc.inner);
22485
23367
  const editedContent = htmlCellInnerToLines(ec.inner);
22486
23368
  if (origContent.hadNonText || editedContent.hadNonText) {
22487
- if (extractImgTags5(oc.inner) !== extractImgTags5(ec.inner)) {
23369
+ if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
22488
23370
  skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
22489
23371
  continue;
22490
23372
  }
@@ -22575,7 +23457,9 @@ function applyCellEdit5(table, scanTable, gridR, gridC, newLines, ctx, before, a
22575
23457
  }
22576
23458
  }
22577
23459
  if (newLines.length > nonEmpty.length) {
22578
- ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after) });
23460
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uCD94\uAC00\uB294 \uBB38\uB2E8 \uC0DD\uC131 \uBBF8\uC9C0\uC6D0 \u2014 \uB9C8\uC9C0\uB9C9 \uBB38\uB2E8\uC5D0 \uBCD1\uD569 \uC801\uC6A9", after: summarize(after), partial: true });
23461
+ } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
23462
+ ctx.skipped.push({ reason: "\uC140 \uB0B4 \uC904 \uC0AD\uC81C\uB294 \uBB38\uB2E8 \uC81C\uAC70 \uBBF8\uC9C0\uC6D0 \u2014 \uBE48 \uBB38\uB2E8 \uC794\uC874(\uBDF0\uC5B4\uC5D0 \uBE48 \uC904 \uD45C\uC2DC \uAC00\uB2A5)", before: summarize(before), after: summarize(after), partial: true });
22579
23463
  }
22580
23464
  let staged = 0;
22581
23465
  for (let i = 0; i < nonEmpty.length; i++) {
@@ -22950,6 +23834,7 @@ var HwpxSession = class _HwpxSession {
22950
23834
  try {
22951
23835
  for (let s = 0; s < st.scans.length; s++) {
22952
23836
  if (sectionSplices[s].length === 0) continue;
23837
+ sectionSplices[s].push(...allLinesegRemovalSplices(st.scans[s].xml));
22953
23838
  replacements.set(st.sectionPaths[s], encoder.encode(applySplices(st.scans[s].xml, sectionSplices[s])));
22954
23839
  }
22955
23840
  } catch (err) {
@@ -23006,7 +23891,7 @@ var HwpxSession = class _HwpxSession {
23006
23891
  const origPrefix = block.text.split(" ", 1)[0];
23007
23892
  const sp = newPlain.indexOf(" ");
23008
23893
  const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
23009
- if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
23894
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
23010
23895
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
23011
23896
  } else {
23012
23897
  skipped.push({ reason: "\uC790\uB3D9\uBC88\uD638 \uC811\uB450 \uC2DD\uBCC4 \uC2E4\uD328 \u2014 \uBC88\uD638 \uD3EC\uD568 \uD14D\uC2A4\uD2B8\uB85C \uC801\uC6A9 (\uBDF0\uC5B4\uC5D0\uC11C \uC911\uBCF5 \uD45C\uC2DC \uAC00\uB2A5)", after: summarize(newPlain) });
@@ -23300,7 +24185,7 @@ async function parseHwp(buffer, options) {
23300
24185
  async function parsePdf(buffer, options) {
23301
24186
  let parsePdfDocument;
23302
24187
  try {
23303
- const mod = await import("./parser-IAROZTY3.js");
24188
+ const mod = await import("./parser-DR5CTZ74.js");
23304
24189
  parsePdfDocument = mod.parsePdfDocument;
23305
24190
  } catch {
23306
24191
  return {
@@ -23393,6 +24278,7 @@ async function fillForm(input, values, outputFormat = "markdown") {
23393
24278
  export {
23394
24279
  extractHwpxMetadataOnly,
23395
24280
  extractHwp5MetadataOnly,
24281
+ ValueCursor,
23396
24282
  isLabelCell,
23397
24283
  extractFormFields,
23398
24284
  inferFieldType,
@@ -23403,6 +24289,13 @@ export {
23403
24289
  buildRangeSplices,
23404
24290
  applySplices,
23405
24291
  fillHwpx,
24292
+ charWidthEm1000,
24293
+ SPACE_EM_FIXED,
24294
+ SPACE_EM_FONT,
24295
+ measureTextWidth,
24296
+ simulateWrap,
24297
+ simulateWrapKeepWord,
24298
+ fitRatioForFewerLines,
23406
24299
  PRESET_ALIAS,
23407
24300
  normalizeGongmunPreset,
23408
24301
  markdownToHwpx,
@@ -23427,4 +24320,4 @@ export {
23427
24320
  parseHwpml,
23428
24321
  fillForm
23429
24322
  };
23430
- //# sourceMappingURL=chunk-GUDFEDFK.js.map
24323
+ //# sourceMappingURL=chunk-NXRABCWW.js.map