kordoc 3.5.4 → 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 (31) hide show
  1. package/README.md +16 -1
  2. package/dist/{-KX7VLNW5.js → -7UC4ZWBS.js} +21 -5
  3. package/dist/{chunk-WEAP42T3.js → chunk-IJJMVTU5.js} +2 -2
  4. package/dist/chunk-IJJMVTU5.js.map +1 -0
  5. package/dist/{chunk-VEB43O2L.js → chunk-MEVKYW55.js} +2 -2
  6. package/dist/chunk-MEVKYW55.js.map +1 -0
  7. package/dist/{chunk-DK2IE43H.js → chunk-NXRABCWW.js} +1584 -701
  8. package/dist/chunk-NXRABCWW.js.map +1 -0
  9. package/dist/{chunk-XZTDYOZV.cjs → chunk-QZCP3UWU.cjs} +2 -2
  10. package/dist/{chunk-XZTDYOZV.cjs.map → chunk-QZCP3UWU.cjs.map} +1 -1
  11. package/dist/cli.js +4 -4
  12. package/dist/index.cjs +1783 -900
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +130 -8
  15. package/dist/index.d.ts +130 -8
  16. package/dist/index.js +1584 -701
  17. package/dist/index.js.map +1 -1
  18. package/dist/mcp.js +3 -3
  19. package/dist/{parser-W5TBEAVX.js → parser-DR5CTZ74.js} +2 -2
  20. package/dist/{parser-CPTBEGQP.cjs → parser-RFLPUZ7P.cjs} +14 -14
  21. package/dist/{parser-CPTBEGQP.cjs.map → parser-RFLPUZ7P.cjs.map} +1 -1
  22. package/dist/{parser-XT65AEDJ.js → parser-ZHJFQR44.js} +2 -2
  23. package/dist/{watch-VOSNRZAY.js → watch-UIX447QV.js} +3 -3
  24. package/package.json +1 -1
  25. package/dist/chunk-DK2IE43H.js.map +0 -1
  26. package/dist/chunk-VEB43O2L.js.map +0 -1
  27. package/dist/chunk-WEAP42T3.js.map +0 -1
  28. /package/dist/{-KX7VLNW5.js.map → -7UC4ZWBS.js.map} +0 -0
  29. /package/dist/{parser-W5TBEAVX.js.map → parser-DR5CTZ74.js.map} +0 -0
  30. /package/dist/{parser-XT65AEDJ.js.map → parser-ZHJFQR44.js.map} +0 -0
  31. /package/dist/{watch-VOSNRZAY.js.map → watch-UIX447QV.js.map} +0 -0
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  sanitizeHref,
20
20
  stripDtd,
21
21
  toArrayBuffer
22
- } from "./chunk-VEB43O2L.js";
22
+ } from "./chunk-MEVKYW55.js";
23
23
  import {
24
24
  parsePageRange
25
25
  } from "./chunk-GE43BE46.js";
@@ -1450,9 +1450,32 @@ function detectHwpxHeadings(blocks, styleMap) {
1450
1450
  function buildTableWithCellMeta(state) {
1451
1451
  const table = buildTable(state.rows);
1452
1452
  if (state.caption) table.caption = state.caption;
1453
+ const anchors = [];
1454
+ {
1455
+ const covered = /* @__PURE__ */ new Set();
1456
+ for (let r = 0; r < table.rows; r++) {
1457
+ for (let c = 0; c < table.cols; c++) {
1458
+ if (covered.has(`${r},${c}`)) continue;
1459
+ const cell = table.cells[r]?.[c];
1460
+ if (!cell) continue;
1461
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
1462
+ for (let dc = 0; dc < cell.colSpan; dc++) {
1463
+ if (dr === 0 && dc === 0) continue;
1464
+ if (r + dr < table.rows && c + dc < table.cols) covered.add(`${r + dr},${c + dc}`);
1465
+ }
1466
+ }
1467
+ anchors.push(cell);
1468
+ c += cell.colSpan - 1;
1469
+ }
1470
+ }
1471
+ }
1472
+ const srcCount = state.rows.reduce((s, r) => s + r.length, 0);
1473
+ const ordinalReliable = anchors.length === srcCount;
1453
1474
  const claimed = /* @__PURE__ */ new Set();
1475
+ let flatIdx = -1;
1454
1476
  for (const row of state.rows) {
1455
1477
  for (const src of row) {
1478
+ flatIdx++;
1456
1479
  const needsBlocks = src.hasStructure && src.blocks && src.blocks.length > 0;
1457
1480
  if (!needsBlocks && !src.isHeader) continue;
1458
1481
  let target;
@@ -1471,6 +1494,10 @@ function buildTableWithCellMeta(state) {
1471
1494
  }
1472
1495
  }
1473
1496
  }
1497
+ if (!target && ordinalReliable) {
1498
+ const cand = anchors[flatIdx];
1499
+ if (cand && !claimed.has(cand)) target = cand;
1500
+ }
1474
1501
  if (!target) continue;
1475
1502
  claimed.add(target);
1476
1503
  if (needsBlocks) target.blocks = src.blocks;
@@ -18694,6 +18721,45 @@ function countSections(body) {
18694
18721
  }
18695
18722
 
18696
18723
  // src/form/match.ts
18724
+ var ValueCursor = class {
18725
+ constructor(values) {
18726
+ this.values = values;
18727
+ }
18728
+ nextIdx = /* @__PURE__ */ new Map();
18729
+ keys() {
18730
+ return this.values.keys();
18731
+ }
18732
+ has(key) {
18733
+ return this.values.has(key);
18734
+ }
18735
+ isArray(key) {
18736
+ return Array.isArray(this.values.get(key));
18737
+ }
18738
+ /** 남은 값이 있으면 true (스칼라는 항상 true) */
18739
+ available(key) {
18740
+ const v = this.values.get(key);
18741
+ if (v === void 0) return false;
18742
+ return typeof v === "string" || (this.nextIdx.get(key) ?? 0) < v.length;
18743
+ }
18744
+ /** 현재 값 미리보기 (소진 없음) */
18745
+ peek(key) {
18746
+ const v = this.values.get(key);
18747
+ if (v === void 0) return void 0;
18748
+ if (typeof v === "string") return v;
18749
+ const i = this.nextIdx.get(key) ?? 0;
18750
+ return i < v.length ? v[i] : void 0;
18751
+ }
18752
+ /** 값 소비 — 배열이면 커서 전진, 소진 시 undefined */
18753
+ consume(key) {
18754
+ const v = this.values.get(key);
18755
+ if (v === void 0) return void 0;
18756
+ if (typeof v === "string") return v;
18757
+ const i = this.nextIdx.get(key) ?? 0;
18758
+ if (i >= v.length) return void 0;
18759
+ this.nextIdx.set(key, i + 1);
18760
+ return v[i];
18761
+ }
18762
+ };
18697
18763
  function normalizeLabel(label) {
18698
18764
  return label.trim().replace(/[::\s()()·]/g, "");
18699
18765
  }
@@ -18732,9 +18798,9 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18732
18798
  (match, prefix, suffix) => {
18733
18799
  const label = prefix + suffix;
18734
18800
  const normalizedLabel = normalizeLabel(label);
18735
- const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18801
+ const matchKey = values.available(normalizedLabel) ? normalizedLabel : values.available(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18736
18802
  if (matchKey === void 0) return match;
18737
- const newValue = values.get(matchKey);
18803
+ const newValue = values.consume(matchKey);
18738
18804
  matchedLabels.add(matchKey);
18739
18805
  matches.push({ key: matchKey, label, value: newValue });
18740
18806
  return `${prefix}(${newValue})${suffix}`;
@@ -18744,11 +18810,12 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18744
18810
  /□([가-힣A-Za-z]+)/g,
18745
18811
  (match, keyword) => {
18746
18812
  const normalizedKw = normalizeLabel(keyword);
18747
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18813
+ const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
18748
18814
  if (matchKey === void 0) return match;
18749
- const val = values.get(matchKey);
18815
+ const val = values.peek(matchKey);
18750
18816
  const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18751
18817
  if (!isTruthy) return match;
18818
+ values.consume(matchKey);
18752
18819
  matchedLabels.add(matchKey);
18753
18820
  matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18754
18821
  return `\u2611${keyword}`;
@@ -18758,9 +18825,9 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18758
18825
  /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18759
18826
  (match, keyword) => {
18760
18827
  const normalizedKw = normalizeLabel(keyword);
18761
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18828
+ const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
18762
18829
  if (matchKey === void 0) return match;
18763
- const newValue = values.get(matchKey);
18830
+ const newValue = values.consume(matchKey);
18764
18831
  matchedLabels.add(matchKey);
18765
18832
  matches.push({ key: matchKey, label: keyword, value: newValue });
18766
18833
  return `(${keyword}\uFF1A${newValue})`;
@@ -18866,14 +18933,66 @@ var LABEL_KEYWORDS = /* @__PURE__ */ new Set([
18866
18933
  "\uCCAD\uAD6C\uC0AC\uC720",
18867
18934
  "\uC18C\uBA85\uC790\uB8CC"
18868
18935
  ]);
18936
+ var ENGLISH_LABEL_WORDS = /* @__PURE__ */ new Set([
18937
+ "name",
18938
+ "date",
18939
+ "address",
18940
+ "tel",
18941
+ "phone",
18942
+ "mobile",
18943
+ "fax",
18944
+ "email",
18945
+ "e-mail",
18946
+ "dept",
18947
+ "department",
18948
+ "division",
18949
+ "title",
18950
+ "position",
18951
+ "grade",
18952
+ "rank",
18953
+ "birth",
18954
+ "nationality",
18955
+ "sex",
18956
+ "gender",
18957
+ "signature",
18958
+ "sign",
18959
+ "seal",
18960
+ "remarks",
18961
+ "note",
18962
+ "period",
18963
+ "place",
18964
+ "purpose",
18965
+ "reason",
18966
+ "amount",
18967
+ "total",
18968
+ "sum",
18969
+ "qty",
18970
+ "quantity",
18971
+ "unit",
18972
+ "no",
18973
+ "id",
18974
+ "passport"
18975
+ ]);
18976
+ var ENGLISH_STOPWORDS = /* @__PURE__ */ new Set(["of", "the", "and", "or", "in"]);
18977
+ var NUMERIC_VALUE_RE = /^제?\d+(?:[.,]\d+)*[십백천만억조]*(?:원|명|건|개|회|부|매|장|점|호|번|년|월|일|시|분|초|개월|주년|차례|퍼센트)?$/;
18978
+ var SENTENCE_ENDING_RE = /(?:입니다|합니다|습니다|하세요|십시오|시오|바랍니다|바람|할 것|할것|하며|하고|한다|된다|됨|음|임)$/;
18869
18979
  function isLabelCell(text) {
18870
18980
  const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18871
18981
  if (!trimmed || trimmed.length > 30) return false;
18872
18982
  for (const kw of LABEL_KEYWORDS) {
18873
18983
  if (trimmed.includes(kw)) return true;
18874
18984
  }
18875
- if (/^[가-힣\s()()·::]+$/.test(trimmed) && trimmed.replace(/\s/g, "").length >= 2 && trimmed.replace(/\s/g, "").length <= 8 && !/\d/.test(trimmed)) return true;
18985
+ const compact = trimmed.replace(/\s/g, "");
18986
+ 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)) {
18987
+ return true;
18988
+ }
18876
18989
  if (/^[가-힣A-Za-z\s]+[::]$/.test(trimmed)) return true;
18990
+ if (/^[A-Za-z][A-Za-z\s./&-]*$/.test(trimmed) && trimmed.length <= 20) {
18991
+ const words = trimmed.toLowerCase().split(/[\s/&]+/).filter((w) => w && !ENGLISH_STOPWORDS.has(w));
18992
+ if (words.length >= 1 && words.length <= 3 && words.every((w) => ENGLISH_LABEL_WORDS.has(w.replace(/\.$/, "")))) {
18993
+ return true;
18994
+ }
18995
+ }
18877
18996
  return false;
18878
18997
  }
18879
18998
  function extractFormFields(blocks) {
@@ -19014,17 +19133,18 @@ function fillFormFields(blocks, values) {
19014
19133
  const filled = [];
19015
19134
  const matchedLabels = /* @__PURE__ */ new Set();
19016
19135
  const normalizedValues = normalizeValues(values);
19136
+ const cursor = new ValueCursor(normalizedValues);
19137
+ const allTables = collectIRTables(cloned, 0);
19017
19138
  const patternFilledCells = /* @__PURE__ */ new Set();
19018
- for (const block of cloned) {
19019
- if (block.type !== "table" || !block.table) continue;
19020
- for (let r = 0; r < block.table.rows; r++) {
19021
- for (let c = 0; c < block.table.cols; c++) {
19022
- const cell = block.table.cells[r]?.[c];
19139
+ for (const table of allTables) {
19140
+ for (let r = 0; r < table.rows; r++) {
19141
+ for (let c = 0; c < table.cols; c++) {
19142
+ const cell = table.cells[r]?.[c];
19023
19143
  if (!cell) continue;
19024
- const result = fillInCellPatterns(cell.text, normalizedValues, matchedLabels);
19144
+ const result = fillInCellPatterns(cell.text, cursor, matchedLabels);
19025
19145
  if (result) {
19026
19146
  cell.text = result.text;
19027
- patternFilledCells.add(`${r},${c}`);
19147
+ patternFilledCells.add(cell);
19028
19148
  for (const m of result.matches) {
19029
19149
  filled.push({ label: m.label, value: m.value, row: r, col: c });
19030
19150
  }
@@ -19032,33 +19152,71 @@ function fillFormFields(blocks, values) {
19032
19152
  }
19033
19153
  }
19034
19154
  }
19035
- for (const block of cloned) {
19036
- if (block.type !== "table" || !block.table) continue;
19037
- fillTable(block.table, normalizedValues, filled, matchedLabels, patternFilledCells);
19155
+ for (const table of allTables) {
19156
+ fillTable(table, cursor, filled, matchedLabels, patternFilledCells);
19038
19157
  }
19039
19158
  for (const block of cloned) {
19040
19159
  if (block.type !== "paragraph" || !block.text) continue;
19041
- const newText = fillInlineFields(block.text, normalizedValues, filled, matchedLabels);
19160
+ const newText = fillInlineFields(block.text, cursor, filled, matchedLabels);
19042
19161
  if (newText !== block.text) block.text = newText;
19043
19162
  }
19044
19163
  const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
19045
19164
  return { blocks: cloned, filled, unmatched };
19046
19165
  }
19166
+ function collectIRTables(blocks, depth) {
19167
+ if (depth > 16) return [];
19168
+ const out = [];
19169
+ for (const block of blocks) {
19170
+ if (block.type !== "table" || !block.table) continue;
19171
+ out.push(block.table);
19172
+ for (const row of block.table.cells) {
19173
+ for (const cell of row) {
19174
+ if (cell?.blocks?.length) out.push(...collectIRTables(cell.blocks, depth + 1));
19175
+ }
19176
+ }
19177
+ }
19178
+ return out;
19179
+ }
19180
+ function coveredPositions(table) {
19181
+ const covered = /* @__PURE__ */ new Set();
19182
+ for (let r = 0; r < table.rows; r++) {
19183
+ for (let c = 0; c < table.cols; c++) {
19184
+ if (covered.has(`${r},${c}`)) continue;
19185
+ const cell = table.cells[r]?.[c];
19186
+ if (!cell) continue;
19187
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
19188
+ for (let dc = 0; dc < cell.colSpan; dc++) {
19189
+ if (dr === 0 && dc === 0) continue;
19190
+ if (r + dr < table.rows && c + dc < table.cols) covered.add(`${r + dr},${c + dc}`);
19191
+ }
19192
+ }
19193
+ c += cell.colSpan - 1;
19194
+ }
19195
+ }
19196
+ return covered;
19197
+ }
19047
19198
  function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
19048
19199
  if (table.cols < 2) return;
19200
+ const covered = coveredPositions(table);
19049
19201
  for (let r = 0; r < table.rows; r++) {
19050
- for (let c = 0; c < table.cols - 1; c++) {
19202
+ for (let c = 0; c < table.cols; c++) {
19203
+ if (covered.has(`${r},${c}`)) continue;
19051
19204
  const labelCell = table.cells[r][c];
19052
- const valueCell = table.cells[r][c + 1];
19053
- if (!labelCell || !valueCell) continue;
19205
+ if (!labelCell) continue;
19054
19206
  if (!isLabelCell(labelCell.text)) continue;
19207
+ let vc = c + labelCell.colSpan;
19208
+ while (vc < table.cols && covered.has(`${r},${vc}`)) vc++;
19209
+ if (vc >= table.cols) continue;
19210
+ const valueCell = table.cells[r][vc];
19211
+ if (!valueCell) continue;
19055
19212
  if (isKeywordLabel(valueCell.text)) continue;
19056
19213
  const normalizedCellLabel = normalizeLabel(labelCell.text);
19057
19214
  if (!normalizedCellLabel) continue;
19058
19215
  const matchKey = findMatchingKey(normalizedCellLabel, values);
19059
19216
  if (matchKey === void 0) continue;
19060
- const newValue = values.get(matchKey);
19061
- if (patternFilledCells?.has(`${r},${c + 1}`)) {
19217
+ const newValue = values.consume(matchKey);
19218
+ if (newValue === void 0) continue;
19219
+ if (patternFilledCells?.has(valueCell)) {
19062
19220
  valueCell.text = newValue + " " + valueCell.text;
19063
19221
  } else {
19064
19222
  valueCell.text = newValue;
@@ -19081,14 +19239,16 @@ function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
19081
19239
  if (!allLabels) return;
19082
19240
  for (let r = 1; r < table.rows; r++) {
19083
19241
  for (let c = 0; c < table.cols; c++) {
19242
+ if (covered.has(`${r},${c}`)) continue;
19084
19243
  const headerCell = headerRow[c];
19085
19244
  const valueCell = table.cells[r]?.[c];
19086
19245
  if (!headerCell || !valueCell) continue;
19087
19246
  const headerLabel = normalizeLabel(headerCell.text);
19088
19247
  const matchKey = findMatchingKey(headerLabel, values);
19089
19248
  if (matchKey === void 0) continue;
19090
- if (matchedLabels.has(matchKey)) continue;
19091
- const newValue = values.get(matchKey);
19249
+ if (!values.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
19250
+ const newValue = values.consume(matchKey);
19251
+ if (newValue === void 0) continue;
19092
19252
  valueCell.text = newValue;
19093
19253
  matchedLabels.add(matchKey);
19094
19254
  filled.push({
@@ -19109,7 +19269,8 @@ function fillInlineFields(text, values, filled, matchedLabels) {
19109
19269
  for (const seg of segments) {
19110
19270
  const matchKey = findMatchingKey(normalizeLabel(seg.label), values);
19111
19271
  if (matchKey === void 0) continue;
19112
- const newValue = values.get(matchKey);
19272
+ const newValue = values.consume(matchKey);
19273
+ if (newValue === void 0) continue;
19113
19274
  matchedLabels.add(matchKey);
19114
19275
  filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
19115
19276
  out += text.slice(pos, seg.valueStart);
@@ -19216,6 +19377,7 @@ function scanSectionXml(xml, sectionIndex) {
19216
19377
  const paraStack = [];
19217
19378
  const tableStack = [];
19218
19379
  const rowStack = [];
19380
+ const trStartStack = [];
19219
19381
  const cellStack = [];
19220
19382
  let pendingT = null;
19221
19383
  const ctrlSubStack = [];
@@ -19280,11 +19442,17 @@ function scanSectionXml(xml, sectionIndex) {
19280
19442
  } else if (local2 === "tr") {
19281
19443
  const row = rowStack[rowStack.length - 1];
19282
19444
  const table = tableStack[tableStack.length - 1];
19283
- if (row && table && row.length > 0) table.rows.push(row);
19445
+ if (row && table && row.length > 0) {
19446
+ table.rows.push(row);
19447
+ const trStart = trStartStack[trStartStack.length - 1];
19448
+ if (trStart >= 0) table.rowRanges.push({ start: trStart, end: m.index + full.length });
19449
+ }
19284
19450
  if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19451
+ if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = -1;
19285
19452
  } else if (local2 === "tbl") {
19286
19453
  const table = tableStack.pop();
19287
19454
  rowStack.pop();
19455
+ trStartStack.pop();
19288
19456
  if (table) {
19289
19457
  finalizeTable(table);
19290
19458
  if (!table.topLevel) {
@@ -19327,6 +19495,7 @@ function scanSectionXml(xml, sectionIndex) {
19327
19495
  const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19328
19496
  if (!isNaN(ca)) cell.colAddr = ca;
19329
19497
  if (!isNaN(ra)) cell.rowAddr = ra;
19498
+ cell.addrTagRange = { start: m.index, end: m.index + full.length };
19330
19499
  }
19331
19500
  } else if (local === "cellSpan") {
19332
19501
  const cell = cellStack[cellStack.length - 1];
@@ -19377,6 +19546,7 @@ function scanSectionXml(xml, sectionIndex) {
19377
19546
  start: m.index,
19378
19547
  topLevel: false,
19379
19548
  rows: [],
19549
+ rowRanges: [],
19380
19550
  cellByAnchor: /* @__PURE__ */ new Map()
19381
19551
  };
19382
19552
  stack.pop();
@@ -19384,9 +19554,11 @@ function scanSectionXml(xml, sectionIndex) {
19384
19554
  stack.push({ local, qname, contentStart });
19385
19555
  tableStack.push(table);
19386
19556
  rowStack.push([]);
19557
+ trStartStack.push(-1);
19387
19558
  if (table.topLevel) tables.push(table);
19388
19559
  } else if (local === "tr") {
19389
19560
  if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19561
+ if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = m.index;
19390
19562
  } else if (local === "tc") {
19391
19563
  cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
19392
19564
  } else if (local === "cellAddr" || local === "cellSpan") {
@@ -19397,6 +19569,7 @@ function scanSectionXml(xml, sectionIndex) {
19397
19569
  const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19398
19570
  if (!isNaN(ca)) cell.colAddr = ca;
19399
19571
  if (!isNaN(ra)) cell.rowAddr = ra;
19572
+ cell.addrTagRange = { start: m.index, end: contentStart };
19400
19573
  } else {
19401
19574
  const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19402
19575
  const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
@@ -19789,6 +19962,7 @@ async function fillHwpx(hwpxBuffer, values) {
19789
19962
  throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19790
19963
  }
19791
19964
  const normalizedValues = normalizeValues(values);
19965
+ const cursor = new ValueCursor(normalizedValues);
19792
19966
  const matchedLabels = /* @__PURE__ */ new Set();
19793
19967
  const filled = [];
19794
19968
  const failedKeys = /* @__PURE__ */ new Set();
@@ -19824,7 +19998,7 @@ async function fillHwpx(hwpxBuffer, values) {
19824
19998
  for (const cell of row) {
19825
19999
  for (const para of cell.paragraphs) {
19826
20000
  const text = matchText(para);
19827
- const result = fillInCellPatterns(text, normalizedValues, matchedLabels);
20001
+ const result = fillInCellPatterns(text, cursor, matchedLabels);
19828
20002
  if (!result) continue;
19829
20003
  const l = led(para);
19830
20004
  if (l.fullText !== void 0) continue;
@@ -19858,21 +20032,30 @@ async function fillHwpx(hwpxBuffer, values) {
19858
20032
  if (isKeywordLabel(cellLabelText(valueCell))) continue;
19859
20033
  const normalizedCellLabel = normalizeLabel(labelText);
19860
20034
  if (!normalizedCellLabel) continue;
19861
- const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
20035
+ const matchKey = findMatchingKey(normalizedCellLabel, cursor);
19862
20036
  if (matchKey === void 0) continue;
19863
- const newValue = normalizedValues.get(matchKey);
19864
20037
  if (patternApplied.has(valueCell)) {
19865
20038
  const target = valueCell.paragraphs.find((p) => p.tRanges.length > 0) ?? valueCell.paragraphs[0];
19866
20039
  if (!target) continue;
19867
20040
  const l = led(target);
19868
- if (l.fullText === void 0) {
19869
- l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
19870
- l.filledIdx.push(filled.length);
19871
- l.matchKeys.push(matchKey);
19872
- }
20041
+ if (l.fullText !== void 0) continue;
20042
+ const newValue = cursor.consume(matchKey);
20043
+ if (newValue === void 0) continue;
20044
+ l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
20045
+ l.filledIdx.push(filled.length);
20046
+ l.matchKeys.push(matchKey);
20047
+ matchedLabels.add(matchKey);
20048
+ filled.push({
20049
+ label: labelText.trim().replace(/[::]\s*$/, ""),
20050
+ value: newValue,
20051
+ row: rowIdx,
20052
+ col: colIdx
20053
+ });
19873
20054
  } else {
19874
20055
  const paras = valueCell.paragraphs;
19875
20056
  if (paras.length === 0) continue;
20057
+ const newValue = cursor.consume(matchKey);
20058
+ if (newValue === void 0) continue;
19876
20059
  const l0 = led(paras[0]);
19877
20060
  l0.fullText = newValue;
19878
20061
  l0.ranges = [];
@@ -19883,14 +20066,14 @@ async function fillHwpx(hwpxBuffer, values) {
19883
20066
  lk.fullText = "";
19884
20067
  lk.ranges = [];
19885
20068
  }
20069
+ matchedLabels.add(matchKey);
20070
+ filled.push({
20071
+ label: labelText.trim().replace(/[::]\s*$/, ""),
20072
+ value: newValue,
20073
+ row: rowIdx,
20074
+ col: colIdx
20075
+ });
19886
20076
  }
19887
- matchedLabels.add(matchKey);
19888
- filled.push({
19889
- label: labelText.trim().replace(/[::]\s*$/, ""),
19890
- value: newValue,
19891
- row: rowIdx,
19892
- col: colIdx
19893
- });
19894
20077
  }
19895
20078
  }
19896
20079
  if (table.rows.length >= 2) {
@@ -19904,10 +20087,11 @@ async function fillHwpx(hwpxBuffer, values) {
19904
20087
  const dataCells = table.rows[rowIdx];
19905
20088
  for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
19906
20089
  const headerLabel = normalizeLabel(cellLabelText(headerCells[colIdx]));
19907
- const matchKey = findMatchingKey(headerLabel, normalizedValues);
20090
+ const matchKey = findMatchingKey(headerLabel, cursor);
19908
20091
  if (matchKey === void 0) continue;
19909
- if (matchedLabels.has(matchKey)) continue;
19910
- const newValue = normalizedValues.get(matchKey);
20092
+ if (!cursor.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
20093
+ const newValue = cursor.consume(matchKey);
20094
+ if (newValue === void 0) continue;
19911
20095
  const paras = dataCells[colIdx].paragraphs;
19912
20096
  if (paras.length === 0) continue;
19913
20097
  const l0 = led(paras[0]);
@@ -19937,9 +20121,10 @@ async function fillHwpx(hwpxBuffer, values) {
19937
20121
  if (existing?.fullText !== void 0) continue;
19938
20122
  const text = matchText(para);
19939
20123
  for (const seg of scanInlineSegments(text)) {
19940
- const matchKey = findMatchingKey(normalizeLabel(seg.label), normalizedValues);
20124
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), cursor);
19941
20125
  if (matchKey === void 0) continue;
19942
- const newValue = normalizedValues.get(matchKey);
20126
+ const newValue = cursor.consume(matchKey);
20127
+ if (newValue === void 0) continue;
19943
20128
  const replacement = seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
19944
20129
  const l = led(para);
19945
20130
  l.ranges.push({ start: seg.valueStart, end: seg.valueEnd, replacement });
@@ -19994,6 +20179,7 @@ async function fillHwpx(hwpxBuffer, values) {
19994
20179
  splices.push(...paraSplices);
19995
20180
  }
19996
20181
  if (splices.length > 0) {
20182
+ splices.push(...allLinesegRemovalSplices(xml));
19997
20183
  replacements.set(sectionPaths[si], encoder.encode(applySplices(xml, splices)));
19998
20184
  }
19999
20185
  }
@@ -20013,6 +20199,245 @@ async function fillHwpx(hwpxBuffer, values) {
20013
20199
  // src/hwpx/generator.ts
20014
20200
  import JSZip6 from "jszip";
20015
20201
 
20202
+ // src/hwpx/text-metrics.ts
20203
+ var ASCII_W = [
20204
+ 300,
20205
+ 320,
20206
+ 320,
20207
+ 610,
20208
+ 610,
20209
+ 830,
20210
+ 724,
20211
+ 320,
20212
+ 320,
20213
+ 320,
20214
+ 550,
20215
+ 550,
20216
+ 320,
20217
+ 550,
20218
+ 320,
20219
+ 550,
20220
+ // 0x20-0x2F
20221
+ 550,
20222
+ 550,
20223
+ 550,
20224
+ 550,
20225
+ 550,
20226
+ 550,
20227
+ 550,
20228
+ 550,
20229
+ 550,
20230
+ 550,
20231
+ 320,
20232
+ 320,
20233
+ 550,
20234
+ 550,
20235
+ 550,
20236
+ 550,
20237
+ // 0x30-0x3F
20238
+ 830,
20239
+ 706,
20240
+ 605,
20241
+ 685,
20242
+ 719,
20243
+ 627,
20244
+ 617,
20245
+ 683,
20246
+ 734,
20247
+ 305,
20248
+ 315,
20249
+ 660,
20250
+ 605,
20251
+ 839,
20252
+ 734,
20253
+ 732,
20254
+ // 0x40-0x4F
20255
+ 603,
20256
+ 705,
20257
+ 660,
20258
+ 627,
20259
+ 664,
20260
+ 731,
20261
+ 706,
20262
+ 910,
20263
+ 705,
20264
+ 705,
20265
+ 626,
20266
+ 320,
20267
+ 550,
20268
+ 320,
20269
+ 550,
20270
+ 550,
20271
+ // 0x50-0x5F
20272
+ 320,
20273
+ 569,
20274
+ 597,
20275
+ 552,
20276
+ 597,
20277
+ 536,
20278
+ 356,
20279
+ 562,
20280
+ 635,
20281
+ 287,
20282
+ 288,
20283
+ 582,
20284
+ 287,
20285
+ 907,
20286
+ 635,
20287
+ 588,
20288
+ // 0x60-0x6F
20289
+ 597,
20290
+ 579,
20291
+ 478,
20292
+ 496,
20293
+ 356,
20294
+ 635,
20295
+ 563,
20296
+ 720,
20297
+ 542,
20298
+ 543,
20299
+ 486,
20300
+ 320,
20301
+ 320,
20302
+ 320,
20303
+ 550,
20304
+ 0
20305
+ // 0x70-0x7E(+DEL)
20306
+ ];
20307
+ var SYM_W = {
20308
+ 160: 300,
20309
+ 163: 568,
20310
+ 165: 707,
20311
+ 167: 498,
20312
+ 171: 440,
20313
+ 172: 564,
20314
+ 176: 291,
20315
+ 177: 798,
20316
+ 182: 606,
20317
+ 183: 320,
20318
+ 187: 440,
20319
+ 215: 617,
20320
+ 247: 678,
20321
+ 8211: 625,
20322
+ 8212: 875,
20323
+ 8213: 875,
20324
+ 8216: 320,
20325
+ 8217: 320,
20326
+ 8220: 480,
20327
+ 8221: 480,
20328
+ 8224: 558,
20329
+ 8225: 438,
20330
+ 8229: 640,
20331
+ 8230: 960,
20332
+ 8240: 988,
20333
+ 8242: 335,
20334
+ 8243: 474,
20335
+ 8251: 770,
20336
+ 8364: 656,
20337
+ 9756: 1012,
20338
+ 9758: 1012
20339
+ };
20340
+ function charWidthEm1000(cp) {
20341
+ if (cp >= 32 && cp <= 126) return ASCII_W[cp - 32];
20342
+ const sym = SYM_W[cp];
20343
+ if (sym !== void 0) return sym;
20344
+ if (cp >= 44032 && cp <= 55203) return 970;
20345
+ if (cp >= 4352 && cp <= 4607) return 970;
20346
+ if (cp >= 12593 && cp <= 12686) return 970;
20347
+ if (cp >= 19968 && cp <= 40959 || cp >= 63744 && cp <= 64255) return 1e3;
20348
+ if (cp >= 12296 && cp <= 12305 || cp >= 12308 && cp <= 12315) return 500;
20349
+ if (cp === 12288) return 970;
20350
+ if (cp >= 8592 && cp <= 8959) return 970;
20351
+ if (cp >= 9312 && cp <= 9471) return 970;
20352
+ if (cp >= 9632 && cp <= 9983) return 970;
20353
+ if (cp >= 12800 && cp <= 13311) return 970;
20354
+ if (cp >= 65281 && cp <= 65376) return 970;
20355
+ return cp >= 11904 ? 970 : 550;
20356
+ }
20357
+ var SPACE_EM_FIXED = 500;
20358
+ var SPACE_EM_FONT = 300;
20359
+ function measureTextWidth(text, height, ratioPct, opts) {
20360
+ const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
20361
+ const spacing = opts?.spacingPct ?? 0;
20362
+ let em = 0;
20363
+ for (const ch of text) {
20364
+ const cp = ch.codePointAt(0);
20365
+ const w = cp === 32 ? spaceEm : charWidthEm1000(cp);
20366
+ em += w * (1 + spacing / 100);
20367
+ }
20368
+ return em / 1e3 * height * (ratioPct / 100);
20369
+ }
20370
+ var FORBID_START = /* @__PURE__ */ new Set([..."!%),.:;?]}\xA2\xB0\u2032\u2033\u2103\u3009\u300B\u300D\u300F\u3011\u3015!%),.:;?]}\u20A9~\u2026\xB7\u3001\u3002\u3003"]);
20371
+ var FORBID_END = /* @__PURE__ */ new Set([..."$([{\xA3\xA5\u3008\u300A\u300C\u300E\u3010\u3014$([{\u20A9"]);
20372
+ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "keep", opts) {
20373
+ const EPS = 0.5;
20374
+ const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
20375
+ const spacing = opts?.spacingPct ?? 0;
20376
+ const k = height * ratioPct / 100 / 1e3;
20377
+ const cwCp = (cp) => (cp === 32 ? spaceEm : charWidthEm1000(cp)) * (1 + spacing / 100) * k;
20378
+ const charW = (ch) => cwCp(ch.codePointAt(0));
20379
+ const rangeW = (from, to) => {
20380
+ let w = 0;
20381
+ for (const ch of text.slice(from, to)) w += charW(ch);
20382
+ return w;
20383
+ };
20384
+ const units = text.match(mode === "keep" ? / +|[^ ]+/g : / +|[^ ]/g) ?? [];
20385
+ const starts = [0];
20386
+ let lineW = 0;
20387
+ let avail = firstWidth;
20388
+ let pos = 0;
20389
+ const lineStart = () => starts[starts.length - 1];
20390
+ const breakBefore = (unitPos, w) => {
20391
+ let bp = unitPos;
20392
+ const u = text[unitPos];
20393
+ if (u !== void 0 && FORBID_START.has(u) && bp - 1 > lineStart() && text[bp - 1] !== " ") bp--;
20394
+ while (bp - 1 > lineStart() && FORBID_END.has(text[bp - 1])) bp--;
20395
+ if (bp <= lineStart()) bp = unitPos;
20396
+ starts.push(bp);
20397
+ avail = contWidth;
20398
+ lineW = rangeW(bp, unitPos) + w;
20399
+ };
20400
+ for (const u of units) {
20401
+ if (u[0] === " ") {
20402
+ lineW += charW(" ") * u.length;
20403
+ pos += u.length;
20404
+ continue;
20405
+ }
20406
+ const w = rangeW(pos, pos + u.length);
20407
+ if (lineW + w <= avail + EPS) {
20408
+ lineW += w;
20409
+ pos += u.length;
20410
+ continue;
20411
+ }
20412
+ if (lineW === 0 || w > contWidth + EPS) {
20413
+ let sub = 0;
20414
+ for (const ch of u) {
20415
+ const c = charW(ch);
20416
+ if (lineW + c > avail + EPS && lineW > 0) breakBefore(pos + sub, 0);
20417
+ lineW += c;
20418
+ sub += ch.length;
20419
+ }
20420
+ pos += u.length;
20421
+ continue;
20422
+ }
20423
+ breakBefore(pos, w);
20424
+ pos += u.length;
20425
+ }
20426
+ return { lines: starts.length, starts, lastLineWidth: lineW };
20427
+ }
20428
+ function simulateWrapKeepWord(text, firstWidth, contWidth, height, ratioPct, opts) {
20429
+ return simulateWrap(text, firstWidth, contWidth, height, ratioPct, "keep", opts);
20430
+ }
20431
+ function fitRatioForFewerLines(text, firstWidth, contWidth, height, baseRatio, minRatio, opts) {
20432
+ const base = simulateWrap(text, firstWidth, contWidth, height, baseRatio, "keep", opts);
20433
+ if (base.lines < 2) return null;
20434
+ for (let r = baseRatio - 1; r >= minRatio; r--) {
20435
+ const sim = simulateWrap(text, firstWidth, contWidth, height, r, "keep", opts);
20436
+ if (sim.lines < base.lines) return r;
20437
+ }
20438
+ return null;
20439
+ }
20440
+
20016
20441
  // src/hwpx/gongmun.ts
20017
20442
  var OFFICIAL_MARGINS = { top: 20, bottom: 10, left: 20, right: 20 };
20018
20443
  var PRESET_DEFAULTS = {
@@ -20048,6 +20473,7 @@ function resolveGongmun(opts) {
20048
20473
  const preset = normalizeGongmunPreset(opts.preset);
20049
20474
  const d = PRESET_DEFAULTS[preset];
20050
20475
  const bodyPt = opts.bodyPt ?? d.bodyPt;
20476
+ const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
20051
20477
  return {
20052
20478
  preset,
20053
20479
  bodyFont: opts.bodyFont ?? "myeongjo",
@@ -20055,7 +20481,8 @@ function resolveGongmun(opts) {
20055
20481
  lineSpacing: opts.lineSpacing ?? d.lineSpacing,
20056
20482
  numbering: opts.numbering ?? d.numbering,
20057
20483
  margins: opts.margins ?? OFFICIAL_MARGINS,
20058
- centerTitle: opts.centerTitle ?? true
20484
+ centerTitle: opts.centerTitle ?? true,
20485
+ autoFitMinRatio
20059
20486
  };
20060
20487
  }
20061
20488
  var HANGUL_INITIALS = [0, 2, 3, 5, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18];
@@ -20098,17 +20525,10 @@ function standardMarker(depth, n) {
20098
20525
  function reportMarker(depth) {
20099
20526
  return REPORT_BULLETS[Math.min(depth, REPORT_BULLETS.length - 1)];
20100
20527
  }
20101
- var MARKER_FULLWIDTH = "\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2469\u246A\u246B\u246C\u246D\u246E\u246F\u2470\u2471\u2472\u2473\u326E\u326F\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327A\u327B\u25A1\u25CB\u25E6\u25AA\u25C7\u25B3\u25B6\u318D";
20102
20528
  function markerWidth(marker, bodyHeight) {
20103
- const ta = bodyHeight / 2;
20104
- let w = 0;
20105
- for (const c of marker) {
20106
- if (/[가-힣]/.test(c) || MARKER_FULLWIDTH.includes(c)) w += bodyHeight;
20107
- else if (c === "." || c === ",") w += Math.round(bodyHeight * 0.25);
20108
- else if (c === "(" || c === ")") w += Math.round(bodyHeight * 0.45);
20109
- else w += Math.round(ta);
20110
- }
20111
- return Math.round(w + ta);
20529
+ let em = SPACE_EM_FIXED;
20530
+ for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
20531
+ return Math.round(em / 1e3 * bodyHeight);
20112
20532
  }
20113
20533
  function levelIndent(depth, bodyHeight, numbering) {
20114
20534
  const marker = numbering === "report" ? reportMarker(depth) : standardMarker(depth, 0);
@@ -20148,77 +20568,53 @@ function mmToHwpunit(mm) {
20148
20568
  return Math.round(mm * 7200 / 25.4);
20149
20569
  }
20150
20570
 
20151
- // src/hwpx/generator.ts
20152
- var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
20153
- var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
20154
- var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
20155
- var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
20156
- var NS_OPF = "http://www.idpf.org/2007/opf/";
20157
- var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
20158
- var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
20159
- var CHAR_NORMAL = 0;
20160
- var CHAR_BOLD = 1;
20161
- var CHAR_ITALIC = 2;
20162
- var CHAR_BOLD_ITALIC = 3;
20163
- var CHAR_CODE = 4;
20164
- var CHAR_H1 = 5;
20165
- var CHAR_H2 = 6;
20166
- var CHAR_H3 = 7;
20167
- var CHAR_H4 = 8;
20168
- var CHAR_TABLE_HEADER = 9;
20169
- var CHAR_QUOTE = 10;
20170
- var PARA_NORMAL = 0;
20171
- var PARA_H1 = 1;
20172
- var PARA_H2 = 2;
20173
- var PARA_H3 = 3;
20174
- var PARA_H4 = 4;
20175
- var PARA_CODE = 5;
20176
- var PARA_QUOTE = 6;
20177
- var PARA_LIST = 7;
20178
- var DEFAULT_TEXT_COLOR = "#000000";
20179
- function resolveTheme(theme) {
20180
- return {
20181
- h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
20182
- h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
20183
- h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20184
- h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
20185
- body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20186
- quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
20187
- /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
20188
- hasQuoteOption: theme?.quoteColor !== void 0,
20189
- tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
20190
- tableHeaderBold: !!theme?.tableHeaderBold
20191
- };
20571
+ // src/diff/text-diff.ts
20572
+ function similarity(a, b) {
20573
+ if (a === b) return 1;
20574
+ if (!a || !b) return 0;
20575
+ const maxLen = Math.max(a.length, b.length);
20576
+ if (maxLen === 0) return 1;
20577
+ return 1 - levenshtein(a, b) / maxLen;
20192
20578
  }
20193
- async function markdownToHwpx(markdown, options) {
20194
- const theme = resolveTheme(options?.theme);
20195
- const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
20196
- const blocks = parseMarkdownToBlocks(markdown);
20197
- const sectionXml = blocksToSectionXml(blocks, theme, gongmun);
20198
- const zip = new JSZip6();
20199
- zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
20200
- zip.file("META-INF/container.xml", generateContainerXml());
20201
- zip.file("Contents/content.hpf", generateManifest());
20202
- zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun));
20203
- zip.file("Contents/section0.xml", sectionXml);
20204
- zip.file("Preview/PrvText.txt", buildPrvText(blocks));
20205
- return await zip.generateAsync({ type: "arraybuffer" });
20579
+ function normalizedSimilarity(a, b) {
20580
+ return similarity(normalize(a), normalize(b));
20206
20581
  }
20207
- function buildPrvText(blocks) {
20208
- const lines = [];
20209
- let bytes = 0;
20210
- for (const b of blocks) {
20211
- const text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
20212
- if (!text) continue;
20213
- lines.push(text);
20214
- bytes += text.length * 3;
20215
- if (bytes > 1024) break;
20582
+ function normalize(s) {
20583
+ return s.replace(/\s+/g, " ").trim();
20584
+ }
20585
+ var MAX_LEVENSHTEIN_LEN = 1e4;
20586
+ function levenshtein(a, b) {
20587
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20588
+ const sampleLen = Math.min(500, a.length, b.length);
20589
+ let diffs = 0;
20590
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20591
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20592
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20216
20593
  }
20217
- return lines.join("\n").slice(0, 1024);
20594
+ if (a.length > b.length) [a, b] = [b, a];
20595
+ const m = a.length;
20596
+ const n = b.length;
20597
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
20598
+ let curr = new Array(m + 1);
20599
+ for (let j = 1; j <= n; j++) {
20600
+ curr[0] = j;
20601
+ for (let i = 1; i <= m; i++) {
20602
+ if (a[i - 1] === b[j - 1]) {
20603
+ curr[i] = prev[i - 1];
20604
+ } else {
20605
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20606
+ }
20607
+ }
20608
+ ;
20609
+ [prev, curr] = [curr, prev];
20610
+ }
20611
+ return prev[m];
20218
20612
  }
20219
- function parseMarkdownToBlocks(md2) {
20613
+
20614
+ // src/roundtrip/markdown-units.ts
20615
+ function splitMarkdownUnits(md2) {
20220
20616
  const lines = md2.split("\n");
20221
- const blocks = [];
20617
+ const units = [];
20222
20618
  let i = 0;
20223
20619
  while (i < lines.length) {
20224
20620
  const line = lines[i];
@@ -20226,66 +20622,523 @@ function parseMarkdownToBlocks(md2) {
20226
20622
  i++;
20227
20623
  continue;
20228
20624
  }
20229
- const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
20230
- if (fenceMatch) {
20231
- const fence = fenceMatch[1];
20232
- const lang = fenceMatch[2].trim();
20233
- const codeLines = [];
20234
- i++;
20235
- while (i < lines.length && !lines[i].startsWith(fence)) {
20236
- codeLines.push(lines[i]);
20625
+ if (line.trim().startsWith("<table>")) {
20626
+ const collected2 = [];
20627
+ let depth = 0;
20628
+ while (i < lines.length) {
20629
+ const l = lines[i];
20630
+ collected2.push(l);
20631
+ depth += (l.match(/<table>/g) || []).length;
20632
+ depth -= (l.match(/<\/table>/g) || []).length;
20237
20633
  i++;
20634
+ if (depth <= 0) break;
20238
20635
  }
20239
- if (i < lines.length) i++;
20240
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20241
- continue;
20242
- }
20243
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20244
- blocks.push({ type: "hr" });
20245
- i++;
20246
- continue;
20247
- }
20248
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
20249
- if (headingMatch) {
20250
- blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
20251
- i++;
20636
+ units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20252
20637
  continue;
20253
20638
  }
20254
20639
  if (line.trimStart().startsWith("|")) {
20255
- const tableRows = [];
20640
+ const collected2 = [];
20256
20641
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20257
- const row = lines[i];
20258
- if (/^[\s|:\-]+$/.test(row)) {
20259
- i++;
20260
- continue;
20261
- }
20262
- const cells = row.split("|").slice(1, -1).map((c) => c.trim());
20263
- if (cells.length > 0) tableRows.push(cells);
20642
+ collected2.push(lines[i]);
20264
20643
  i++;
20265
20644
  }
20266
- if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
20645
+ units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20267
20646
  continue;
20268
20647
  }
20269
- if (line.trimStart().startsWith("> ")) {
20270
- const quoteLines = [];
20271
- while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
20272
- quoteLines.push(lines[i].replace(/^>\s?/, ""));
20273
- i++;
20274
- }
20275
- for (const ql of quoteLines) {
20276
- blocks.push({ type: "blockquote", text: ql.trim() || "" });
20277
- }
20648
+ if (/^-{3,}\s*$/.test(line.trim())) {
20649
+ units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20650
+ i++;
20278
20651
  continue;
20279
20652
  }
20280
- const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
20281
- if (listMatch) {
20282
- const indent = Math.floor(listMatch[1].length / 2);
20283
- const ordered = /\d/.test(listMatch[2]);
20284
- blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
20653
+ if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20654
+ units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20285
20655
  i++;
20286
20656
  continue;
20287
20657
  }
20288
- blocks.push({ type: "paragraph", text: line.trim() });
20658
+ const collected = [];
20659
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20660
+ collected.push(lines[i].trim());
20661
+ i++;
20662
+ }
20663
+ units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20664
+ }
20665
+ return units;
20666
+ }
20667
+ function alignUnits(a, b) {
20668
+ const m = a.length, n = b.length;
20669
+ if (m * n > 4e6) {
20670
+ const result2 = [];
20671
+ let pre = 0;
20672
+ while (pre < m && pre < n && a[pre] === b[pre]) {
20673
+ result2.push([pre, pre]);
20674
+ pre++;
20675
+ }
20676
+ let suf = 0;
20677
+ while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
20678
+ const aMid = m - pre - suf, bMid = n - pre - suf;
20679
+ if (aMid === bMid) {
20680
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
20681
+ } else {
20682
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
20683
+ for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
20684
+ }
20685
+ for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
20686
+ return result2;
20687
+ }
20688
+ const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
20689
+ for (let i2 = 1; i2 <= m; i2++) {
20690
+ for (let j2 = 1; j2 <= n; j2++) {
20691
+ 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]);
20692
+ }
20693
+ }
20694
+ const matches = [];
20695
+ let i = m, j = n;
20696
+ while (i > 0 && j > 0) {
20697
+ if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
20698
+ matches.push([i - 1, j - 1]);
20699
+ i--;
20700
+ j--;
20701
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
20702
+ else j--;
20703
+ }
20704
+ matches.reverse();
20705
+ const result = [];
20706
+ let ai = 0, bi = 0;
20707
+ const flushGap = (aEnd, bEnd) => {
20708
+ if (aEnd - ai === bEnd - bi) {
20709
+ while (ai < aEnd) result.push([ai++, bi++]);
20710
+ return;
20711
+ }
20712
+ while (ai < aEnd && bi < bEnd) {
20713
+ const sim = normalizedSimilarity(a[ai], b[bi]);
20714
+ if (sim >= 0.4) {
20715
+ if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
20716
+ result.push([ai++, null]);
20717
+ } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
20718
+ result.push([null, bi++]);
20719
+ } else {
20720
+ result.push([ai++, bi++]);
20721
+ }
20722
+ } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
20723
+ else result.push([null, bi++]);
20724
+ }
20725
+ while (ai < aEnd) result.push([ai++, null]);
20726
+ while (bi < bEnd) result.push([null, bi++]);
20727
+ };
20728
+ for (const [pi, pj] of matches) {
20729
+ flushGap(pi, pj);
20730
+ result.push([ai++, bi++]);
20731
+ }
20732
+ flushGap(m, n);
20733
+ return result;
20734
+ }
20735
+ function bestSimInRange(arr, from, to, target) {
20736
+ let best = 0;
20737
+ for (let k = from; k <= to && k < arr.length; k++) {
20738
+ const s = normalizedSimilarity(arr[k], target);
20739
+ if (s > best) best = s;
20740
+ }
20741
+ return best;
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
+ });
20886
+ }
20887
+ if (rowCells.length) result.push({ tag, cells: rowCells });
20888
+ }
20889
+ return result;
20890
+ }
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;
20907
+ }
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;
20914
+ }
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;
20925
+ }
20926
+ }
20927
+ }
20928
+ if (depth !== 0) return null;
20929
+ return rows;
20930
+ }
20931
+ var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
20932
+ function htmlCellInnerToLines(inner) {
20933
+ let hadNonText = false;
20934
+ let work = inner;
20935
+ if (/<table[\s>]/i.test(work)) {
20936
+ hadNonText = true;
20937
+ work = removeNestedTables(work);
20938
+ }
20939
+ if (/<img\s/i.test(work)) {
20940
+ hadNonText = true;
20941
+ work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
20942
+ }
20943
+ const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
20944
+ return { lines, hadNonText };
20945
+ }
20946
+ function extractTopLevelTables(html) {
20947
+ const result = [];
20948
+ let depth = 0;
20949
+ let start = -1;
20950
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
20951
+ let m;
20952
+ while ((m = re.exec(html)) !== null) {
20953
+ if (m[1] !== "/") {
20954
+ if (depth === 0) start = m.index;
20955
+ depth++;
20956
+ } else {
20957
+ depth--;
20958
+ if (depth === 0 && start >= 0) {
20959
+ result.push(html.slice(start, m.index + m[0].length));
20960
+ start = -1;
20961
+ }
20962
+ if (depth < 0) depth = 0;
20963
+ }
20964
+ }
20965
+ return result;
20966
+ }
20967
+ function removeNestedTables(html) {
20968
+ let result = "";
20969
+ let depth = 0;
20970
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
20971
+ let last = 0;
20972
+ let m;
20973
+ while ((m = re.exec(html)) !== null) {
20974
+ if (m[1] !== "/") {
20975
+ if (depth === 0) result += html.slice(last, m.index);
20976
+ depth++;
20977
+ } else {
20978
+ depth--;
20979
+ if (depth === 0) last = m.index + m[0].length;
20980
+ if (depth < 0) depth = 0;
20981
+ }
20982
+ }
20983
+ if (depth === 0) result += html.slice(last);
20984
+ return result;
20985
+ }
20986
+
20987
+ // src/hwpx/generator.ts
20988
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
20989
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
20990
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
20991
+ var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
20992
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
20993
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
20994
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
20995
+ var CHAR_NORMAL = 0;
20996
+ var CHAR_BOLD = 1;
20997
+ var CHAR_ITALIC = 2;
20998
+ var CHAR_BOLD_ITALIC = 3;
20999
+ var CHAR_CODE = 4;
21000
+ var CHAR_H1 = 5;
21001
+ var CHAR_H2 = 6;
21002
+ var CHAR_H3 = 7;
21003
+ var CHAR_H4 = 8;
21004
+ var CHAR_TABLE_HEADER = 9;
21005
+ var CHAR_QUOTE = 10;
21006
+ var PARA_NORMAL = 0;
21007
+ var PARA_H1 = 1;
21008
+ var PARA_H2 = 2;
21009
+ var PARA_H3 = 3;
21010
+ var PARA_H4 = 4;
21011
+ var PARA_CODE = 5;
21012
+ var PARA_QUOTE = 6;
21013
+ var PARA_LIST = 7;
21014
+ var DEFAULT_TEXT_COLOR = "#000000";
21015
+ function resolveTheme(theme) {
21016
+ return {
21017
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
21018
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
21019
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
21020
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
21021
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
21022
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
21023
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
21024
+ hasQuoteOption: theme?.quoteColor !== void 0,
21025
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
21026
+ tableHeaderBold: !!theme?.tableHeaderBold
21027
+ };
21028
+ }
21029
+ async function markdownToHwpx(markdown, options) {
21030
+ const theme = resolveTheme(options?.theme);
21031
+ const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
21032
+ const blocks = parseMarkdownToBlocks(markdown);
21033
+ const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
21034
+ const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
21035
+ const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
21036
+ const zip = new JSZip6();
21037
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
21038
+ zip.file("META-INF/container.xml", generateContainerXml());
21039
+ zip.file("Contents/content.hpf", generateManifest());
21040
+ zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
21041
+ zip.file("Contents/section0.xml", sectionXml);
21042
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
21043
+ return await zip.generateAsync({ type: "arraybuffer" });
21044
+ }
21045
+ function buildPrvText(blocks) {
21046
+ const lines = [];
21047
+ let bytes = 0;
21048
+ for (const b of blocks) {
21049
+ let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
21050
+ if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
21051
+ if (!text) continue;
21052
+ lines.push(text);
21053
+ bytes += text.length * 3;
21054
+ if (bytes > 1024) break;
21055
+ }
21056
+ return lines.join("\n").slice(0, 1024);
21057
+ }
21058
+ function parseMarkdownToBlocks(md2) {
21059
+ const lines = md2.split("\n");
21060
+ const blocks = [];
21061
+ let i = 0;
21062
+ while (i < lines.length) {
21063
+ const line = lines[i];
21064
+ if (!line.trim()) {
21065
+ i++;
21066
+ continue;
21067
+ }
21068
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
21069
+ if (fenceMatch) {
21070
+ const fence = fenceMatch[1];
21071
+ const lang = fenceMatch[2].trim();
21072
+ const codeLines = [];
21073
+ i++;
21074
+ while (i < lines.length && !lines[i].startsWith(fence)) {
21075
+ codeLines.push(lines[i]);
21076
+ i++;
21077
+ }
21078
+ if (i < lines.length) i++;
21079
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
21080
+ continue;
21081
+ }
21082
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
21083
+ blocks.push({ type: "hr" });
21084
+ i++;
21085
+ continue;
21086
+ }
21087
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
21088
+ if (headingMatch) {
21089
+ blocks.push({ type: "heading", text: headingMatch[2].trim(), level: headingMatch[1].length });
21090
+ i++;
21091
+ continue;
21092
+ }
21093
+ if (/^<table[\s>]/i.test(line.trimStart())) {
21094
+ const htmlLines = [];
21095
+ let depth = 0;
21096
+ while (i < lines.length) {
21097
+ const l = lines[i];
21098
+ htmlLines.push(l);
21099
+ depth += (l.match(/<table[\s>]/gi) ?? []).length;
21100
+ depth -= (l.match(/<\/table>/gi) ?? []).length;
21101
+ i++;
21102
+ if (depth <= 0) break;
21103
+ }
21104
+ blocks.push({ type: "html_table", text: htmlLines.join("\n") });
21105
+ continue;
21106
+ }
21107
+ if (line.trimStart().startsWith("|")) {
21108
+ const tableRows = [];
21109
+ while (i < lines.length && lines[i].trimStart().startsWith("|")) {
21110
+ const row = lines[i];
21111
+ if (/^[\s|:\-]+$/.test(row)) {
21112
+ i++;
21113
+ continue;
21114
+ }
21115
+ const cells = row.split("|").slice(1, -1).map((c) => c.trim());
21116
+ if (cells.length > 0) tableRows.push(cells);
21117
+ i++;
21118
+ }
21119
+ if (tableRows.length > 0) blocks.push({ type: "table", rows: tableRows });
21120
+ continue;
21121
+ }
21122
+ if (line.trimStart().startsWith("> ")) {
21123
+ const quoteLines = [];
21124
+ while (i < lines.length && (lines[i].trimStart().startsWith("> ") || lines[i].trimStart().startsWith(">"))) {
21125
+ quoteLines.push(lines[i].replace(/^>\s?/, ""));
21126
+ i++;
21127
+ }
21128
+ for (const ql of quoteLines) {
21129
+ blocks.push({ type: "blockquote", text: ql.trim() || "" });
21130
+ }
21131
+ continue;
21132
+ }
21133
+ const listMatch = line.match(/^(\s*)([-*+]|\d+[.)]) (.+)$/);
21134
+ if (listMatch) {
21135
+ const indent = Math.floor(listMatch[1].length / 2);
21136
+ const ordered = /\d/.test(listMatch[2]);
21137
+ blocks.push({ type: "list_item", text: listMatch[3].trim(), ordered, indent });
21138
+ i++;
21139
+ continue;
21140
+ }
21141
+ blocks.push({ type: "paragraph", text: line.trim() });
20289
21142
  i++;
20290
21143
  }
20291
21144
  return blocks;
@@ -20332,18 +21185,19 @@ function spanToCharPrId(span) {
20332
21185
  function escapeXml(text) {
20333
21186
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20334
21187
  }
20335
- function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
21188
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
20336
21189
  const spans = parseInlineMarkdown(text);
20337
21190
  return spans.map((span) => {
20338
- const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
21191
+ let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
21192
+ if (mapCharId) charId = mapCharId(charId);
20339
21193
  return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
20340
21194
  }).join("");
20341
21195
  }
20342
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
21196
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
20343
21197
  if (paraPrId === PARA_CODE) {
20344
21198
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
20345
21199
  }
20346
- const runs = generateRuns(text, charPrId);
21200
+ const runs = generateRuns(text, charPrId, mapCharId);
20347
21201
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
20348
21202
  }
20349
21203
  function headingParaPrId(level) {
@@ -20383,7 +21237,7 @@ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_C
20383
21237
  const boldAttr = bold ? ` bold="1"` : "";
20384
21238
  const italicAttr = italic ? ` italic="1"` : "";
20385
21239
  const effFont = bold ? 2 : fontId;
20386
- return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="0"${boldAttr}${italicAttr}>
21240
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
20387
21241
  <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
20388
21242
  <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
20389
21243
  <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
@@ -20402,13 +21256,58 @@ function paraPr(id, opts = {}) {
20402
21256
  <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20403
21257
  <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>
20404
21258
  <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20405
- <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
21259
+ <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20406
21260
  </hh:paraPr>`;
20407
21261
  }
20408
21262
  var GONGMUN_LIST_BASE = 8;
20409
21263
  var GONGMUN_LIST_LEVELS = 8;
20410
21264
  var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
20411
- function buildCharProperties(theme, gongmun) {
21265
+ var CHAR_VARIANT_BASE = 11;
21266
+ var GONGMUN_BODY_RATIO = 95;
21267
+ function plainRenderText(text) {
21268
+ return parseInlineMarkdown(text).map((s) => s.text).join("");
21269
+ }
21270
+ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
21271
+ const minRatio = gongmun.autoFitMinRatio;
21272
+ if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
21273
+ const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
21274
+ const ratioByBlock = /* @__PURE__ */ new Map();
21275
+ const variants = [];
21276
+ for (let i = 0; i < blocks.length; i++) {
21277
+ const block = blocks[i];
21278
+ let text;
21279
+ let firstW;
21280
+ let contW;
21281
+ if (block.type === "list_item" && gongmunList.has(i)) {
21282
+ const { marker, depth } = gongmunList.get(i);
21283
+ const content = plainRenderText(block.text || "");
21284
+ text = marker ? `${marker} ${content}` : content;
21285
+ const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
21286
+ firstW = pageW - left - Math.max(indent, 0);
21287
+ contW = pageW - left - Math.max(-indent, 0);
21288
+ } else if (block.type === "paragraph") {
21289
+ const raw = (block.text || "").trim();
21290
+ if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
21291
+ text = plainRenderText(raw);
21292
+ firstW = contW = pageW;
21293
+ } else {
21294
+ continue;
21295
+ }
21296
+ if (!text) continue;
21297
+ const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
21298
+ if (r === null) continue;
21299
+ ratioByBlock.set(i, r);
21300
+ if (!variants.includes(r)) variants.push(r);
21301
+ }
21302
+ return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
21303
+ }
21304
+ function variantMapper(fit, blockIdx) {
21305
+ const r = fit.ratioByBlock.get(blockIdx);
21306
+ if (r === void 0) return void 0;
21307
+ const vi = fit.variants.indexOf(r);
21308
+ return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
21309
+ }
21310
+ function buildCharProperties(theme, gongmun, ratioVariants = []) {
20412
21311
  let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
20413
21312
  if (gongmun) {
20414
21313
  body = gongmun.bodyHeight;
@@ -20418,7 +21317,7 @@ function buildCharProperties(theme, gongmun) {
20418
21317
  h3 = body;
20419
21318
  h4 = Math.max(body - 100, 1300);
20420
21319
  }
20421
- const bodyRatio = gongmun ? 95 : 100;
21320
+ const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
20422
21321
  const rows = [
20423
21322
  charPr(0, body, false, false, 0, theme.body, bodyRatio),
20424
21323
  charPr(1, body, true, false, 0, theme.body, bodyRatio),
@@ -20432,6 +21331,14 @@ function buildCharProperties(theme, gongmun) {
20432
21331
  charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
20433
21332
  charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
20434
21333
  ];
21334
+ for (const r of ratioVariants) {
21335
+ rows.push(
21336
+ charPr(rows.length, body, false, false, 0, theme.body, r),
21337
+ charPr(rows.length + 1, body, true, false, 0, theme.body, r),
21338
+ charPr(rows.length + 2, body, false, true, 0, theme.body, r),
21339
+ charPr(rows.length + 3, body, true, true, 0, theme.body, r)
21340
+ );
21341
+ }
20435
21342
  return `<hh:charProperties itemCnt="${rows.length}">
20436
21343
  ${rows.join("\n")}
20437
21344
  </hh:charProperties>`;
@@ -20474,9 +21381,9 @@ ${base2.join("\n")}
20474
21381
  ${base.join("\n")}
20475
21382
  </hh:paraProperties>`;
20476
21383
  }
20477
- function generateHeaderXml(theme, gongmun) {
21384
+ function generateHeaderXml(theme, gongmun, ratioVariants = []) {
20478
21385
  const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
20479
- const charPropsXml = buildCharProperties(theme, gongmun);
21386
+ const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
20480
21387
  const paraPropsXml = buildParaProperties(gongmun);
20481
21388
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20482
21389
  <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
@@ -20532,25 +21439,21 @@ function generateHeaderXml(theme, gongmun) {
20532
21439
  </hh:fontface>
20533
21440
  </hh:fontfaces>
20534
21441
  <hh:borderFills itemCnt="2">
20535
- <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
21442
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20536
21443
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20537
21444
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20538
21445
  <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
20539
21446
  <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
20540
21447
  <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
20541
21448
  <hh:bottomBorder type="NONE" width="0.1 mm" color="#000000"/>
20542
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20543
- <hh:fillInfo/>
20544
21449
  </hh:borderFill>
20545
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
21450
+ <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20546
21451
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20547
21452
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20548
21453
  <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
20549
21454
  <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
20550
21455
  <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
20551
21456
  <hh:bottomBorder type="SOLID" width="0.12 mm" color="#000000"/>
20552
- <hh:diagonal type="NONE" width="0.1 mm" color="#000000"/>
20553
- <hh:fillInfo/>
20554
21457
  </hh:borderFill>
20555
21458
  </hh:borderFills>
20556
21459
  ${charPropsXml}
@@ -20597,14 +21500,74 @@ function generateTable(rows, theme) {
20597
21500
  const tdElements = cells.map((cell, colIdx) => {
20598
21501
  const runs = generateRuns(cell, headerCharPr);
20599
21502
  const p = `<hp:p paraPrIDRef="0" styleIDRef="0">${runs}</hp:p>`;
20600
- 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>`;
21503
+ 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>`;
20601
21504
  }).join("");
20602
21505
  return `<hp:tr>${tdElements}</hp:tr>`;
20603
21506
  }).join("");
20604
21507
  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;
20605
- 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>`;
21508
+ 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>`;
20606
21509
  return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
20607
21510
  }
21511
+ function layoutHtmlRows(rows) {
21512
+ const occupied = /* @__PURE__ */ new Set();
21513
+ const placed = [];
21514
+ let colCnt = 0;
21515
+ for (let r = 0; r < rows.length; r++) {
21516
+ let c = 0;
21517
+ for (const cell of rows[r].cells) {
21518
+ while (occupied.has(`${r},${c}`)) c++;
21519
+ const colSpan = Math.max(1, cell.colSpan);
21520
+ const rowSpan = Math.max(1, cell.rowSpan);
21521
+ placed.push({ r, c, colSpan, rowSpan, inner: cell.inner, isHeader: rows[r].tag === "th" });
21522
+ for (let dr = 0; dr < rowSpan; dr++) {
21523
+ for (let dc = 0; dc < colSpan; dc++) occupied.add(`${r + dr},${c + dc}`);
21524
+ }
21525
+ c += colSpan;
21526
+ colCnt = Math.max(colCnt, c);
21527
+ }
21528
+ }
21529
+ return { placed, rowCnt: rows.length, colCnt };
21530
+ }
21531
+ function unescapeHtml(s) {
21532
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
21533
+ }
21534
+ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3) {
21535
+ const rows = parseHtmlTable(rawHtml);
21536
+ if (!rows || rows.length === 0) return null;
21537
+ const { placed, rowCnt, colCnt } = layoutHtmlRows(rows);
21538
+ if (rowCnt === 0 || colCnt === 0) return null;
21539
+ const colW = Math.floor(totalWidth / colCnt);
21540
+ const cellH = 1500;
21541
+ const tblW = colW * colCnt;
21542
+ const tblId = nextTableId();
21543
+ const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
21544
+ const tcXmls = placed.map((cell) => {
21545
+ const headerCharPr = cell.isHeader && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
21546
+ const { lines } = htmlCellInnerToLines(cell.inner);
21547
+ const paras = lines.map(
21548
+ (line) => `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="${headerCharPr}"><hp:t>${escapeXml(unescapeHtml(line))}</hp:t></hp:run></hp:p>`
21549
+ );
21550
+ let nestedH = 0;
21551
+ for (const nested of extractTopLevelTables(cell.inner)) {
21552
+ const nestedXml = generateHtmlTableXml(nested, theme, Math.max(colW * cell.colSpan - 1020, 4e3));
21553
+ if (nestedXml) {
21554
+ paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${nestedXml}</hp:run></hp:p>`);
21555
+ nestedH += (nested.match(/<tr[\s>]/gi) ?? []).length * cellH + 300;
21556
+ }
21557
+ }
21558
+ if (paras.length === 0) {
21559
+ paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="${headerCharPr}"><hp:t></hp:t></hp:run></hp:p>`);
21560
+ }
21561
+ const cellHeight = Math.max(cellH * cell.rowSpan, Math.max(lines.length, 1) * 800 + nestedH);
21562
+ 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>`;
21563
+ });
21564
+ const trXmls = [];
21565
+ for (let r = 0; r < rowCnt; r++) {
21566
+ const rowTcs = tcXmls.filter((_, i) => placed[i].r === r);
21567
+ trXmls.push(`<hp:tr>${rowTcs.join("")}</hp:tr>`);
21568
+ }
21569
+ 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>`;
21570
+ }
20608
21571
  function precomputeGongmunList(blocks, gongmun) {
20609
21572
  const result = /* @__PURE__ */ new Map();
20610
21573
  let i = 0;
@@ -20628,12 +21591,11 @@ function precomputeGongmunList(blocks, gongmun) {
20628
21591
  }
20629
21592
  return result;
20630
21593
  }
20631
- function blocksToSectionXml(blocks, theme, gongmun) {
21594
+ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null) {
20632
21595
  const paraXmls = [];
20633
21596
  let isFirst = true;
20634
21597
  const orderedCounters = {};
20635
21598
  let prevWasOrdered = false;
20636
- const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
20637
21599
  for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
20638
21600
  const block = blocks[blockIdx];
20639
21601
  let xml = "";
@@ -20655,7 +21617,7 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20655
21617
  if (ctr) {
20656
21618
  xml = generateParagraph(ctr[1].trim(), GONGMUN_CENTER);
20657
21619
  } else {
20658
- xml = generateParagraph(block.text || "");
21620
+ xml = generateParagraph(block.text || "", PARA_NORMAL, CHAR_NORMAL, fit ? variantMapper(fit, blockIdx) : void 0);
20659
21621
  }
20660
21622
  break;
20661
21623
  }
@@ -20679,7 +21641,7 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20679
21641
  const content = block.text || "";
20680
21642
  const text = marker2 ? `${marker2} ${content}` : content;
20681
21643
  const listCharPr = gongmun.numbering === "report" && depth === 0 ? CHAR_BOLD : CHAR_NORMAL;
20682
- xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr);
21644
+ xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr, fit ? variantMapper(fit, blockIdx) : void 0);
20683
21645
  break;
20684
21646
  }
20685
21647
  const indent = block.indent || 0;
@@ -20702,80 +21664,52 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20702
21664
  xml = generateParagraph(indentPrefix + marker + (block.text || ""), PARA_LIST);
20703
21665
  break;
20704
21666
  }
20705
- case "hr":
20706
- 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>`;
20707
- break;
20708
- case "table":
20709
- if (block.rows) {
20710
- if (isFirst) {
20711
- const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
20712
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
20713
- isFirst = false;
20714
- }
20715
- xml = generateTable(block.rows, theme);
20716
- }
20717
- break;
20718
- }
20719
- if (!xml) continue;
20720
- if (isFirst && block.type !== "table") {
20721
- xml = xml.replace(
20722
- /<hp:run charPrIDRef="(\d+)">/,
20723
- `<hp:run charPrIDRef="$1">${generateSecPr(gongmun)}`
20724
- );
20725
- isFirst = false;
20726
- }
20727
- paraXmls.push(xml);
20728
- }
20729
- if (paraXmls.length === 0) {
20730
- paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run></hp:p>`);
20731
- }
20732
- return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20733
- <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
20734
- ${paraXmls.join("\n ")}
20735
- </hs:sec>`;
20736
- }
20737
-
20738
- // src/diff/text-diff.ts
20739
- function similarity(a, b) {
20740
- if (a === b) return 1;
20741
- if (!a || !b) return 0;
20742
- const maxLen = Math.max(a.length, b.length);
20743
- if (maxLen === 0) return 1;
20744
- return 1 - levenshtein(a, b) / maxLen;
20745
- }
20746
- function normalizedSimilarity(a, b) {
20747
- return similarity(normalize(a), normalize(b));
20748
- }
20749
- function normalize(s) {
20750
- return s.replace(/\s+/g, " ").trim();
20751
- }
20752
- var MAX_LEVENSHTEIN_LEN = 1e4;
20753
- function levenshtein(a, b) {
20754
- if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20755
- const sampleLen = Math.min(500, a.length, b.length);
20756
- let diffs = 0;
20757
- for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20758
- const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20759
- return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20760
- }
20761
- if (a.length > b.length) [a, b] = [b, a];
20762
- const m = a.length;
20763
- const n = b.length;
20764
- let prev = Array.from({ length: m + 1 }, (_, i) => i);
20765
- let curr = new Array(m + 1);
20766
- for (let j = 1; j <= n; j++) {
20767
- curr[0] = j;
20768
- for (let i = 1; i <= m; i++) {
20769
- if (a[i - 1] === b[j - 1]) {
20770
- curr[i] = prev[i - 1];
20771
- } else {
20772
- curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
21667
+ case "hr":
21668
+ 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>`;
21669
+ break;
21670
+ case "table":
21671
+ if (block.rows) {
21672
+ if (isFirst) {
21673
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
21674
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
21675
+ isFirst = false;
21676
+ }
21677
+ xml = generateTable(block.rows, theme);
21678
+ }
21679
+ break;
21680
+ case "html_table": {
21681
+ const tbl = generateHtmlTableXml(block.text || "", theme);
21682
+ if (tbl) {
21683
+ if (isFirst) {
21684
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
21685
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
21686
+ isFirst = false;
21687
+ }
21688
+ xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
21689
+ } else {
21690
+ const plain = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
21691
+ xml = plain ? generateParagraph(plain) : "";
21692
+ }
21693
+ break;
20773
21694
  }
20774
21695
  }
20775
- ;
20776
- [prev, curr] = [curr, prev];
21696
+ if (!xml) continue;
21697
+ if (isFirst && block.type !== "table") {
21698
+ xml = xml.replace(
21699
+ /<hp:run charPrIDRef="(\d+)">/,
21700
+ `<hp:run charPrIDRef="$1">${generateSecPr(gongmun)}`
21701
+ );
21702
+ isFirst = false;
21703
+ }
21704
+ paraXmls.push(xml);
20777
21705
  }
20778
- return prev[m];
21706
+ if (paraXmls.length === 0) {
21707
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run></hp:p>`);
21708
+ }
21709
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
21710
+ <hs:sec xmlns:hs="${NS_SECTION}" xmlns:hp="${NS_PARA}">
21711
+ ${paraXmls.join("\n ")}
21712
+ </hs:sec>`;
20779
21713
  }
20780
21714
 
20781
21715
  // src/diff/compare.ts
@@ -20895,320 +21829,251 @@ function diffTableCells(a, b) {
20895
21829
  const maxRows = Math.max(a.rows, b.rows);
20896
21830
  const maxCols = Math.max(a.cols, b.cols);
20897
21831
  const result = [];
20898
- for (let r = 0; r < maxRows; r++) {
20899
- const row = [];
20900
- for (let c = 0; c < maxCols; c++) {
20901
- const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
20902
- const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
20903
- let type;
20904
- if (cellA === void 0) type = "added";
20905
- else if (cellB === void 0) type = "removed";
20906
- else if (cellA === cellB) type = "unchanged";
20907
- else type = "modified";
20908
- row.push({ type, before: cellA, after: cellB });
20909
- }
20910
- result.push(row);
20911
- }
20912
- return result;
20913
- }
20914
-
20915
- // src/roundtrip/patcher.ts
20916
- import JSZip7 from "jszip";
20917
-
20918
- // src/roundtrip/markdown-units.ts
20919
- function splitMarkdownUnits(md2) {
20920
- const lines = md2.split("\n");
20921
- const units = [];
20922
- let i = 0;
20923
- while (i < lines.length) {
20924
- const line = lines[i];
20925
- if (!line.trim()) {
20926
- i++;
20927
- continue;
20928
- }
20929
- if (line.trim().startsWith("<table>")) {
20930
- const collected2 = [];
20931
- let depth = 0;
20932
- while (i < lines.length) {
20933
- const l = lines[i];
20934
- collected2.push(l);
20935
- depth += (l.match(/<table>/g) || []).length;
20936
- depth -= (l.match(/<\/table>/g) || []).length;
20937
- i++;
20938
- if (depth <= 0) break;
20939
- }
20940
- units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20941
- continue;
20942
- }
20943
- if (line.trimStart().startsWith("|")) {
20944
- const collected2 = [];
20945
- while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20946
- collected2.push(lines[i]);
20947
- i++;
20948
- }
20949
- units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20950
- continue;
20951
- }
20952
- if (/^-{3,}\s*$/.test(line.trim())) {
20953
- units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20954
- i++;
20955
- continue;
20956
- }
20957
- if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20958
- units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20959
- i++;
20960
- continue;
20961
- }
20962
- const collected = [];
20963
- while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20964
- collected.push(lines[i].trim());
20965
- i++;
20966
- }
20967
- units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20968
- }
20969
- return units;
20970
- }
20971
- function escapeGfm(text) {
20972
- return text.replace(/~/g, "\\~");
20973
- }
20974
- var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
20975
- function sanitizeText(text) {
20976
- let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
20977
- if (result.length <= 30 && result.includes(" ")) {
20978
- const tokens = result.split(" ");
20979
- const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
20980
- if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
20981
- result = tokens.join("");
20982
- }
20983
- }
20984
- return result;
20985
- }
20986
- function normForMatch(text) {
20987
- return sanitizeText(text).replace(/\s+/g, " ").trim();
20988
- }
20989
- function unescapeGfm(text) {
20990
- return text.replace(/\\~/g, "~");
20991
- }
20992
- function summarize(text) {
20993
- const t = text.replace(/\s+/g, " ").trim();
20994
- return t.length > 80 ? t.slice(0, 77) + "..." : t;
20995
- }
20996
- function replicateGfmTable(table) {
20997
- const { cells, rows: numRows, cols: numCols } = table;
20998
- if (numRows === 0 || numCols === 0) return null;
20999
- if (numRows === 1 && numCols === 1) return null;
21000
- if (numCols === 1) return null;
21001
- const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
21002
- const skip = /* @__PURE__ */ new Set();
21003
- for (let r = 0; r < numRows; r++) {
21004
- for (let c = 0; c < numCols; c++) {
21005
- if (skip.has(`${r},${c}`)) continue;
21006
- const cell = cells[r]?.[c];
21007
- if (!cell) continue;
21008
- display[r][c] = {
21009
- text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
21010
- gridR: r,
21011
- gridC: c
21012
- };
21013
- for (let dr = 0; dr < cell.rowSpan; dr++) {
21014
- for (let dc = 0; dc < cell.colSpan; dc++) {
21015
- if (dr === 0 && dc === 0) continue;
21016
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
21017
- }
21018
- }
21019
- c += cell.colSpan - 1;
21020
- }
21021
- }
21022
- const uniqueRows = [];
21023
- let pendingLabelRow = null;
21024
- for (let r = 0; r < display.length; r++) {
21025
- const row = display[r];
21026
- if (row.every((cell) => cell.text === "")) continue;
21027
- const nonEmptyCols = row.filter((cell) => cell.text !== "");
21028
- const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
21029
- if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
21030
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
21031
- pendingLabelRow = row;
21032
- continue;
21033
- }
21034
- if (pendingLabelRow) {
21035
- if (row[0].text === "") row[0] = pendingLabelRow[0];
21036
- else uniqueRows.push(pendingLabelRow);
21037
- pendingLabelRow = null;
21038
- }
21039
- uniqueRows.push(row);
21040
- }
21041
- if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
21042
- return uniqueRows.length > 0 ? uniqueRows : null;
21043
- }
21044
- function parseGfmTable(lines) {
21045
- const rows = [];
21046
- for (const line of lines) {
21047
- const trimmed = line.trim();
21048
- if (!trimmed.startsWith("|")) continue;
21049
- const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
21050
- if (cells.length === 0) continue;
21051
- if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
21052
- rows.push(cells);
21053
- }
21054
- return rows;
21055
- }
21056
- function unescapeGfmCell(text) {
21057
- return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
21058
- }
21059
- function replicateCellInnerHtml(cell) {
21060
- if (cell.blocks?.length) {
21061
- return cell.blocks.map((b) => {
21062
- if (b.type === "table" && b.table) {
21063
- const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
21064
- return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
21065
- }
21066
- if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
21067
- const t = sanitizeText(b.text ?? "");
21068
- return t ? t.replace(/\n/g, "<br>") : "";
21069
- }).filter(Boolean).join("<br>");
21070
- }
21071
- return sanitizeText(cell.text).replace(/\n/g, "<br>");
21072
- }
21073
- function replicateTableToHtml(table) {
21074
- const rows = replicateHtmlTable(table);
21075
- const lines = ["<table>"];
21076
- for (let r = 0; r < rows.length; r++) {
21077
- const tag = rows[r].tag;
21078
- const rowHtml = rows[r].cells.map((cell) => {
21079
- const attrs = [];
21080
- if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
21081
- if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
21082
- const attrStr = attrs.length ? " " + attrs.join(" ") : "";
21083
- return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
21084
- });
21085
- if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
21086
- }
21087
- lines.push("</table>");
21088
- return lines.join("\n");
21089
- }
21090
- function replicateHtmlTable(table) {
21091
- const { cells, rows: numRows, cols: numCols } = table;
21092
- const skip = /* @__PURE__ */ new Set();
21093
- const result = [];
21094
- for (let r = 0; r < numRows; r++) {
21095
- const tag = r === 0 ? "th" : "td";
21096
- const rowCells = [];
21097
- for (let c = 0; c < numCols; c++) {
21098
- if (skip.has(`${r},${c}`)) continue;
21099
- const cell = cells[r]?.[c];
21100
- if (!cell) continue;
21101
- for (let dr = 0; dr < cell.rowSpan; dr++) {
21102
- for (let dc = 0; dc < cell.colSpan; dc++) {
21103
- if (dr === 0 && dc === 0) continue;
21104
- if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
21105
- }
21106
- }
21107
- rowCells.push({
21108
- inner: replicateCellInnerHtml(cell),
21109
- colSpan: cell.colSpan,
21110
- rowSpan: cell.rowSpan,
21111
- gridR: r,
21112
- gridC: c
21113
- });
21832
+ for (let r = 0; r < maxRows; r++) {
21833
+ const row = [];
21834
+ for (let c = 0; c < maxCols; c++) {
21835
+ const cellA = r < a.rows && c < a.cols ? a.cells[r][c].text : void 0;
21836
+ const cellB = r < b.rows && c < b.cols ? b.cells[r][c].text : void 0;
21837
+ let type;
21838
+ if (cellA === void 0) type = "added";
21839
+ else if (cellB === void 0) type = "removed";
21840
+ else if (cellA === cellB) type = "unchanged";
21841
+ else type = "modified";
21842
+ row.push({ type, before: cellA, after: cellB });
21114
21843
  }
21115
- if (rowCells.length) result.push({ tag, cells: rowCells });
21844
+ result.push(row);
21116
21845
  }
21117
21846
  return result;
21118
21847
  }
21119
- function parseHtmlTable(raw) {
21120
- const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
21121
- let depth = 0;
21122
- let currentRow = null;
21123
- let cellStart = -1;
21124
- let cellInfo = null;
21125
- const rows = [];
21126
- let m;
21127
- while ((m = re.exec(raw)) !== null) {
21128
- const isClose = m[1] === "/";
21129
- const tag = m[2].toLowerCase();
21130
- const attrs = m[3] || "";
21131
- if (tag === "table") {
21132
- depth += isClose ? -1 : 1;
21133
- if (depth < 0) return null;
21134
- continue;
21848
+
21849
+ // src/roundtrip/patcher.ts
21850
+ import JSZip7 from "jszip";
21851
+
21852
+ // src/roundtrip/table-rows.ts
21853
+ var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
21854
+ var TAG_AT_RE = /<[A-Za-z0-9_:]+(?:"[^"]*"|'[^']*'|[^>"'])*>/y;
21855
+ function patchTableRows(input) {
21856
+ const { table, scanTable, ctx, skip, origKeys, editedKeys } = input;
21857
+ const xml = ctx.scans[scanTable.sectionIndex]?.xml;
21858
+ if (!xml) return skip("\uC139\uC158 XML \uB9E4\uD551 \uC2E4\uD328");
21859
+ const numRows = table.rows;
21860
+ if (origKeys.length !== numRows) return skip("\uD45C \uD589 \uC88C\uD45C \uBD88\uC77C\uCE58 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21861
+ if (scanTable.rows.length !== numRows || scanTable.rowRanges.length !== numRows) {
21862
+ 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");
21863
+ }
21864
+ for (let r = 0; r < numRows; r++) {
21865
+ if (scanTable.rows[r].some((c) => c.rowAddr !== r)) {
21866
+ return skip("\uD589 \uC8FC\uC18C \uBE44\uC5F0\uC18D \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21135
21867
  }
21136
- if (depth !== 1) continue;
21137
- if (tag === "tr") {
21138
- if (!isClose) currentRow = [];
21139
- else if (currentRow) {
21140
- rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
21141
- currentRow = null;
21868
+ }
21869
+ const allCells = scanTable.rows.flat();
21870
+ const explicitAddr = (c) => !!c.addrTagRange && /\browAddr\s*=\s*"/.test(xml.slice(c.addrTagRange.start, c.addrTagRange.end));
21871
+ const explicitCount = allCells.filter(explicitAddr).length;
21872
+ if (explicitCount !== 0 && explicitCount !== allCells.length) {
21873
+ return skip("\uC140 \uC8FC\uC18C(cellAddr) \uD45C\uAE30 \uD63C\uC7AC \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21874
+ }
21875
+ const hasExplicitAddr = explicitCount > 0;
21876
+ const pairs = alignUnits(origKeys, editedKeys);
21877
+ const seq = [];
21878
+ let lastOrig = -1;
21879
+ for (const [oi, ei] of pairs) {
21880
+ if (oi !== null && ei !== null) {
21881
+ seq.push({ kind: "keep", oi, ei });
21882
+ lastOrig = oi;
21883
+ } else if (oi !== null) {
21884
+ seq.push({ kind: "del", oi });
21885
+ lastOrig = oi;
21886
+ } else if (ei !== null) seq.push({ kind: "ins", ei, insertAt: lastOrig + 1 });
21887
+ }
21888
+ const dels = seq.filter((e) => e.kind === "del");
21889
+ const inss = seq.filter((e) => e.kind === "ins");
21890
+ const keeps = seq.filter((e) => e.kind === "keep");
21891
+ if (dels.length === 0 && inss.length === 0) {
21892
+ return skip("\uD45C \uD589 \uC815\uB82C \uC2E4\uD328 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21893
+ }
21894
+ if (keeps.length + inss.length === 0) return skip("\uBAA8\uB4E0 \uD589 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
21895
+ if (keeps.length === 0) return skip("\uD589 \uC804\uBA74 \uAD50\uCCB4 \u2014 \uC11C\uC2DD \uAE30\uC900 \uD589\uC774 \uC5C6\uC5B4 \uBBF8\uC9C0\uC6D0");
21896
+ const spans = allCells.filter((c) => (c.rowSpan ?? 1) > 1);
21897
+ for (const d of dels) {
21898
+ if (spans.some((s) => s.rowAddr <= d.oi && s.rowAddr + s.rowSpan > d.oi)) {
21899
+ return skip(`\uD45C ${d.oi + 1}\uD589 \uC0AD\uC81C\uAC00 \uC138\uB85C \uBCD1\uD569\uACFC \uACB9\uCE68 \u2014 \uBBF8\uC9C0\uC6D0`);
21900
+ }
21901
+ }
21902
+ for (const ins of inss) {
21903
+ const p = ins.insertAt;
21904
+ if (spans.some((s) => s.rowAddr < p && s.rowAddr + s.rowSpan > p)) {
21905
+ return skip("\uD589 \uC0BD\uC785 \uC704\uCE58\uAC00 \uC138\uB85C \uBCD1\uD569 \uB0B4\uBD80 \u2014 \uBBF8\uC9C0\uC6D0");
21906
+ }
21907
+ }
21908
+ for (const d of dels) {
21909
+ const rr = scanTable.rowRanges[d.oi];
21910
+ if (ROW_OBJECT_RE.test(xml.slice(rr.start, rr.end))) {
21911
+ 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`);
21912
+ }
21913
+ }
21914
+ const keptSet = new Set(keeps.map((k) => k.oi));
21915
+ const insertPlans = [];
21916
+ for (const ins of inss) {
21917
+ const p = ins.insertAt;
21918
+ const kept = [...keptSet].sort((a, b) => a - b);
21919
+ let template = -1;
21920
+ for (const k of kept) {
21921
+ if (k < p) template = k;
21922
+ }
21923
+ if (template <= 0 && p >= 1) {
21924
+ const following = kept.find((k) => k >= 1);
21925
+ if (following !== void 0) template = following;
21926
+ }
21927
+ if (template < 0) template = kept[0];
21928
+ const cells = input.editedCells(ins.ei);
21929
+ if (!cells) return skip("\uC0BD\uC785 \uD589\uC5D0 \uC774\uBBF8\uC9C0/\uC911\uCCA9\uD45C \uD3EC\uD568 \u2014 \uD589 \uCD94\uAC00 \uBBF8\uC9C0\uC6D0");
21930
+ for (const cell of cells) {
21931
+ const unstable = cell.lines.find((l) => sanitizeText(l) !== l);
21932
+ 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");
21933
+ }
21934
+ const tmplCells = scanTable.rows[template];
21935
+ const rr = scanTable.rowRanges[template];
21936
+ if (ROW_OBJECT_RE.test(xml.slice(rr.start, rr.end))) {
21937
+ 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");
21938
+ }
21939
+ 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");
21940
+ const tmplWidth = tmplCells.reduce((s, c) => s + c.colSpan, 0);
21941
+ 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");
21942
+ if (cells.length !== tmplCells.length) {
21943
+ return skip(`\uC0BD\uC785 \uD589 \uC140 \uC218(${cells.length}) \u2260 \uAE30\uC900 \uD589 \uC140 \uC218(${tmplCells.length}) \u2014 \uBBF8\uC9C0\uC6D0`);
21944
+ }
21945
+ for (let i = 0; i < cells.length; i++) {
21946
+ if (cells[i].rowSpan !== 1 || cells[i].colSpan !== tmplCells[i].colSpan) {
21947
+ return skip("\uC0BD\uC785 \uD589 \uBCD1\uD569 \uAD6C\uC870\uAC00 \uAE30\uC900 \uD589\uACFC \uB2E4\uB984 \u2014 \uBBF8\uC9C0\uC6D0");
21142
21948
  }
21143
- } else {
21144
- if (!isClose) {
21145
- const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
21146
- const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
21147
- cellStart = m.index + m[0].length;
21148
- cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
21149
- } else if (cellStart >= 0 && cellInfo && currentRow) {
21150
- currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
21151
- cellStart = -1;
21152
- cellInfo = null;
21949
+ }
21950
+ insertPlans.push({ entry: ins, template, cells });
21951
+ }
21952
+ const net = inss.length - dels.length;
21953
+ TAG_AT_RE.lastIndex = scanTable.start;
21954
+ const tblOpen = TAG_AT_RE.exec(xml);
21955
+ if (!tblOpen || tblOpen.index !== scanTable.start) return skip("\uD45C \uC5EC\uB294 \uD0DC\uADF8 \uD574\uC11D \uC2E4\uD328");
21956
+ const rowCntM = tblOpen[0].match(/\browCnt\s*=\s*"(\d+)"/);
21957
+ if (!rowCntM || rowCntM.index === void 0) return skip("\uD45C rowCnt \uC18D\uC131 \uC5C6\uC74C \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21958
+ 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");
21959
+ const finalIndex = /* @__PURE__ */ new Map();
21960
+ {
21961
+ let fi = 0;
21962
+ for (const e of seq) {
21963
+ if (e.kind !== "del") finalIndex.set(e, fi++);
21964
+ }
21965
+ }
21966
+ const splices = [];
21967
+ let applied = 0;
21968
+ for (const d of dels) {
21969
+ const rr = scanTable.rowRanges[d.oi];
21970
+ splices.push({ start: rr.start, end: rr.end, replacement: "" });
21971
+ applied++;
21972
+ }
21973
+ const fragmentsByAnchor = /* @__PURE__ */ new Map();
21974
+ let heightDelta = 0;
21975
+ for (const plan of insertPlans) {
21976
+ const finalRow = finalIndex.get(plan.entry);
21977
+ const fragment = buildRowFragment(xml, scanTable, plan.template, plan.cells, finalRow, hasExplicitAddr, ctx);
21978
+ if (fragment === null) return skip("\uD589 \uBCF5\uC81C \uC2E4\uD328 (\uC140 \uBB38\uB2E8 \uAD6C\uC870 \uBBF8\uC9C0\uC6D0)");
21979
+ const p = plan.entry.insertAt;
21980
+ const anchor = p === 0 ? scanTable.rowRanges[0].start : scanTable.rowRanges[p - 1].end;
21981
+ let list = fragmentsByAnchor.get(anchor);
21982
+ if (!list) fragmentsByAnchor.set(anchor, list = []);
21983
+ list.push(fragment);
21984
+ heightDelta += rowHeightOf(fragment);
21985
+ applied++;
21986
+ }
21987
+ for (const [anchor, fragments] of fragmentsByAnchor) {
21988
+ splices.push({ start: anchor, end: anchor, replacement: fragments.join("") });
21989
+ }
21990
+ for (const d of dels) {
21991
+ const rr = scanTable.rowRanges[d.oi];
21992
+ heightDelta -= rowHeightOf(xml.slice(rr.start, rr.end));
21993
+ }
21994
+ if (hasExplicitAddr) {
21995
+ for (const k of keeps) {
21996
+ const fi = finalIndex.get(k);
21997
+ if (fi === k.oi) continue;
21998
+ for (const cell of scanTable.rows[k.oi]) {
21999
+ const sp = rowAddrRewrite(xml, cell, fi);
22000
+ if (sp) splices.push(sp);
21153
22001
  }
21154
22002
  }
21155
22003
  }
21156
- if (depth !== 0) return null;
21157
- return rows;
21158
- }
21159
- function htmlCellInnerToLines(inner) {
21160
- let hadNonText = false;
21161
- let work = inner;
21162
- if (/<table[\s>]/i.test(work)) {
21163
- hadNonText = true;
21164
- work = removeNestedTables(work);
22004
+ {
22005
+ const valStart = scanTable.start + rowCntM.index + rowCntM[0].indexOf('"') + 1;
22006
+ splices.push({ start: valStart, end: valStart + rowCntM[1].length, replacement: String(numRows + net) });
21165
22007
  }
21166
- if (/<img\s/i.test(work)) {
21167
- hadNonText = true;
21168
- work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
22008
+ if (heightDelta !== 0) {
22009
+ const sp = tableSzHeightSplice(xml, scanTable, tblOpen[0].length, heightDelta);
22010
+ if (sp) splices.push(sp);
21169
22011
  }
21170
- const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
21171
- return { lines, hadNonText };
22012
+ ctx.sectionSplices[scanTable.sectionIndex].push(...splices);
22013
+ for (const k of keeps) {
22014
+ if (origKeys[k.oi] !== editedKeys[k.ei]) applied += input.patchMatched(k.oi, k.ei);
22015
+ }
22016
+ return applied;
21172
22017
  }
21173
- function extractTopLevelTables(html) {
21174
- const result = [];
21175
- let depth = 0;
21176
- let start = -1;
21177
- const re = /<(\/?)table(?:[\s>]|>)/gi;
21178
- let m;
21179
- while ((m = re.exec(html)) !== null) {
21180
- if (m[1] !== "/") {
21181
- if (depth === 0) start = m.index;
21182
- depth++;
21183
- } else {
21184
- depth--;
21185
- if (depth === 0 && start >= 0) {
21186
- result.push(html.slice(start, m.index + m[0].length));
21187
- start = -1;
21188
- }
21189
- if (depth < 0) depth = 0;
22018
+ var FRAG_OPEN = "<hp:tbl>";
22019
+ var FRAG_CLOSE = "</hp:tbl>";
22020
+ function buildRowFragment(xml, scanTable, template, cells, finalRow, hasExplicitAddr, ctx) {
22021
+ const rr = scanTable.rowRanges[template];
22022
+ const wrapped = FRAG_OPEN + xml.slice(rr.start, rr.end) + FRAG_CLOSE;
22023
+ const scan = scanSectionXml(wrapped, 0);
22024
+ const row = scan.tables[0]?.rows[0];
22025
+ if (!row || row.length !== cells.length) return null;
22026
+ const splices = allLinesegRemovalSplices(wrapped);
22027
+ for (let i = 0; i < cells.length; i++) {
22028
+ const paras = row[i].paragraphs;
22029
+ let lines = cells[i].lines;
22030
+ if (lines.length > 0 && paras.length === 0) return null;
22031
+ if (lines.length > paras.length) {
22032
+ lines = [...lines.slice(0, paras.length - 1), lines.slice(paras.length - 1).join(" ")];
22033
+ 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 });
22034
+ }
22035
+ for (let p = 0; p < paras.length; p++) {
22036
+ const sp = buildParagraphSplices(paras[p], lines[p] ?? "", wrapped);
22037
+ if (sp === null) return null;
22038
+ splices.push(...sp);
22039
+ }
22040
+ if (hasExplicitAddr) {
22041
+ const sp = rowAddrRewrite(wrapped, row[i], finalRow);
22042
+ if (sp) splices.push(sp);
21190
22043
  }
21191
22044
  }
21192
- return result;
22045
+ const patched = applySplices(wrapped, splices);
22046
+ return patched.slice(FRAG_OPEN.length, patched.length - FRAG_CLOSE.length);
21193
22047
  }
21194
- function removeNestedTables(html) {
21195
- let result = "";
21196
- let depth = 0;
21197
- const re = /<(\/?)table(?:[\s>]|>)/gi;
21198
- let last = 0;
21199
- let m;
21200
- while ((m = re.exec(html)) !== null) {
21201
- if (m[1] !== "/") {
21202
- if (depth === 0) result += html.slice(last, m.index);
21203
- depth++;
21204
- } else {
21205
- depth--;
21206
- if (depth === 0) last = m.index + m[0].length;
21207
- if (depth < 0) depth = 0;
21208
- }
22048
+ function rowAddrRewrite(xml, cell, newRow) {
22049
+ if (!cell.addrTagRange) return null;
22050
+ const tag = xml.slice(cell.addrTagRange.start, cell.addrTagRange.end);
22051
+ const m = tag.match(/\browAddr\s*=\s*"(\d+)"/);
22052
+ if (!m || m.index === void 0) return null;
22053
+ if (parseInt(m[1], 10) === newRow) return null;
22054
+ const valStart = cell.addrTagRange.start + m.index + m[0].indexOf('"') + 1;
22055
+ return { start: valStart, end: valStart + m[1].length, replacement: String(newRow) };
22056
+ }
22057
+ function rowHeightOf(fragment) {
22058
+ let max = 0;
22059
+ for (const m of fragment.matchAll(/<(?:[A-Za-z0-9_]+:)?cellSz\b(?:"[^"]*"|'[^']*'|[^>"'])*>/g)) {
22060
+ const h = m[0].match(/\bheight\s*=\s*"(\d+)"/);
22061
+ if (h) max = Math.max(max, parseInt(h[1], 10));
21209
22062
  }
21210
- if (depth === 0) result += html.slice(last);
21211
- return result;
22063
+ return max;
22064
+ }
22065
+ function tableSzHeightSplice(xml, scanTable, tblOpenLen, delta) {
22066
+ const from = scanTable.start + tblOpenLen;
22067
+ const to = scanTable.rowRanges[0]?.start ?? from;
22068
+ const slice = xml.slice(from, to);
22069
+ const szM = slice.match(/<(?:[A-Za-z0-9_]+:)?sz\b(?:"[^"]*"|'[^']*'|[^>"'])*>/);
22070
+ if (!szM || szM.index === void 0) return null;
22071
+ const hM = szM[0].match(/\bheight\s*=\s*"(\d+)"/);
22072
+ if (!hM || hM.index === void 0) return null;
22073
+ const oldH = parseInt(hM[1], 10);
22074
+ const newH = Math.max(0, oldH + delta);
22075
+ const valStart = from + szM.index + hM.index + hM[0].indexOf('"') + 1;
22076
+ return { start: valStart, end: valStart + hM[1].length, replacement: String(newH) };
21212
22077
  }
21213
22078
 
21214
22079
  // src/roundtrip/table-patch.ts
@@ -21220,37 +22085,89 @@ function patchGfmTable(table, scanTable, orig, edited, ctx, skip) {
21220
22085
  if (replica.length !== origRows.length || replica.some((row, r) => row.length !== origRows[r].length || row.some((c, j) => c.text !== origRows[r][j]))) {
21221
22086
  return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
21222
22087
  }
21223
- if (editedRows.length !== origRows.length) return skip("\uD45C \uD589 \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
22088
+ if (editedRows.length !== origRows.length) {
22089
+ return patchGfmTableRows(table, scanTable, origRows, editedRows, replica, ctx, skip);
22090
+ }
21224
22091
  let applied = 0;
21225
22092
  for (let r = 0; r < origRows.length; r++) {
21226
- if (editedRows[r].length !== origRows[r].length) {
21227
- skip(`\uD45C ${r + 1}\uD589 \uC5F4 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22093
+ applied += patchGfmRowPair(table, scanTable, origRows, editedRows, replica, r, r, ctx, skip);
22094
+ }
22095
+ return applied;
22096
+ }
22097
+ function patchGfmRowPair(table, scanTable, origRows, editedRows, replica, r, er, ctx, skip) {
22098
+ if (editedRows[er].length !== origRows[r].length) {
22099
+ skip(`\uD45C ${r + 1}\uD589 \uC5F4 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22100
+ return 0;
22101
+ }
22102
+ let applied = 0;
22103
+ for (let c = 0; c < origRows[r].length; c++) {
22104
+ if (origRows[r][c] === editedRows[er][c]) continue;
22105
+ const { gridR, gridC } = replica[r][c];
22106
+ const origTokens = extractCellTokens(origRows[r][c]);
22107
+ const editedTokens = extractCellTokens(editedRows[er][c]);
22108
+ if (origTokens !== editedTokens) {
22109
+ skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21228
22110
  continue;
21229
22111
  }
21230
- for (let c = 0; c < origRows[r].length; c++) {
21231
- if (origRows[r][c] === editedRows[r][c]) continue;
21232
- const { gridR, gridC } = replica[r][c];
21233
- const origTokens = extractCellTokens(origRows[r][c]);
21234
- const editedTokens = extractCellTokens(editedRows[r][c]);
21235
- if (origTokens !== editedTokens) {
21236
- skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21237
- continue;
21238
- }
21239
- const newLines = unescapeGfmCell(stripCellTokens(editedRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
21240
- const origLines = unescapeGfmCell(stripCellTokens(origRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
21241
- const n = applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, origRows[r][c], editedRows[r][c], origLines.length);
21242
- if (n > 0 && origTokens) {
21243
- ctx.skipped.push({
21244
- 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)",
21245
- before: summarize(origRows[r][c]),
21246
- after: summarize(editedRows[r][c])
21247
- });
21248
- }
21249
- applied += n;
22112
+ const newLines = unescapeGfmCell(stripCellTokens(editedRows[er][c])).split("\n").map((s) => s.trim()).filter(Boolean);
22113
+ const origLines = unescapeGfmCell(stripCellTokens(origRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
22114
+ const n = applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, origRows[r][c], editedRows[er][c], origLines.length);
22115
+ if (n > 0 && origTokens) {
22116
+ ctx.skipped.push({
22117
+ 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)",
22118
+ before: summarize(origRows[r][c]),
22119
+ after: summarize(editedRows[er][c]),
22120
+ partial: true
22121
+ });
21250
22122
  }
22123
+ applied += n;
21251
22124
  }
21252
22125
  return applied;
21253
22126
  }
22127
+ function patchGfmTableRows(table, scanTable, origRows, editedRows, replica, ctx, skip) {
22128
+ if (replica.length !== table.rows || replica.some((row, r) => row.some((c) => c.gridR !== r))) {
22129
+ 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");
22130
+ }
22131
+ if (table.cells.some((row) => row.some((c) => c && (c.colSpan > 1 || c.rowSpan > 1)))) {
22132
+ return skip("\uBCD1\uD569 \uC140 \uD45C \u2014 GFM \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
22133
+ }
22134
+ if (!gfmRenderStable(editedRows, table.cols)) {
22135
+ 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");
22136
+ }
22137
+ const key = (row) => row.join("\0");
22138
+ return patchTableRows({
22139
+ table,
22140
+ scanTable,
22141
+ ctx,
22142
+ skip,
22143
+ origKeys: origRows.map(key),
22144
+ editedKeys: editedRows.map(key),
22145
+ editedCells: (ei) => {
22146
+ const cells = [];
22147
+ for (const cellMd of editedRows[ei]) {
22148
+ if (extractCellTokens(cellMd)) return null;
22149
+ const lines = unescapeGfmCell(cellMd).split("\n").map((s) => s.trim()).filter(Boolean);
22150
+ cells.push({ lines, colSpan: 1, rowSpan: 1 });
22151
+ }
22152
+ return cells;
22153
+ },
22154
+ patchMatched: (oi, ei) => patchGfmRowPair(table, scanTable, origRows, editedRows, replica, oi, ei, ctx, skip)
22155
+ });
22156
+ }
22157
+ function gfmRenderStable(editedRows, cols) {
22158
+ const sim = {
22159
+ rows: editedRows.length,
22160
+ cols,
22161
+ hasHeader: editedRows.length > 1,
22162
+ cells: editedRows.map((row) => {
22163
+ const padded = row.length < cols ? [...row, ...Array(cols - row.length).fill("")] : row;
22164
+ return padded.map((md2) => ({ text: unescapeGfmCell(md2), colSpan: 1, rowSpan: 1 }));
22165
+ })
22166
+ };
22167
+ const replica = replicateGfmTable(sim);
22168
+ if (!replica || replica.length !== editedRows.length) return false;
22169
+ return replica.every((row, r) => row.length === editedRows[r].length && row.every((c, j) => c.text === editedRows[r][j]));
22170
+ }
21254
22171
  function patchHtmlTable(table, scanTable, orig, edited, ctx, skip) {
21255
22172
  return patchHtmlTableRaw(table, scanTable, orig.raw, edited.raw, ctx, skip, 0);
21256
22173
  }
@@ -21264,46 +22181,77 @@ function patchHtmlTableRaw(table, scanTable, origRaw, editedRaw, ctx, skip, dept
21264
22181
  }
21265
22182
  const editedRows = parseHtmlTable(editedRaw);
21266
22183
  if (!editedRows) return skip("\uD3B8\uC9D1\uB41C HTML \uD45C \uD30C\uC2F1 \uC2E4\uD328");
21267
- if (editedRows.length !== replica.length) return skip("\uD45C \uD589 \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
22184
+ if (editedRows.length !== replica.length) {
22185
+ return patchHtmlTableRows(table, scanTable, replica, editedRows, ctx, skip, depth);
22186
+ }
21268
22187
  let applied = 0;
21269
22188
  for (let r = 0; r < replica.length; r++) {
21270
- if (editedRows[r].cells.length !== replica[r].cells.length) {
21271
- skip(`\uD45C ${r + 1}\uD589 \uC140 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22189
+ applied += patchHtmlRowPair(table, scanTable, replica, editedRows, r, r, ctx, skip, depth);
22190
+ }
22191
+ return applied;
22192
+ }
22193
+ function patchHtmlRowPair(table, scanTable, replica, editedRows, r, er, ctx, skip, depth) {
22194
+ if (editedRows[er].cells.length !== replica[r].cells.length) {
22195
+ skip(`\uD45C ${r + 1}\uD589 \uC140 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22196
+ return 0;
22197
+ }
22198
+ let applied = 0;
22199
+ for (let c = 0; c < replica[r].cells.length; c++) {
22200
+ const oc = replica[r].cells[c];
22201
+ const ec = editedRows[er].cells[c];
22202
+ if (oc.colSpan !== ec.colSpan || oc.rowSpan !== ec.rowSpan) {
22203
+ skip(`\uC140 \uBCD1\uD569(colspan/rowspan) \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21272
22204
  continue;
21273
22205
  }
21274
- for (let c = 0; c < replica[r].cells.length; c++) {
21275
- const oc = replica[r].cells[c];
21276
- const ec = editedRows[r].cells[c];
21277
- if (oc.colSpan !== ec.colSpan || oc.rowSpan !== ec.rowSpan) {
21278
- skip(`\uC140 \uBCD1\uD569(colspan/rowspan) \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22206
+ if (oc.inner === ec.inner) continue;
22207
+ const origContent = htmlCellInnerToLines(oc.inner);
22208
+ const editedContent = htmlCellInnerToLines(ec.inner);
22209
+ if (origContent.hadNonText || editedContent.hadNonText) {
22210
+ if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
22211
+ skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21279
22212
  continue;
21280
22213
  }
21281
- if (oc.inner === ec.inner) continue;
21282
- const origContent = htmlCellInnerToLines(oc.inner);
21283
- const editedContent = htmlCellInnerToLines(ec.inner);
21284
- if (origContent.hadNonText || editedContent.hadNonText) {
21285
- if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
21286
- skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21287
- continue;
21288
- }
21289
- const origTables = extractTopLevelTables(oc.inner);
21290
- const editedTables = extractTopLevelTables(ec.inner);
21291
- if (origTables.length !== editedTables.length) {
21292
- skip("\uC140 \uB0B4 \uC911\uCCA9\uD45C \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
21293
- continue;
21294
- }
21295
- if (origTables.join("\n") !== editedTables.join("\n")) {
21296
- applied += patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth);
21297
- }
22214
+ const origTables = extractTopLevelTables(oc.inner);
22215
+ const editedTables = extractTopLevelTables(ec.inner);
22216
+ if (origTables.length !== editedTables.length) {
22217
+ skip("\uC140 \uB0B4 \uC911\uCCA9\uD45C \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
22218
+ continue;
21298
22219
  }
21299
- if (origContent.lines.join("\n") !== editedContent.lines.join("\n")) {
21300
- const newLines = editedContent.lines.map((l) => unescapeGfm(l));
21301
- applied += applyCellEdit(table, scanTable, oc.gridR, oc.gridC, newLines, ctx, oc.inner, ec.inner, origContent.lines.length);
22220
+ if (origTables.join("\n") !== editedTables.join("\n")) {
22221
+ applied += patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth);
21302
22222
  }
21303
22223
  }
22224
+ if (origContent.lines.join("\n") !== editedContent.lines.join("\n")) {
22225
+ const newLines = editedContent.lines.map((l) => unescapeGfm(l));
22226
+ applied += applyCellEdit(table, scanTable, oc.gridR, oc.gridC, newLines, ctx, oc.inner, ec.inner, origContent.lines.length);
22227
+ }
21304
22228
  }
21305
22229
  return applied;
21306
22230
  }
22231
+ function patchHtmlTableRows(table, scanTable, replica, editedRows, ctx, skip, depth) {
22232
+ if (replica.length !== table.rows || replica.some((row, r) => row.cells.some((c) => c.gridR !== r))) {
22233
+ 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");
22234
+ }
22235
+ const key = (row) => row.cells.map((c) => `${c.colSpan}x${c.rowSpan}:${c.inner}`).join("\0");
22236
+ return patchTableRows({
22237
+ table,
22238
+ scanTable,
22239
+ ctx,
22240
+ skip,
22241
+ origKeys: replica.map(key),
22242
+ editedKeys: editedRows.map(key),
22243
+ editedCells: (ei) => {
22244
+ const cells = [];
22245
+ for (const cell of editedRows[ei].cells) {
22246
+ const content = htmlCellInnerToLines(cell.inner);
22247
+ if (content.hadNonText) return null;
22248
+ cells.push({ lines: content.lines.map((l) => unescapeGfm(l)), colSpan: cell.colSpan, rowSpan: cell.rowSpan });
22249
+ }
22250
+ return cells;
22251
+ },
22252
+ patchMatched: (oi, ei) => patchHtmlRowPair(table, scanTable, replica, editedRows, oi, ei, ctx, skip, depth)
22253
+ });
22254
+ }
21307
22255
  function patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth) {
21308
22256
  const irCell = table.cells[oc.gridR]?.[oc.gridC];
21309
22257
  const scanCell = scanTable.cellByAnchor.get(`${oc.gridR},${oc.gridC}`);
@@ -21403,7 +22351,7 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21403
22351
  splices.push(...sp);
21404
22352
  sectionIndex = target.sectionIndex;
21405
22353
  if (newLines.length > 1) {
21406
- 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) });
22354
+ 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 });
21407
22355
  }
21408
22356
  } else {
21409
22357
  const assigned = [];
@@ -21415,7 +22363,9 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21415
22363
  }
21416
22364
  }
21417
22365
  if (newLines.length > nonEmpty.length) {
21418
- 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) });
22366
+ 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 });
22367
+ } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
22368
+ 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 });
21419
22369
  }
21420
22370
  for (let i = 0; i < nonEmpty.length; i++) {
21421
22371
  if (assigned[i] === nonEmpty[i].text || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].text)) continue;
@@ -21588,7 +22538,8 @@ async function patchHwpx(original, editedMarkdown, options) {
21588
22538
  try {
21589
22539
  for (let i = 0; i < scans.length; i++) {
21590
22540
  if (sectionSplices[i].length === 0) continue;
21591
- sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml));
22541
+ const claimed = sectionSplices[i].filter((s) => s.end > s.start);
22542
+ sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml).filter((ls) => !claimed.some((c) => ls.start >= c.start && ls.end <= c.end)));
21592
22543
  const newXml = applySplices(scans[i].xml, sectionSplices[i]);
21593
22544
  replacements.set(sectionPaths[i], encoder.encode(newXml));
21594
22545
  }
@@ -21656,82 +22607,6 @@ function buildTableOrdinals(blocks) {
21656
22607
  }
21657
22608
  return map;
21658
22609
  }
21659
- function alignUnits(a, b) {
21660
- const m = a.length, n = b.length;
21661
- if (m * n > 4e6) {
21662
- const result2 = [];
21663
- let pre = 0;
21664
- while (pre < m && pre < n && a[pre] === b[pre]) {
21665
- result2.push([pre, pre]);
21666
- pre++;
21667
- }
21668
- let suf = 0;
21669
- while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
21670
- const aMid = m - pre - suf, bMid = n - pre - suf;
21671
- if (aMid === bMid) {
21672
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
21673
- } else {
21674
- for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
21675
- for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
21676
- }
21677
- for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
21678
- return result2;
21679
- }
21680
- const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
21681
- for (let i2 = 1; i2 <= m; i2++) {
21682
- for (let j2 = 1; j2 <= n; j2++) {
21683
- 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]);
21684
- }
21685
- }
21686
- const matches = [];
21687
- let i = m, j = n;
21688
- while (i > 0 && j > 0) {
21689
- if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
21690
- matches.push([i - 1, j - 1]);
21691
- i--;
21692
- j--;
21693
- } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
21694
- else j--;
21695
- }
21696
- matches.reverse();
21697
- const result = [];
21698
- let ai = 0, bi = 0;
21699
- const flushGap = (aEnd, bEnd) => {
21700
- if (aEnd - ai === bEnd - bi) {
21701
- while (ai < aEnd) result.push([ai++, bi++]);
21702
- return;
21703
- }
21704
- while (ai < aEnd && bi < bEnd) {
21705
- const sim = normalizedSimilarity(a[ai], b[bi]);
21706
- if (sim >= 0.4) {
21707
- if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
21708
- result.push([ai++, null]);
21709
- } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
21710
- result.push([null, bi++]);
21711
- } else {
21712
- result.push([ai++, bi++]);
21713
- }
21714
- } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
21715
- else result.push([null, bi++]);
21716
- }
21717
- while (ai < aEnd) result.push([ai++, null]);
21718
- while (bi < bEnd) result.push([null, bi++]);
21719
- };
21720
- for (const [pi, pj] of matches) {
21721
- flushGap(pi, pj);
21722
- result.push([ai++, bi++]);
21723
- }
21724
- flushGap(m, n);
21725
- return result;
21726
- }
21727
- function bestSimInRange(arr, from, to, target) {
21728
- let best = 0;
21729
- for (let k = from; k <= to && k < arr.length; k++) {
21730
- const s = normalizedSimilarity(arr[k], target);
21731
- if (s > best) best = s;
21732
- }
21733
- return best;
21734
- }
21735
22610
  function resolveParagraphMappings(blocks, scans) {
21736
22611
  const buckets = /* @__PURE__ */ new Map();
21737
22612
  for (const scan of scans) {
@@ -21874,7 +22749,7 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
21874
22749
  const origPrefix = block.text.split(" ", 1)[0];
21875
22750
  const sp = newPlain.indexOf(" ");
21876
22751
  const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
21877
- if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
22752
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
21878
22753
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
21879
22754
  } else {
21880
22755
  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) });
@@ -22617,7 +23492,7 @@ function patchParagraph(block, orig, edited, ctx, skip) {
22617
23492
  const origPrefix = block.text.split(" ", 1)[0];
22618
23493
  const sp = newPlain.indexOf(" ");
22619
23494
  const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
22620
- if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
23495
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
22621
23496
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
22622
23497
  } else {
22623
23498
  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) });
@@ -22682,9 +23557,6 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
22682
23557
  }
22683
23558
  return applied;
22684
23559
  }
22685
- function extractImgTags5(inner) {
22686
- return (inner.match(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi) || []).join(" ");
22687
- }
22688
23560
  function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
22689
23561
  if (replicateTableToHtml(table) !== orig.raw) return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
22690
23562
  const replica = replicateHtmlTable(table);
@@ -22712,7 +23584,7 @@ function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
22712
23584
  const origContent = htmlCellInnerToLines(oc.inner);
22713
23585
  const editedContent = htmlCellInnerToLines(ec.inner);
22714
23586
  if (origContent.hadNonText || editedContent.hadNonText) {
22715
- if (extractImgTags5(oc.inner) !== extractImgTags5(ec.inner)) {
23587
+ if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
22716
23588
  skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
22717
23589
  continue;
22718
23590
  }
@@ -22803,7 +23675,9 @@ function applyCellEdit5(table, scanTable, gridR, gridC, newLines, ctx, before, a
22803
23675
  }
22804
23676
  }
22805
23677
  if (newLines.length > nonEmpty.length) {
22806
- 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) });
23678
+ 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 });
23679
+ } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
23680
+ 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 });
22807
23681
  }
22808
23682
  let staged = 0;
22809
23683
  for (let i = 0; i < nonEmpty.length; i++) {
@@ -23178,6 +24052,7 @@ var HwpxSession = class _HwpxSession {
23178
24052
  try {
23179
24053
  for (let s = 0; s < st.scans.length; s++) {
23180
24054
  if (sectionSplices[s].length === 0) continue;
24055
+ sectionSplices[s].push(...allLinesegRemovalSplices(st.scans[s].xml));
23181
24056
  replacements.set(st.sectionPaths[s], encoder.encode(applySplices(st.scans[s].xml, sectionSplices[s])));
23182
24057
  }
23183
24058
  } catch (err) {
@@ -23234,7 +24109,7 @@ var HwpxSession = class _HwpxSession {
23234
24109
  const origPrefix = block.text.split(" ", 1)[0];
23235
24110
  const sp = newPlain.indexOf(" ");
23236
24111
  const newFirst = sp > 0 ? newPlain.slice(0, sp) : newPlain;
23237
- if (newFirst === origPrefix || /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u.test(newFirst)) {
24112
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
23238
24113
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
23239
24114
  } else {
23240
24115
  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) });
@@ -23528,7 +24403,7 @@ async function parseHwp(buffer, options) {
23528
24403
  async function parsePdf(buffer, options) {
23529
24404
  let parsePdfDocument;
23530
24405
  try {
23531
- const mod = await import("./parser-XT65AEDJ.js");
24406
+ const mod = await import("./parser-ZHJFQR44.js");
23532
24407
  parsePdfDocument = mod.parsePdfDocument;
23533
24408
  } catch {
23534
24409
  return {
@@ -23620,12 +24495,16 @@ async function fillForm(input, values, outputFormat = "markdown") {
23620
24495
  export {
23621
24496
  HwpxSession,
23622
24497
  PRESET_ALIAS,
24498
+ SPACE_EM_FIXED,
24499
+ SPACE_EM_FONT,
23623
24500
  VERSION,
24501
+ ValueCursor,
23624
24502
  applySplices,
23625
24503
  blocksToMarkdown,
23626
24504
  blocksToPdf,
23627
24505
  buildParagraphSplices,
23628
24506
  buildRangeSplices,
24507
+ charWidthEm1000,
23629
24508
  compare,
23630
24509
  detectFormat,
23631
24510
  detectOle2Format,
@@ -23636,6 +24515,7 @@ export {
23636
24515
  fillForm,
23637
24516
  fillFormFields,
23638
24517
  fillHwpx,
24518
+ fitRatioForFewerLines,
23639
24519
  inferFieldType,
23640
24520
  isHwpxFile,
23641
24521
  isLabelCell,
@@ -23644,6 +24524,7 @@ export {
23644
24524
  isZipFile,
23645
24525
  markdownToHwpx,
23646
24526
  markdownToPdf,
24527
+ measureTextWidth,
23647
24528
  normalizeGongmunPreset,
23648
24529
  openHwpxDocument,
23649
24530
  parse,
@@ -23659,6 +24540,8 @@ export {
23659
24540
  patchHwpx,
23660
24541
  patchHwpxBlocks,
23661
24542
  renderHtml,
23662
- scanSectionXml
24543
+ scanSectionXml,
24544
+ simulateWrap,
24545
+ simulateWrapKeepWord
23663
24546
  };
23664
24547
  //# sourceMappingURL=index.js.map