kordoc 3.5.4 → 3.8.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 +23 -1
  2. package/dist/{-KX7VLNW5.js → -ATVQYFSW.js} +21 -5
  3. package/dist/{chunk-WEAP42T3.js → chunk-3R3YK7EM.js} +2 -2
  4. package/dist/chunk-3R3YK7EM.js.map +1 -0
  5. package/dist/{chunk-XZTDYOZV.cjs → chunk-ITJIALN5.cjs} +2 -2
  6. package/dist/{chunk-XZTDYOZV.cjs.map → chunk-ITJIALN5.cjs.map} +1 -1
  7. package/dist/{chunk-VEB43O2L.js → chunk-QV25HMU7.js} +2 -2
  8. package/dist/chunk-QV25HMU7.js.map +1 -0
  9. package/dist/{chunk-DK2IE43H.js → chunk-SLKF72QF.js} +1682 -718
  10. package/dist/chunk-SLKF72QF.js.map +1 -0
  11. package/dist/cli.js +4 -4
  12. package/dist/index.cjs +1908 -944
  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 +1681 -717
  17. package/dist/index.js.map +1 -1
  18. package/dist/mcp.js +3 -3
  19. package/dist/{parser-W5TBEAVX.js → parser-7G5F7PT2.js} +2 -2
  20. package/dist/{parser-XT65AEDJ.js → parser-DCK42RMA.js} +2 -2
  21. package/dist/{parser-CPTBEGQP.cjs → parser-GUSJH44K.cjs} +14 -14
  22. package/dist/{parser-CPTBEGQP.cjs.map → parser-GUSJH44K.cjs.map} +1 -1
  23. package/dist/{watch-VOSNRZAY.js → watch-GVZESOCE.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 → -ATVQYFSW.js.map} +0 -0
  29. /package/dist/{parser-W5TBEAVX.js.map → parser-7G5F7PT2.js.map} +0 -0
  30. /package/dist/{parser-XT65AEDJ.js.map → parser-DCK42RMA.js.map} +0 -0
  31. /package/dist/{watch-VOSNRZAY.js.map → watch-GVZESOCE.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-QV25HMU7.js";
23
23
  import {
24
24
  parsePageRange
25
25
  } from "./chunk-GE43BE46.js";
@@ -1212,57 +1212,62 @@ async function extractImagesFromZip(zip, blocks, decompressed, warnings) {
1212
1212
  let imageIndex = 0;
1213
1213
  const imageBlocks = [];
1214
1214
  collectImageBlocks(blocks, imageBlocks);
1215
+ const resolved = /* @__PURE__ */ new Map();
1215
1216
  for (const { block, ownerCell } of imageBlocks) {
1216
1217
  if (block.type !== "image" || !block.text) continue;
1217
1218
  const ref = block.text;
1218
- const candidates = [
1219
- `BinData/${ref}`,
1220
- `Contents/BinData/${ref}`,
1221
- ref
1222
- // 절대 경로일 수도 있음
1223
- ];
1224
- let resolvedPath = null;
1225
- if (!ref.includes(".")) {
1226
- const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
1227
- for (const prefix of prefixes) {
1228
- const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
1229
- if (match.length > 0) {
1230
- resolvedPath = match[0].name;
1231
- break;
1219
+ let img = resolved.get(ref);
1220
+ if (img === void 0) {
1221
+ img = null;
1222
+ const candidates = [
1223
+ `BinData/${ref}`,
1224
+ `Contents/BinData/${ref}`,
1225
+ ref
1226
+ // 절대 경로일 수도 있음
1227
+ ];
1228
+ let resolvedPath = null;
1229
+ if (!ref.includes(".")) {
1230
+ const prefixes = [`BinData/${ref}`, `Contents/BinData/${ref}`];
1231
+ for (const prefix of prefixes) {
1232
+ const match = zip.file(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.[a-zA-Z0-9]+$`));
1233
+ if (match.length > 0) {
1234
+ resolvedPath = match[0].name;
1235
+ break;
1236
+ }
1232
1237
  }
1233
1238
  }
1234
- }
1235
- let found = false;
1236
- const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
1237
- for (const path of allCandidates) {
1238
- if (isPathTraversal(path)) continue;
1239
- const file = zip.file(path);
1240
- if (!file) continue;
1241
- try {
1242
- const data = await file.async("uint8array");
1243
- decompressed.total += data.length;
1244
- if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
1245
- const actualPath = path;
1246
- const ext = actualPath.includes(".") ? actualPath.split(".").pop() || "png" : "png";
1247
- const mimeType = imageExtToMime(ext);
1248
- imageIndex++;
1249
- const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
1250
- images.push({ filename, data, mimeType });
1251
- block.text = filename;
1252
- block.imageData = { data, mimeType, filename: ref };
1253
- if (ownerCell) ownerCell.text = ownerCell.text.replace(`![image](${ref})`, `![image](${filename})`);
1254
- found = true;
1255
- break;
1256
- } catch (err) {
1257
- if (err instanceof KordocError) throw err;
1239
+ const allCandidates = resolvedPath ? [resolvedPath, ...candidates] : candidates;
1240
+ for (const path of allCandidates) {
1241
+ if (isPathTraversal(path)) continue;
1242
+ const file = zip.file(path);
1243
+ if (!file) continue;
1244
+ try {
1245
+ const data = await file.async("uint8array");
1246
+ decompressed.total += data.length;
1247
+ if (decompressed.total > MAX_DECOMPRESS_SIZE) throw new KordocError("ZIP \uC555\uCD95 \uD574\uC81C \uD06C\uAE30 \uCD08\uACFC (ZIP bomb \uC758\uC2EC)");
1248
+ const ext = path.includes(".") ? path.split(".").pop() || "png" : "png";
1249
+ const mimeType = imageExtToMime(ext);
1250
+ imageIndex++;
1251
+ const filename = `image_${String(imageIndex).padStart(3, "0")}.${mimeToExt(mimeType)}`;
1252
+ img = { filename, data, mimeType };
1253
+ images.push(img);
1254
+ break;
1255
+ } catch (err) {
1256
+ if (err instanceof KordocError) throw err;
1257
+ }
1258
1258
  }
1259
+ if (!img) warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
1260
+ resolved.set(ref, img);
1259
1261
  }
1260
- if (!found) {
1261
- warnings?.push({ page: block.pageNumber, message: `\uC774\uBBF8\uC9C0 \uD30C\uC77C \uC5C6\uC74C: ${ref}`, code: "SKIPPED_IMAGE" });
1262
+ if (!img) {
1262
1263
  block.type = "paragraph";
1263
1264
  block.text = `[\uC774\uBBF8\uC9C0: ${ref}]`;
1264
1265
  if (ownerCell) ownerCell.text = ownerCell.text.replace(`![image](${ref})`, `[\uC774\uBBF8\uC9C0: ${ref}]`);
1266
+ continue;
1265
1267
  }
1268
+ block.text = img.filename;
1269
+ block.imageData = { data: img.data, mimeType: img.mimeType, filename: ref };
1270
+ if (ownerCell) ownerCell.text = ownerCell.text.replace(`![image](${ref})`, `![image](${img.filename})`);
1266
1271
  }
1267
1272
  return images;
1268
1273
  }
@@ -1450,9 +1455,32 @@ function detectHwpxHeadings(blocks, styleMap) {
1450
1455
  function buildTableWithCellMeta(state) {
1451
1456
  const table = buildTable(state.rows);
1452
1457
  if (state.caption) table.caption = state.caption;
1458
+ const anchors = [];
1459
+ {
1460
+ const covered = /* @__PURE__ */ new Set();
1461
+ for (let r = 0; r < table.rows; r++) {
1462
+ for (let c = 0; c < table.cols; c++) {
1463
+ if (covered.has(`${r},${c}`)) continue;
1464
+ const cell = table.cells[r]?.[c];
1465
+ if (!cell) continue;
1466
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
1467
+ for (let dc = 0; dc < cell.colSpan; dc++) {
1468
+ if (dr === 0 && dc === 0) continue;
1469
+ if (r + dr < table.rows && c + dc < table.cols) covered.add(`${r + dr},${c + dc}`);
1470
+ }
1471
+ }
1472
+ anchors.push(cell);
1473
+ c += cell.colSpan - 1;
1474
+ }
1475
+ }
1476
+ }
1477
+ const srcCount = state.rows.reduce((s, r) => s + r.length, 0);
1478
+ const ordinalReliable = anchors.length === srcCount;
1453
1479
  const claimed = /* @__PURE__ */ new Set();
1480
+ let flatIdx = -1;
1454
1481
  for (const row of state.rows) {
1455
1482
  for (const src of row) {
1483
+ flatIdx++;
1456
1484
  const needsBlocks = src.hasStructure && src.blocks && src.blocks.length > 0;
1457
1485
  if (!needsBlocks && !src.isHeader) continue;
1458
1486
  let target;
@@ -1471,6 +1499,10 @@ function buildTableWithCellMeta(state) {
1471
1499
  }
1472
1500
  }
1473
1501
  }
1502
+ if (!target && ordinalReliable) {
1503
+ const cand = anchors[flatIdx];
1504
+ if (cand && !claimed.has(cand)) target = cand;
1505
+ }
1474
1506
  if (!target) continue;
1475
1507
  claimed.add(target);
1476
1508
  if (needsBlocks) target.blocks = src.blocks;
@@ -2601,32 +2633,42 @@ function resolveImageBlocks(binDataMap, blocks, warnings) {
2601
2633
  if (imageBlocks.length === 0) return [];
2602
2634
  const images = [];
2603
2635
  const renamed = /* @__PURE__ */ new Map();
2636
+ const resolved = /* @__PURE__ */ new Map();
2604
2637
  let imageIndex = 0;
2605
2638
  for (const block of imageBlocks) {
2606
2639
  if (!block.text) continue;
2607
2640
  const storageId = parseInt(block.text, 10);
2608
2641
  if (isNaN(storageId)) continue;
2609
- const bin = binDataMap.get(storageId);
2610
- if (!bin) {
2611
- warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
2612
- block.type = "paragraph";
2613
- block.text = `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
2614
- continue;
2642
+ let img = resolved.get(storageId);
2643
+ if (img === void 0) {
2644
+ const bin = binDataMap.get(storageId);
2645
+ if (!bin) {
2646
+ warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
2647
+ resolved.set(storageId, null);
2648
+ } else {
2649
+ const mime = detectImageMime(bin.data);
2650
+ if (!mime) {
2651
+ warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
2652
+ resolved.set(storageId, null);
2653
+ } else {
2654
+ imageIndex++;
2655
+ const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
2656
+ img = { filename: `image_${String(imageIndex).padStart(3, "0")}.${ext}`, data: new Uint8Array(bin.data), mime };
2657
+ resolved.set(storageId, img);
2658
+ images.push({ filename: img.filename, data: img.data, mimeType: img.mime });
2659
+ renamed.set(storageId, img.filename);
2660
+ }
2661
+ }
2662
+ img = resolved.get(storageId);
2615
2663
  }
2616
- const mime = detectImageMime(bin.data);
2617
- if (!mime) {
2618
- warnings.push({ page: block.pageNumber, message: `BinData ${storageId}: \uC54C \uC218 \uC5C6\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD`, code: "SKIPPED_IMAGE" });
2664
+ if (!img) {
2665
+ const bin = binDataMap.get(storageId);
2619
2666
  block.type = "paragraph";
2620
- block.text = `[\uC774\uBBF8\uC9C0: ${bin.name}]`;
2667
+ block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
2621
2668
  continue;
2622
2669
  }
2623
- imageIndex++;
2624
- const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
2625
- const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
2626
- images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime });
2627
- renamed.set(storageId, filename);
2628
- block.text = filename;
2629
- block.imageData = { data: new Uint8Array(bin.data), mimeType: mime, filename: bin.name };
2670
+ block.text = img.filename;
2671
+ block.imageData = { data: img.data, mimeType: img.mime, filename: binDataMap.get(storageId).name };
2630
2672
  }
2631
2673
  resolveCellImageSentinels(blocks, renamed);
2632
2674
  return images;
@@ -18314,7 +18356,7 @@ function parseTable(tbl, styles, numbering, footnotes, rels) {
18314
18356
  };
18315
18357
  return { type: "table", table };
18316
18358
  }
18317
- async function extractImages(zip, rels, doc) {
18359
+ async function extractImages(zip, rels, doc, warnings) {
18318
18360
  const blocks = [];
18319
18361
  const images = [];
18320
18362
  const drawingElements = findElements(doc.documentElement, "drawing");
@@ -18345,7 +18387,11 @@ async function extractImages(zip, rels, doc) {
18345
18387
  const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
18346
18388
  images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
18347
18389
  blocks.push({ type: "image", text: filename });
18348
- } catch {
18390
+ } catch (err) {
18391
+ warnings.push({
18392
+ code: "SKIPPED_IMAGE",
18393
+ message: `DOCX \uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC2E4\uD328 (${imgPath}): ${err instanceof Error ? err.message : String(err)}`
18394
+ });
18349
18395
  }
18350
18396
  }
18351
18397
  }
@@ -18369,7 +18415,11 @@ async function parseDocxDocument(buffer, options) {
18369
18415
  if (stylesFile) {
18370
18416
  try {
18371
18417
  styles = parseStyles(await stylesFile.async("text"));
18372
- } catch {
18418
+ } catch (err) {
18419
+ warnings.push({
18420
+ code: "PARTIAL_PARSE",
18421
+ message: `DOCX \uC2A4\uD0C0\uC77C(styles.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uAE30\uBCF8 \uC2A4\uD0C0\uC77C\uB85C \uACC4\uC18D: ${err instanceof Error ? err.message : String(err)}`
18422
+ });
18373
18423
  }
18374
18424
  }
18375
18425
  let numbering = /* @__PURE__ */ new Map();
@@ -18377,7 +18427,11 @@ async function parseDocxDocument(buffer, options) {
18377
18427
  if (numFile) {
18378
18428
  try {
18379
18429
  numbering = parseNumbering(await numFile.async("text"));
18380
- } catch {
18430
+ } catch (err) {
18431
+ warnings.push({
18432
+ code: "PARTIAL_PARSE",
18433
+ message: `DOCX \uBC88\uD638\uB9E4\uAE30\uAE30(numbering.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uBAA9\uB85D \uBC88\uD638 \uC0DD\uB7B5: ${err instanceof Error ? err.message : String(err)}`
18434
+ });
18381
18435
  }
18382
18436
  }
18383
18437
  let footnotes = /* @__PURE__ */ new Map();
@@ -18385,7 +18439,11 @@ async function parseDocxDocument(buffer, options) {
18385
18439
  if (fnFile) {
18386
18440
  try {
18387
18441
  footnotes = parseFootnotes(await fnFile.async("text"));
18388
- } catch {
18442
+ } catch (err) {
18443
+ warnings.push({
18444
+ code: "PARTIAL_PARSE",
18445
+ message: `DOCX \uAC01\uC8FC(footnotes.xml) \uD30C\uC2F1 \uC2E4\uD328 \u2014 \uAC01\uC8FC \uC0DD\uB7B5: ${err instanceof Error ? err.message : String(err)}`
18446
+ });
18389
18447
  }
18390
18448
  }
18391
18449
  const docXml = await docFile.async("text");
@@ -18407,7 +18465,7 @@ async function parseDocxDocument(buffer, options) {
18407
18465
  if (block) blocks.push(block);
18408
18466
  }
18409
18467
  }
18410
- const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc);
18468
+ const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc, warnings);
18411
18469
  const metadata = {};
18412
18470
  const coreFile = zip.file("docProps/core.xml");
18413
18471
  if (coreFile) {
@@ -18425,7 +18483,11 @@ async function parseDocxDocument(buffer, options) {
18425
18483
  if (created) metadata.createdAt = created;
18426
18484
  const modified = getFirst("dcterms:modified");
18427
18485
  if (modified) metadata.modifiedAt = modified;
18428
- } catch {
18486
+ } catch (err) {
18487
+ warnings.push({
18488
+ code: "PARTIAL_PARSE",
18489
+ message: `DOCX \uBA54\uD0C0\uB370\uC774\uD130(core.xml) \uD30C\uC2F1 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
18490
+ });
18429
18491
  }
18430
18492
  }
18431
18493
  const outline = blocks.filter((b) => b.type === "heading").map((b) => ({ level: b.level ?? 2, text: b.text ?? "" }));
@@ -18694,6 +18756,45 @@ function countSections(body) {
18694
18756
  }
18695
18757
 
18696
18758
  // src/form/match.ts
18759
+ var ValueCursor = class {
18760
+ constructor(values) {
18761
+ this.values = values;
18762
+ }
18763
+ nextIdx = /* @__PURE__ */ new Map();
18764
+ keys() {
18765
+ return this.values.keys();
18766
+ }
18767
+ has(key) {
18768
+ return this.values.has(key);
18769
+ }
18770
+ isArray(key) {
18771
+ return Array.isArray(this.values.get(key));
18772
+ }
18773
+ /** 남은 값이 있으면 true (스칼라는 항상 true) */
18774
+ available(key) {
18775
+ const v = this.values.get(key);
18776
+ if (v === void 0) return false;
18777
+ return typeof v === "string" || (this.nextIdx.get(key) ?? 0) < v.length;
18778
+ }
18779
+ /** 현재 값 미리보기 (소진 없음) */
18780
+ peek(key) {
18781
+ const v = this.values.get(key);
18782
+ if (v === void 0) return void 0;
18783
+ if (typeof v === "string") return v;
18784
+ const i = this.nextIdx.get(key) ?? 0;
18785
+ return i < v.length ? v[i] : void 0;
18786
+ }
18787
+ /** 값 소비 — 배열이면 커서 전진, 소진 시 undefined */
18788
+ consume(key) {
18789
+ const v = this.values.get(key);
18790
+ if (v === void 0) return void 0;
18791
+ if (typeof v === "string") return v;
18792
+ const i = this.nextIdx.get(key) ?? 0;
18793
+ if (i >= v.length) return void 0;
18794
+ this.nextIdx.set(key, i + 1);
18795
+ return v[i];
18796
+ }
18797
+ };
18697
18798
  function normalizeLabel(label) {
18698
18799
  return label.trim().replace(/[::\s()()·]/g, "");
18699
18800
  }
@@ -18732,9 +18833,9 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18732
18833
  (match, prefix, suffix) => {
18733
18834
  const label = prefix + suffix;
18734
18835
  const normalizedLabel = normalizeLabel(label);
18735
- const matchKey = values.has(normalizedLabel) ? normalizedLabel : values.has(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18836
+ const matchKey = values.available(normalizedLabel) ? normalizedLabel : values.available(normalizeLabel(prefix)) ? normalizeLabel(prefix) : void 0;
18736
18837
  if (matchKey === void 0) return match;
18737
- const newValue = values.get(matchKey);
18838
+ const newValue = values.consume(matchKey);
18738
18839
  matchedLabels.add(matchKey);
18739
18840
  matches.push({ key: matchKey, label, value: newValue });
18740
18841
  return `${prefix}(${newValue})${suffix}`;
@@ -18744,11 +18845,12 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18744
18845
  /□([가-힣A-Za-z]+)/g,
18745
18846
  (match, keyword) => {
18746
18847
  const normalizedKw = normalizeLabel(keyword);
18747
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18848
+ const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
18748
18849
  if (matchKey === void 0) return match;
18749
- const val = values.get(matchKey);
18850
+ const val = values.peek(matchKey);
18750
18851
  const isTruthy = ["\u2611", "\u2713", "\u2714", "v", "V", "true", "1", "yes", "o", "O"].includes(val.trim()) || val.trim() === "";
18751
18852
  if (!isTruthy) return match;
18853
+ values.consume(matchKey);
18752
18854
  matchedLabels.add(matchKey);
18753
18855
  matches.push({ key: matchKey, label: `\u25A1${keyword}`, value: "\u2611" });
18754
18856
  return `\u2611${keyword}`;
@@ -18758,9 +18860,9 @@ function fillInCellPatterns(cellText, values, matchedLabels) {
18758
18860
  /\(([가-힣A-Za-z]+)[::]\s{1,}\)/g,
18759
18861
  (match, keyword) => {
18760
18862
  const normalizedKw = normalizeLabel(keyword);
18761
- const matchKey = values.has(normalizedKw) ? normalizedKw : void 0;
18863
+ const matchKey = values.available(normalizedKw) ? normalizedKw : void 0;
18762
18864
  if (matchKey === void 0) return match;
18763
- const newValue = values.get(matchKey);
18865
+ const newValue = values.consume(matchKey);
18764
18866
  matchedLabels.add(matchKey);
18765
18867
  matches.push({ key: matchKey, label: keyword, value: newValue });
18766
18868
  return `(${keyword}\uFF1A${newValue})`;
@@ -18866,14 +18968,66 @@ var LABEL_KEYWORDS = /* @__PURE__ */ new Set([
18866
18968
  "\uCCAD\uAD6C\uC0AC\uC720",
18867
18969
  "\uC18C\uBA85\uC790\uB8CC"
18868
18970
  ]);
18971
+ var ENGLISH_LABEL_WORDS = /* @__PURE__ */ new Set([
18972
+ "name",
18973
+ "date",
18974
+ "address",
18975
+ "tel",
18976
+ "phone",
18977
+ "mobile",
18978
+ "fax",
18979
+ "email",
18980
+ "e-mail",
18981
+ "dept",
18982
+ "department",
18983
+ "division",
18984
+ "title",
18985
+ "position",
18986
+ "grade",
18987
+ "rank",
18988
+ "birth",
18989
+ "nationality",
18990
+ "sex",
18991
+ "gender",
18992
+ "signature",
18993
+ "sign",
18994
+ "seal",
18995
+ "remarks",
18996
+ "note",
18997
+ "period",
18998
+ "place",
18999
+ "purpose",
19000
+ "reason",
19001
+ "amount",
19002
+ "total",
19003
+ "sum",
19004
+ "qty",
19005
+ "quantity",
19006
+ "unit",
19007
+ "no",
19008
+ "id",
19009
+ "passport"
19010
+ ]);
19011
+ var ENGLISH_STOPWORDS = /* @__PURE__ */ new Set(["of", "the", "and", "or", "in"]);
19012
+ var NUMERIC_VALUE_RE = /^제?\d+(?:[.,]\d+)*[십백천만억조]*(?:원|명|건|개|회|부|매|장|점|호|번|년|월|일|시|분|초|개월|주년|차례|퍼센트)?$/;
19013
+ var SENTENCE_ENDING_RE = /(?:입니다|합니다|습니다|하세요|십시오|시오|바랍니다|바람|할 것|할것|하며|하고|한다|된다|됨|음|임)$/;
18869
19014
  function isLabelCell(text) {
18870
19015
  const trimmed = text.trim().replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰*※]+$/g, "").trim();
18871
19016
  if (!trimmed || trimmed.length > 30) return false;
18872
19017
  for (const kw of LABEL_KEYWORDS) {
18873
19018
  if (trimmed.includes(kw)) return true;
18874
19019
  }
18875
- if (/^[가-힣\s()()·::]+$/.test(trimmed) && trimmed.replace(/\s/g, "").length >= 2 && trimmed.replace(/\s/g, "").length <= 8 && !/\d/.test(trimmed)) return true;
19020
+ const compact = trimmed.replace(/\s/g, "");
19021
+ 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)) {
19022
+ return true;
19023
+ }
18876
19024
  if (/^[가-힣A-Za-z\s]+[::]$/.test(trimmed)) return true;
19025
+ if (/^[A-Za-z][A-Za-z\s./&-]*$/.test(trimmed) && trimmed.length <= 20) {
19026
+ const words = trimmed.toLowerCase().split(/[\s/&]+/).filter((w) => w && !ENGLISH_STOPWORDS.has(w));
19027
+ if (words.length >= 1 && words.length <= 3 && words.every((w) => ENGLISH_LABEL_WORDS.has(w.replace(/\.$/, "")))) {
19028
+ return true;
19029
+ }
19030
+ }
18877
19031
  return false;
18878
19032
  }
18879
19033
  function extractFormFields(blocks) {
@@ -19014,17 +19168,18 @@ function fillFormFields(blocks, values) {
19014
19168
  const filled = [];
19015
19169
  const matchedLabels = /* @__PURE__ */ new Set();
19016
19170
  const normalizedValues = normalizeValues(values);
19171
+ const cursor = new ValueCursor(normalizedValues);
19172
+ const allTables = collectIRTables(cloned, 0);
19017
19173
  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];
19174
+ for (const table of allTables) {
19175
+ for (let r = 0; r < table.rows; r++) {
19176
+ for (let c = 0; c < table.cols; c++) {
19177
+ const cell = table.cells[r]?.[c];
19023
19178
  if (!cell) continue;
19024
- const result = fillInCellPatterns(cell.text, normalizedValues, matchedLabels);
19179
+ const result = fillInCellPatterns(cell.text, cursor, matchedLabels);
19025
19180
  if (result) {
19026
19181
  cell.text = result.text;
19027
- patternFilledCells.add(`${r},${c}`);
19182
+ patternFilledCells.add(cell);
19028
19183
  for (const m of result.matches) {
19029
19184
  filled.push({ label: m.label, value: m.value, row: r, col: c });
19030
19185
  }
@@ -19032,33 +19187,71 @@ function fillFormFields(blocks, values) {
19032
19187
  }
19033
19188
  }
19034
19189
  }
19035
- for (const block of cloned) {
19036
- if (block.type !== "table" || !block.table) continue;
19037
- fillTable(block.table, normalizedValues, filled, matchedLabels, patternFilledCells);
19190
+ for (const table of allTables) {
19191
+ fillTable(table, cursor, filled, matchedLabels, patternFilledCells);
19038
19192
  }
19039
19193
  for (const block of cloned) {
19040
19194
  if (block.type !== "paragraph" || !block.text) continue;
19041
- const newText = fillInlineFields(block.text, normalizedValues, filled, matchedLabels);
19195
+ const newText = fillInlineFields(block.text, cursor, filled, matchedLabels);
19042
19196
  if (newText !== block.text) block.text = newText;
19043
19197
  }
19044
19198
  const unmatched = resolveUnmatched(normalizedValues, matchedLabels, values);
19045
19199
  return { blocks: cloned, filled, unmatched };
19046
19200
  }
19201
+ function collectIRTables(blocks, depth) {
19202
+ if (depth > 16) return [];
19203
+ const out = [];
19204
+ for (const block of blocks) {
19205
+ if (block.type !== "table" || !block.table) continue;
19206
+ out.push(block.table);
19207
+ for (const row of block.table.cells) {
19208
+ for (const cell of row) {
19209
+ if (cell?.blocks?.length) out.push(...collectIRTables(cell.blocks, depth + 1));
19210
+ }
19211
+ }
19212
+ }
19213
+ return out;
19214
+ }
19215
+ function coveredPositions(table) {
19216
+ const covered = /* @__PURE__ */ new Set();
19217
+ for (let r = 0; r < table.rows; r++) {
19218
+ for (let c = 0; c < table.cols; c++) {
19219
+ if (covered.has(`${r},${c}`)) continue;
19220
+ const cell = table.cells[r]?.[c];
19221
+ if (!cell) continue;
19222
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
19223
+ for (let dc = 0; dc < cell.colSpan; dc++) {
19224
+ if (dr === 0 && dc === 0) continue;
19225
+ if (r + dr < table.rows && c + dc < table.cols) covered.add(`${r + dr},${c + dc}`);
19226
+ }
19227
+ }
19228
+ c += cell.colSpan - 1;
19229
+ }
19230
+ }
19231
+ return covered;
19232
+ }
19047
19233
  function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
19048
19234
  if (table.cols < 2) return;
19235
+ const covered = coveredPositions(table);
19049
19236
  for (let r = 0; r < table.rows; r++) {
19050
- for (let c = 0; c < table.cols - 1; c++) {
19237
+ for (let c = 0; c < table.cols; c++) {
19238
+ if (covered.has(`${r},${c}`)) continue;
19051
19239
  const labelCell = table.cells[r][c];
19052
- const valueCell = table.cells[r][c + 1];
19053
- if (!labelCell || !valueCell) continue;
19240
+ if (!labelCell) continue;
19054
19241
  if (!isLabelCell(labelCell.text)) continue;
19242
+ let vc = c + labelCell.colSpan;
19243
+ while (vc < table.cols && covered.has(`${r},${vc}`)) vc++;
19244
+ if (vc >= table.cols) continue;
19245
+ const valueCell = table.cells[r][vc];
19246
+ if (!valueCell) continue;
19055
19247
  if (isKeywordLabel(valueCell.text)) continue;
19056
19248
  const normalizedCellLabel = normalizeLabel(labelCell.text);
19057
19249
  if (!normalizedCellLabel) continue;
19058
19250
  const matchKey = findMatchingKey(normalizedCellLabel, values);
19059
19251
  if (matchKey === void 0) continue;
19060
- const newValue = values.get(matchKey);
19061
- if (patternFilledCells?.has(`${r},${c + 1}`)) {
19252
+ const newValue = values.consume(matchKey);
19253
+ if (newValue === void 0) continue;
19254
+ if (patternFilledCells?.has(valueCell)) {
19062
19255
  valueCell.text = newValue + " " + valueCell.text;
19063
19256
  } else {
19064
19257
  valueCell.text = newValue;
@@ -19081,14 +19274,16 @@ function fillTable(table, values, filled, matchedLabels, patternFilledCells) {
19081
19274
  if (!allLabels) return;
19082
19275
  for (let r = 1; r < table.rows; r++) {
19083
19276
  for (let c = 0; c < table.cols; c++) {
19277
+ if (covered.has(`${r},${c}`)) continue;
19084
19278
  const headerCell = headerRow[c];
19085
19279
  const valueCell = table.cells[r]?.[c];
19086
19280
  if (!headerCell || !valueCell) continue;
19087
19281
  const headerLabel = normalizeLabel(headerCell.text);
19088
19282
  const matchKey = findMatchingKey(headerLabel, values);
19089
19283
  if (matchKey === void 0) continue;
19090
- if (matchedLabels.has(matchKey)) continue;
19091
- const newValue = values.get(matchKey);
19284
+ if (!values.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
19285
+ const newValue = values.consume(matchKey);
19286
+ if (newValue === void 0) continue;
19092
19287
  valueCell.text = newValue;
19093
19288
  matchedLabels.add(matchKey);
19094
19289
  filled.push({
@@ -19109,7 +19304,8 @@ function fillInlineFields(text, values, filled, matchedLabels) {
19109
19304
  for (const seg of segments) {
19110
19305
  const matchKey = findMatchingKey(normalizeLabel(seg.label), values);
19111
19306
  if (matchKey === void 0) continue;
19112
- const newValue = values.get(matchKey);
19307
+ const newValue = values.consume(matchKey);
19308
+ if (newValue === void 0) continue;
19113
19309
  matchedLabels.add(matchKey);
19114
19310
  filled.push({ label: seg.label.trim(), value: newValue, row: -1, col: -1 });
19115
19311
  out += text.slice(pos, seg.valueStart);
@@ -19216,6 +19412,7 @@ function scanSectionXml(xml, sectionIndex) {
19216
19412
  const paraStack = [];
19217
19413
  const tableStack = [];
19218
19414
  const rowStack = [];
19415
+ const trStartStack = [];
19219
19416
  const cellStack = [];
19220
19417
  let pendingT = null;
19221
19418
  const ctrlSubStack = [];
@@ -19280,11 +19477,17 @@ function scanSectionXml(xml, sectionIndex) {
19280
19477
  } else if (local2 === "tr") {
19281
19478
  const row = rowStack[rowStack.length - 1];
19282
19479
  const table = tableStack[tableStack.length - 1];
19283
- if (row && table && row.length > 0) table.rows.push(row);
19480
+ if (row && table && row.length > 0) {
19481
+ table.rows.push(row);
19482
+ const trStart = trStartStack[trStartStack.length - 1];
19483
+ if (trStart >= 0) table.rowRanges.push({ start: trStart, end: m.index + full.length });
19484
+ }
19284
19485
  if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19486
+ if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = -1;
19285
19487
  } else if (local2 === "tbl") {
19286
19488
  const table = tableStack.pop();
19287
19489
  rowStack.pop();
19490
+ trStartStack.pop();
19288
19491
  if (table) {
19289
19492
  finalizeTable(table);
19290
19493
  if (!table.topLevel) {
@@ -19327,6 +19530,7 @@ function scanSectionXml(xml, sectionIndex) {
19327
19530
  const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19328
19531
  if (!isNaN(ca)) cell.colAddr = ca;
19329
19532
  if (!isNaN(ra)) cell.rowAddr = ra;
19533
+ cell.addrTagRange = { start: m.index, end: m.index + full.length };
19330
19534
  }
19331
19535
  } else if (local === "cellSpan") {
19332
19536
  const cell = cellStack[cellStack.length - 1];
@@ -19377,6 +19581,7 @@ function scanSectionXml(xml, sectionIndex) {
19377
19581
  start: m.index,
19378
19582
  topLevel: false,
19379
19583
  rows: [],
19584
+ rowRanges: [],
19380
19585
  cellByAnchor: /* @__PURE__ */ new Map()
19381
19586
  };
19382
19587
  stack.pop();
@@ -19384,9 +19589,11 @@ function scanSectionXml(xml, sectionIndex) {
19384
19589
  stack.push({ local, qname, contentStart });
19385
19590
  tableStack.push(table);
19386
19591
  rowStack.push([]);
19592
+ trStartStack.push(-1);
19387
19593
  if (table.topLevel) tables.push(table);
19388
19594
  } else if (local === "tr") {
19389
19595
  if (rowStack.length > 0) rowStack[rowStack.length - 1] = [];
19596
+ if (trStartStack.length > 0) trStartStack[trStartStack.length - 1] = m.index;
19390
19597
  } else if (local === "tc") {
19391
19598
  cellStack.push({ colSpan: 1, rowSpan: 1, paragraphs: [], tables: [] });
19392
19599
  } else if (local === "cellAddr" || local === "cellSpan") {
@@ -19397,6 +19604,7 @@ function scanSectionXml(xml, sectionIndex) {
19397
19604
  const ra = parseInt(getAttr2(attrsRaw, "rowAddr") || "", 10);
19398
19605
  if (!isNaN(ca)) cell.colAddr = ca;
19399
19606
  if (!isNaN(ra)) cell.rowAddr = ra;
19607
+ cell.addrTagRange = { start: m.index, end: contentStart };
19400
19608
  } else {
19401
19609
  const cs = parseInt(getAttr2(attrsRaw, "colSpan") || "1", 10);
19402
19610
  const rs = parseInt(getAttr2(attrsRaw, "rowSpan") || "1", 10);
@@ -19789,6 +19997,7 @@ async function fillHwpx(hwpxBuffer, values) {
19789
19997
  throw new KordocError("HWPX\uC5D0\uC11C \uC139\uC158 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
19790
19998
  }
19791
19999
  const normalizedValues = normalizeValues(values);
20000
+ const cursor = new ValueCursor(normalizedValues);
19792
20001
  const matchedLabels = /* @__PURE__ */ new Set();
19793
20002
  const filled = [];
19794
20003
  const failedKeys = /* @__PURE__ */ new Set();
@@ -19824,7 +20033,7 @@ async function fillHwpx(hwpxBuffer, values) {
19824
20033
  for (const cell of row) {
19825
20034
  for (const para of cell.paragraphs) {
19826
20035
  const text = matchText(para);
19827
- const result = fillInCellPatterns(text, normalizedValues, matchedLabels);
20036
+ const result = fillInCellPatterns(text, cursor, matchedLabels);
19828
20037
  if (!result) continue;
19829
20038
  const l = led(para);
19830
20039
  if (l.fullText !== void 0) continue;
@@ -19858,21 +20067,30 @@ async function fillHwpx(hwpxBuffer, values) {
19858
20067
  if (isKeywordLabel(cellLabelText(valueCell))) continue;
19859
20068
  const normalizedCellLabel = normalizeLabel(labelText);
19860
20069
  if (!normalizedCellLabel) continue;
19861
- const matchKey = findMatchingKey(normalizedCellLabel, normalizedValues);
20070
+ const matchKey = findMatchingKey(normalizedCellLabel, cursor);
19862
20071
  if (matchKey === void 0) continue;
19863
- const newValue = normalizedValues.get(matchKey);
19864
20072
  if (patternApplied.has(valueCell)) {
19865
20073
  const target = valueCell.paragraphs.find((p) => p.tRanges.length > 0) ?? valueCell.paragraphs[0];
19866
20074
  if (!target) continue;
19867
20075
  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
- }
20076
+ if (l.fullText !== void 0) continue;
20077
+ const newValue = cursor.consume(matchKey);
20078
+ if (newValue === void 0) continue;
20079
+ l.ranges.push({ start: 0, end: 0, replacement: newValue + " " });
20080
+ l.filledIdx.push(filled.length);
20081
+ l.matchKeys.push(matchKey);
20082
+ matchedLabels.add(matchKey);
20083
+ filled.push({
20084
+ label: labelText.trim().replace(/[::]\s*$/, ""),
20085
+ value: newValue,
20086
+ row: rowIdx,
20087
+ col: colIdx
20088
+ });
19873
20089
  } else {
19874
20090
  const paras = valueCell.paragraphs;
19875
20091
  if (paras.length === 0) continue;
20092
+ const newValue = cursor.consume(matchKey);
20093
+ if (newValue === void 0) continue;
19876
20094
  const l0 = led(paras[0]);
19877
20095
  l0.fullText = newValue;
19878
20096
  l0.ranges = [];
@@ -19883,14 +20101,14 @@ async function fillHwpx(hwpxBuffer, values) {
19883
20101
  lk.fullText = "";
19884
20102
  lk.ranges = [];
19885
20103
  }
20104
+ matchedLabels.add(matchKey);
20105
+ filled.push({
20106
+ label: labelText.trim().replace(/[::]\s*$/, ""),
20107
+ value: newValue,
20108
+ row: rowIdx,
20109
+ col: colIdx
20110
+ });
19886
20111
  }
19887
- matchedLabels.add(matchKey);
19888
- filled.push({
19889
- label: labelText.trim().replace(/[::]\s*$/, ""),
19890
- value: newValue,
19891
- row: rowIdx,
19892
- col: colIdx
19893
- });
19894
20112
  }
19895
20113
  }
19896
20114
  if (table.rows.length >= 2) {
@@ -19904,10 +20122,11 @@ async function fillHwpx(hwpxBuffer, values) {
19904
20122
  const dataCells = table.rows[rowIdx];
19905
20123
  for (let colIdx = 0; colIdx < Math.min(headerCells.length, dataCells.length); colIdx++) {
19906
20124
  const headerLabel = normalizeLabel(cellLabelText(headerCells[colIdx]));
19907
- const matchKey = findMatchingKey(headerLabel, normalizedValues);
20125
+ const matchKey = findMatchingKey(headerLabel, cursor);
19908
20126
  if (matchKey === void 0) continue;
19909
- if (matchedLabels.has(matchKey)) continue;
19910
- const newValue = normalizedValues.get(matchKey);
20127
+ if (!cursor.isArray(matchKey) && matchedLabels.has(matchKey)) continue;
20128
+ const newValue = cursor.consume(matchKey);
20129
+ if (newValue === void 0) continue;
19911
20130
  const paras = dataCells[colIdx].paragraphs;
19912
20131
  if (paras.length === 0) continue;
19913
20132
  const l0 = led(paras[0]);
@@ -19937,9 +20156,10 @@ async function fillHwpx(hwpxBuffer, values) {
19937
20156
  if (existing?.fullText !== void 0) continue;
19938
20157
  const text = matchText(para);
19939
20158
  for (const seg of scanInlineSegments(text)) {
19940
- const matchKey = findMatchingKey(normalizeLabel(seg.label), normalizedValues);
20159
+ const matchKey = findMatchingKey(normalizeLabel(seg.label), cursor);
19941
20160
  if (matchKey === void 0) continue;
19942
- const newValue = normalizedValues.get(matchKey);
20161
+ const newValue = cursor.consume(matchKey);
20162
+ if (newValue === void 0) continue;
19943
20163
  const replacement = seg.valueStart === seg.valueEnd ? padInsertion(text, seg.valueStart, newValue) : newValue;
19944
20164
  const l = led(para);
19945
20165
  l.ranges.push({ start: seg.valueStart, end: seg.valueEnd, replacement });
@@ -19994,6 +20214,7 @@ async function fillHwpx(hwpxBuffer, values) {
19994
20214
  splices.push(...paraSplices);
19995
20215
  }
19996
20216
  if (splices.length > 0) {
20217
+ splices.push(...allLinesegRemovalSplices(xml));
19997
20218
  replacements.set(sectionPaths[si], encoder.encode(applySplices(xml, splices)));
19998
20219
  }
19999
20220
  }
@@ -20013,6 +20234,245 @@ async function fillHwpx(hwpxBuffer, values) {
20013
20234
  // src/hwpx/generator.ts
20014
20235
  import JSZip6 from "jszip";
20015
20236
 
20237
+ // src/hwpx/text-metrics.ts
20238
+ var ASCII_W = [
20239
+ 300,
20240
+ 320,
20241
+ 320,
20242
+ 610,
20243
+ 610,
20244
+ 830,
20245
+ 724,
20246
+ 320,
20247
+ 320,
20248
+ 320,
20249
+ 550,
20250
+ 550,
20251
+ 320,
20252
+ 550,
20253
+ 320,
20254
+ 550,
20255
+ // 0x20-0x2F
20256
+ 550,
20257
+ 550,
20258
+ 550,
20259
+ 550,
20260
+ 550,
20261
+ 550,
20262
+ 550,
20263
+ 550,
20264
+ 550,
20265
+ 550,
20266
+ 320,
20267
+ 320,
20268
+ 550,
20269
+ 550,
20270
+ 550,
20271
+ 550,
20272
+ // 0x30-0x3F
20273
+ 830,
20274
+ 706,
20275
+ 605,
20276
+ 685,
20277
+ 719,
20278
+ 627,
20279
+ 617,
20280
+ 683,
20281
+ 734,
20282
+ 305,
20283
+ 315,
20284
+ 660,
20285
+ 605,
20286
+ 839,
20287
+ 734,
20288
+ 732,
20289
+ // 0x40-0x4F
20290
+ 603,
20291
+ 705,
20292
+ 660,
20293
+ 627,
20294
+ 664,
20295
+ 731,
20296
+ 706,
20297
+ 910,
20298
+ 705,
20299
+ 705,
20300
+ 626,
20301
+ 320,
20302
+ 550,
20303
+ 320,
20304
+ 550,
20305
+ 550,
20306
+ // 0x50-0x5F
20307
+ 320,
20308
+ 569,
20309
+ 597,
20310
+ 552,
20311
+ 597,
20312
+ 536,
20313
+ 356,
20314
+ 562,
20315
+ 635,
20316
+ 287,
20317
+ 288,
20318
+ 582,
20319
+ 287,
20320
+ 907,
20321
+ 635,
20322
+ 588,
20323
+ // 0x60-0x6F
20324
+ 597,
20325
+ 579,
20326
+ 478,
20327
+ 496,
20328
+ 356,
20329
+ 635,
20330
+ 563,
20331
+ 720,
20332
+ 542,
20333
+ 543,
20334
+ 486,
20335
+ 320,
20336
+ 320,
20337
+ 320,
20338
+ 550,
20339
+ 0
20340
+ // 0x70-0x7E(+DEL)
20341
+ ];
20342
+ var SYM_W = {
20343
+ 160: 300,
20344
+ 163: 568,
20345
+ 165: 707,
20346
+ 167: 498,
20347
+ 171: 440,
20348
+ 172: 564,
20349
+ 176: 291,
20350
+ 177: 798,
20351
+ 182: 606,
20352
+ 183: 320,
20353
+ 187: 440,
20354
+ 215: 617,
20355
+ 247: 678,
20356
+ 8211: 625,
20357
+ 8212: 875,
20358
+ 8213: 875,
20359
+ 8216: 320,
20360
+ 8217: 320,
20361
+ 8220: 480,
20362
+ 8221: 480,
20363
+ 8224: 558,
20364
+ 8225: 438,
20365
+ 8229: 640,
20366
+ 8230: 960,
20367
+ 8240: 988,
20368
+ 8242: 335,
20369
+ 8243: 474,
20370
+ 8251: 770,
20371
+ 8364: 656,
20372
+ 9756: 1012,
20373
+ 9758: 1012
20374
+ };
20375
+ function charWidthEm1000(cp) {
20376
+ if (cp >= 32 && cp <= 126) return ASCII_W[cp - 32];
20377
+ const sym = SYM_W[cp];
20378
+ if (sym !== void 0) return sym;
20379
+ if (cp >= 44032 && cp <= 55203) return 970;
20380
+ if (cp >= 4352 && cp <= 4607) return 970;
20381
+ if (cp >= 12593 && cp <= 12686) return 970;
20382
+ if (cp >= 19968 && cp <= 40959 || cp >= 63744 && cp <= 64255) return 1e3;
20383
+ if (cp >= 12296 && cp <= 12305 || cp >= 12308 && cp <= 12315) return 500;
20384
+ if (cp === 12288) return 970;
20385
+ if (cp >= 8592 && cp <= 8959) return 970;
20386
+ if (cp >= 9312 && cp <= 9471) return 970;
20387
+ if (cp >= 9632 && cp <= 9983) return 970;
20388
+ if (cp >= 12800 && cp <= 13311) return 970;
20389
+ if (cp >= 65281 && cp <= 65376) return 970;
20390
+ return cp >= 11904 ? 970 : 550;
20391
+ }
20392
+ var SPACE_EM_FIXED = 500;
20393
+ var SPACE_EM_FONT = 300;
20394
+ function measureTextWidth(text, height, ratioPct, opts) {
20395
+ const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
20396
+ const spacing = opts?.spacingPct ?? 0;
20397
+ let em = 0;
20398
+ for (const ch of text) {
20399
+ const cp = ch.codePointAt(0);
20400
+ const w = cp === 32 ? spaceEm : charWidthEm1000(cp);
20401
+ em += w * (1 + spacing / 100);
20402
+ }
20403
+ return em / 1e3 * height * (ratioPct / 100);
20404
+ }
20405
+ var FORBID_START = /* @__PURE__ */ new Set([..."!%),.:;?]}\xA2\xB0\u2032\u2033\u2103\u3009\u300B\u300D\u300F\u3011\u3015!%),.:;?]}\u20A9~\u2026\xB7\u3001\u3002\u3003"]);
20406
+ var FORBID_END = /* @__PURE__ */ new Set([..."$([{\xA3\xA5\u3008\u300A\u300C\u300E\u3010\u3014$([{\u20A9"]);
20407
+ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "keep", opts) {
20408
+ const EPS = 0.5;
20409
+ const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
20410
+ const spacing = opts?.spacingPct ?? 0;
20411
+ const k = height * ratioPct / 100 / 1e3;
20412
+ const cwCp = (cp) => (cp === 32 ? spaceEm : charWidthEm1000(cp)) * (1 + spacing / 100) * k;
20413
+ const charW = (ch) => cwCp(ch.codePointAt(0));
20414
+ const rangeW = (from, to) => {
20415
+ let w = 0;
20416
+ for (const ch of text.slice(from, to)) w += charW(ch);
20417
+ return w;
20418
+ };
20419
+ const units = text.match(mode === "keep" ? / +|[^ ]+/g : / +|[^ ]/g) ?? [];
20420
+ const starts = [0];
20421
+ let lineW = 0;
20422
+ let avail = firstWidth;
20423
+ let pos = 0;
20424
+ const lineStart = () => starts[starts.length - 1];
20425
+ const breakBefore = (unitPos, w) => {
20426
+ let bp = unitPos;
20427
+ const u = text[unitPos];
20428
+ if (u !== void 0 && FORBID_START.has(u) && bp - 1 > lineStart() && text[bp - 1] !== " ") bp--;
20429
+ while (bp - 1 > lineStart() && FORBID_END.has(text[bp - 1])) bp--;
20430
+ if (bp <= lineStart()) bp = unitPos;
20431
+ starts.push(bp);
20432
+ avail = contWidth;
20433
+ lineW = rangeW(bp, unitPos) + w;
20434
+ };
20435
+ for (const u of units) {
20436
+ if (u[0] === " ") {
20437
+ lineW += charW(" ") * u.length;
20438
+ pos += u.length;
20439
+ continue;
20440
+ }
20441
+ const w = rangeW(pos, pos + u.length);
20442
+ if (lineW + w <= avail + EPS) {
20443
+ lineW += w;
20444
+ pos += u.length;
20445
+ continue;
20446
+ }
20447
+ if (lineW === 0 || w > contWidth + EPS) {
20448
+ let sub = 0;
20449
+ for (const ch of u) {
20450
+ const c = charW(ch);
20451
+ if (lineW + c > avail + EPS && lineW > 0) breakBefore(pos + sub, 0);
20452
+ lineW += c;
20453
+ sub += ch.length;
20454
+ }
20455
+ pos += u.length;
20456
+ continue;
20457
+ }
20458
+ breakBefore(pos, w);
20459
+ pos += u.length;
20460
+ }
20461
+ return { lines: starts.length, starts, lastLineWidth: lineW };
20462
+ }
20463
+ function simulateWrapKeepWord(text, firstWidth, contWidth, height, ratioPct, opts) {
20464
+ return simulateWrap(text, firstWidth, contWidth, height, ratioPct, "keep", opts);
20465
+ }
20466
+ function fitRatioForFewerLines(text, firstWidth, contWidth, height, baseRatio, minRatio, opts) {
20467
+ const base = simulateWrap(text, firstWidth, contWidth, height, baseRatio, "keep", opts);
20468
+ if (base.lines < 2) return null;
20469
+ for (let r = baseRatio - 1; r >= minRatio; r--) {
20470
+ const sim = simulateWrap(text, firstWidth, contWidth, height, r, "keep", opts);
20471
+ if (sim.lines < base.lines) return r;
20472
+ }
20473
+ return null;
20474
+ }
20475
+
20016
20476
  // src/hwpx/gongmun.ts
20017
20477
  var OFFICIAL_MARGINS = { top: 20, bottom: 10, left: 20, right: 20 };
20018
20478
  var PRESET_DEFAULTS = {
@@ -20048,6 +20508,7 @@ function resolveGongmun(opts) {
20048
20508
  const preset = normalizeGongmunPreset(opts.preset);
20049
20509
  const d = PRESET_DEFAULTS[preset];
20050
20510
  const bodyPt = opts.bodyPt ?? d.bodyPt;
20511
+ const autoFitMinRatio = opts.autoFit === false ? null : typeof opts.autoFit === "object" ? Math.min(Math.max(opts.autoFit.minRatio ?? 90, 50), 99) : 90;
20051
20512
  return {
20052
20513
  preset,
20053
20514
  bodyFont: opts.bodyFont ?? "myeongjo",
@@ -20055,7 +20516,8 @@ function resolveGongmun(opts) {
20055
20516
  lineSpacing: opts.lineSpacing ?? d.lineSpacing,
20056
20517
  numbering: opts.numbering ?? d.numbering,
20057
20518
  margins: opts.margins ?? OFFICIAL_MARGINS,
20058
- centerTitle: opts.centerTitle ?? true
20519
+ centerTitle: opts.centerTitle ?? true,
20520
+ autoFitMinRatio
20059
20521
  };
20060
20522
  }
20061
20523
  var HANGUL_INITIALS = [0, 2, 3, 5, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18];
@@ -20098,17 +20560,10 @@ function standardMarker(depth, n) {
20098
20560
  function reportMarker(depth) {
20099
20561
  return REPORT_BULLETS[Math.min(depth, REPORT_BULLETS.length - 1)];
20100
20562
  }
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
20563
  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);
20564
+ let em = SPACE_EM_FIXED;
20565
+ for (const c of marker) em += charWidthEm1000(c.codePointAt(0));
20566
+ return Math.round(em / 1e3 * bodyHeight);
20112
20567
  }
20113
20568
  function levelIndent(depth, bodyHeight, numbering) {
20114
20569
  const marker = numbering === "report" ? reportMarker(depth) : standardMarker(depth, 0);
@@ -20148,77 +20603,53 @@ function mmToHwpunit(mm) {
20148
20603
  return Math.round(mm * 7200 / 25.4);
20149
20604
  }
20150
20605
 
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
- };
20606
+ // src/diff/text-diff.ts
20607
+ function similarity(a, b) {
20608
+ if (a === b) return 1;
20609
+ if (!a || !b) return 0;
20610
+ const maxLen = Math.max(a.length, b.length);
20611
+ if (maxLen === 0) return 1;
20612
+ return 1 - levenshtein(a, b) / maxLen;
20192
20613
  }
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" });
20614
+ function normalizedSimilarity(a, b) {
20615
+ return similarity(normalize(a), normalize(b));
20206
20616
  }
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;
20617
+ function normalize(s) {
20618
+ return s.replace(/\s+/g, " ").trim();
20619
+ }
20620
+ var MAX_LEVENSHTEIN_LEN = 1e4;
20621
+ function levenshtein(a, b) {
20622
+ if (a.length + b.length > MAX_LEVENSHTEIN_LEN) {
20623
+ const sampleLen = Math.min(500, a.length, b.length);
20624
+ let diffs = 0;
20625
+ for (let i = 0; i < sampleLen; i++) if (a[i] !== b[i]) diffs++;
20626
+ const sampleRate = sampleLen > 0 ? diffs / sampleLen : 1;
20627
+ return Math.abs(a.length - b.length) + Math.round(Math.min(a.length, b.length) * sampleRate);
20216
20628
  }
20217
- return lines.join("\n").slice(0, 1024);
20629
+ if (a.length > b.length) [a, b] = [b, a];
20630
+ const m = a.length;
20631
+ const n = b.length;
20632
+ let prev = Array.from({ length: m + 1 }, (_, i) => i);
20633
+ let curr = new Array(m + 1);
20634
+ for (let j = 1; j <= n; j++) {
20635
+ curr[0] = j;
20636
+ for (let i = 1; i <= m; i++) {
20637
+ if (a[i - 1] === b[j - 1]) {
20638
+ curr[i] = prev[i - 1];
20639
+ } else {
20640
+ curr[i] = 1 + Math.min(prev[i - 1], prev[i], curr[i - 1]);
20641
+ }
20642
+ }
20643
+ ;
20644
+ [prev, curr] = [curr, prev];
20645
+ }
20646
+ return prev[m];
20218
20647
  }
20219
- function parseMarkdownToBlocks(md2) {
20648
+
20649
+ // src/roundtrip/markdown-units.ts
20650
+ function splitMarkdownUnits(md2) {
20220
20651
  const lines = md2.split("\n");
20221
- const blocks = [];
20652
+ const units = [];
20222
20653
  let i = 0;
20223
20654
  while (i < lines.length) {
20224
20655
  const line = lines[i];
@@ -20226,21 +20657,464 @@ function parseMarkdownToBlocks(md2) {
20226
20657
  i++;
20227
20658
  continue;
20228
20659
  }
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]);
20660
+ if (line.trim().startsWith("<table>")) {
20661
+ const collected2 = [];
20662
+ let depth = 0;
20663
+ while (i < lines.length) {
20664
+ const l = lines[i];
20665
+ collected2.push(l);
20666
+ depth += (l.match(/<table>/g) || []).length;
20667
+ depth -= (l.match(/<\/table>/g) || []).length;
20237
20668
  i++;
20669
+ if (depth <= 0) break;
20238
20670
  }
20239
- if (i < lines.length) i++;
20240
- blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
20671
+ units.push({ kind: "html-table", raw: collected2.join("\n"), lines: collected2 });
20241
20672
  continue;
20242
20673
  }
20243
- if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20674
+ if (line.trimStart().startsWith("|")) {
20675
+ const collected2 = [];
20676
+ while (i < lines.length && lines[i].trimStart().startsWith("|")) {
20677
+ collected2.push(lines[i]);
20678
+ i++;
20679
+ }
20680
+ units.push({ kind: "gfm-table", raw: collected2.join("\n"), lines: collected2 });
20681
+ continue;
20682
+ }
20683
+ if (/^-{3,}\s*$/.test(line.trim())) {
20684
+ units.push({ kind: "separator", raw: line.trim(), lines: [line.trim()] });
20685
+ i++;
20686
+ continue;
20687
+ }
20688
+ if (/^!\[image\]\([^)]*\)\s*$/.test(line.trim())) {
20689
+ units.push({ kind: "image", raw: line.trim(), lines: [line.trim()] });
20690
+ i++;
20691
+ continue;
20692
+ }
20693
+ const collected = [];
20694
+ while (i < lines.length && lines[i].trim() && !lines[i].trimStart().startsWith("|") && !lines[i].trim().startsWith("<table>")) {
20695
+ collected.push(lines[i].trim());
20696
+ i++;
20697
+ }
20698
+ units.push({ kind: "text", raw: collected.join("\n"), lines: collected });
20699
+ }
20700
+ return units;
20701
+ }
20702
+ function alignUnits(a, b) {
20703
+ const m = a.length, n = b.length;
20704
+ if (m * n > 4e6) {
20705
+ const result2 = [];
20706
+ let pre = 0;
20707
+ while (pre < m && pre < n && a[pre] === b[pre]) {
20708
+ result2.push([pre, pre]);
20709
+ pre++;
20710
+ }
20711
+ let suf = 0;
20712
+ while (suf < m - pre && suf < n - pre && a[m - 1 - suf] === b[n - 1 - suf]) suf++;
20713
+ const aMid = m - pre - suf, bMid = n - pre - suf;
20714
+ if (aMid === bMid) {
20715
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, pre + i2]);
20716
+ } else {
20717
+ for (let i2 = 0; i2 < aMid; i2++) result2.push([pre + i2, null]);
20718
+ for (let j2 = 0; j2 < bMid; j2++) result2.push([null, pre + j2]);
20719
+ }
20720
+ for (let s = suf - 1; s >= 0; s--) result2.push([m - 1 - s, n - 1 - s]);
20721
+ return result2;
20722
+ }
20723
+ const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
20724
+ for (let i2 = 1; i2 <= m; i2++) {
20725
+ for (let j2 = 1; j2 <= n; j2++) {
20726
+ 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]);
20727
+ }
20728
+ }
20729
+ const matches = [];
20730
+ let i = m, j = n;
20731
+ while (i > 0 && j > 0) {
20732
+ if (a[i - 1] === b[j - 1] && dp[i][j] === dp[i - 1][j - 1] + 1) {
20733
+ matches.push([i - 1, j - 1]);
20734
+ i--;
20735
+ j--;
20736
+ } else if (dp[i - 1][j] >= dp[i][j - 1]) i--;
20737
+ else j--;
20738
+ }
20739
+ matches.reverse();
20740
+ const result = [];
20741
+ let ai = 0, bi = 0;
20742
+ const flushGap = (aEnd, bEnd) => {
20743
+ if (aEnd - ai === bEnd - bi) {
20744
+ while (ai < aEnd) result.push([ai++, bi++]);
20745
+ return;
20746
+ }
20747
+ while (ai < aEnd && bi < bEnd) {
20748
+ const sim = normalizedSimilarity(a[ai], b[bi]);
20749
+ if (sim >= 0.4) {
20750
+ if (aEnd - ai > bEnd - bi && bestSimInRange(a, ai + 1, ai + (aEnd - ai) - (bEnd - bi), b[bi]) > sim) {
20751
+ result.push([ai++, null]);
20752
+ } else if (bEnd - bi > aEnd - ai && bestSimInRange(b, bi + 1, bi + (bEnd - bi) - (aEnd - ai), a[ai]) > sim) {
20753
+ result.push([null, bi++]);
20754
+ } else {
20755
+ result.push([ai++, bi++]);
20756
+ }
20757
+ } else if (aEnd - ai >= bEnd - bi) result.push([ai++, null]);
20758
+ else result.push([null, bi++]);
20759
+ }
20760
+ while (ai < aEnd) result.push([ai++, null]);
20761
+ while (bi < bEnd) result.push([null, bi++]);
20762
+ };
20763
+ for (const [pi, pj] of matches) {
20764
+ flushGap(pi, pj);
20765
+ result.push([ai++, bi++]);
20766
+ }
20767
+ flushGap(m, n);
20768
+ return result;
20769
+ }
20770
+ function bestSimInRange(arr, from, to, target) {
20771
+ let best = 0;
20772
+ for (let k = from; k <= to && k < arr.length; k++) {
20773
+ const s = normalizedSimilarity(arr[k], target);
20774
+ if (s > best) best = s;
20775
+ }
20776
+ return best;
20777
+ }
20778
+ function escapeGfm(text) {
20779
+ return text.replace(/~/g, "\\~");
20780
+ }
20781
+ var HWP_SHAPE_ALT_TEXT_RE = /(?:모서리가 둥근 |둥근 )?(?:사각형|직사각형|정사각형|원|타원|삼각형|이등변 삼각형|직각 삼각형|선|직선|곡선|화살표|굵은 화살표|이중 화살표|오각형|육각형|팔각형|별|[4-8]점별|십자|십자형|구름|구름형|마름모|도넛|평행사변형|사다리꼴|부채꼴|호|반원|물결|번개|하트|빗금|블록 화살표|수식|표|그림|개체|그리기\s?개체|묶음\s?개체|글상자|수식\s?개체|OLE\s?개체)\s?입니다\.?/g;
20782
+ function sanitizeText(text) {
20783
+ let result = mapPuaText(text).replace(/[\u{F0000}-\u{FFFFD}]/gu, "").replace(HWP_SHAPE_ALT_TEXT_RE, "").replace(/ +/g, " ").trim();
20784
+ if (result.length <= 30 && result.includes(" ")) {
20785
+ const tokens = result.split(" ");
20786
+ const koreanSingleCharCount = tokens.filter((t) => t.length === 1 && /[가-힯ㄱ-ㆎ]/.test(t)).length;
20787
+ if (tokens.length >= 3 && koreanSingleCharCount / tokens.length >= 0.7) {
20788
+ result = tokens.join("");
20789
+ }
20790
+ }
20791
+ return result;
20792
+ }
20793
+ function normForMatch(text) {
20794
+ return sanitizeText(text).replace(/\s+/g, " ").trim();
20795
+ }
20796
+ function unescapeGfm(text) {
20797
+ return text.replace(/\\~/g, "~");
20798
+ }
20799
+ function summarize(text) {
20800
+ const t = text.replace(/\s+/g, " ").trim();
20801
+ return t.length > 80 ? t.slice(0, 77) + "..." : t;
20802
+ }
20803
+ function replicateGfmTable(table) {
20804
+ const { cells, rows: numRows, cols: numCols } = table;
20805
+ if (numRows === 0 || numCols === 0) return null;
20806
+ if (numRows === 1 && numCols === 1) return null;
20807
+ if (numCols === 1) return null;
20808
+ const display = Array.from({ length: numRows }, (_, r) => Array.from({ length: numCols }, (_2, c) => ({ text: "", gridR: r, gridC: c })));
20809
+ const skip = /* @__PURE__ */ new Set();
20810
+ for (let r = 0; r < numRows; r++) {
20811
+ for (let c = 0; c < numCols; c++) {
20812
+ if (skip.has(`${r},${c}`)) continue;
20813
+ const cell = cells[r]?.[c];
20814
+ if (!cell) continue;
20815
+ display[r][c] = {
20816
+ text: escapeGfm(sanitizeText(cell.text)).replace(/\|/g, "\\|").replace(/\n/g, "<br>"),
20817
+ gridR: r,
20818
+ gridC: c
20819
+ };
20820
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
20821
+ for (let dc = 0; dc < cell.colSpan; dc++) {
20822
+ if (dr === 0 && dc === 0) continue;
20823
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20824
+ }
20825
+ }
20826
+ c += cell.colSpan - 1;
20827
+ }
20828
+ }
20829
+ const uniqueRows = [];
20830
+ let pendingLabelRow = null;
20831
+ for (let r = 0; r < display.length; r++) {
20832
+ const row = display[r];
20833
+ if (row.every((cell) => cell.text === "")) continue;
20834
+ const nonEmptyCols = row.filter((cell) => cell.text !== "");
20835
+ const hasSkipInRow = row.some((_, c) => skip.has(`${r},${c}`));
20836
+ if (!hasSkipInRow && nonEmptyCols.length === 1 && row[0].text !== "" && row.slice(1).every((c) => c.text === "")) {
20837
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20838
+ pendingLabelRow = row;
20839
+ continue;
20840
+ }
20841
+ if (pendingLabelRow) {
20842
+ if (row[0].text === "") row[0] = pendingLabelRow[0];
20843
+ else uniqueRows.push(pendingLabelRow);
20844
+ pendingLabelRow = null;
20845
+ }
20846
+ uniqueRows.push(row);
20847
+ }
20848
+ if (pendingLabelRow) uniqueRows.push(pendingLabelRow);
20849
+ return uniqueRows.length > 0 ? uniqueRows : null;
20850
+ }
20851
+ function parseGfmTable(lines) {
20852
+ const rows = [];
20853
+ for (const line of lines) {
20854
+ const trimmed = line.trim();
20855
+ if (!trimmed.startsWith("|")) continue;
20856
+ const cells = trimmed.split(/(?<!\\)\|/).slice(1, -1).map((c) => c.trim());
20857
+ if (cells.length === 0) continue;
20858
+ if (cells.every((c) => /^:?-{3,}:?$/.test(c))) continue;
20859
+ rows.push(cells);
20860
+ }
20861
+ return rows;
20862
+ }
20863
+ function unescapeGfmCell(text) {
20864
+ return text.replace(/<br\s*\/?>/gi, "\n").replace(/\\\|/g, "|").replace(/\\~/g, "~");
20865
+ }
20866
+ function replicateCellInnerHtml(cell) {
20867
+ if (cell.blocks?.length) {
20868
+ return cell.blocks.map((b) => {
20869
+ if (b.type === "table" && b.table) {
20870
+ const cap = b.table.caption ? sanitizeText(b.table.caption) : "";
20871
+ return (cap ? cap + "<br>" : "") + replicateTableToHtml(b.table);
20872
+ }
20873
+ if (b.type === "image" && b.text) return `<img src="${b.text}" alt="image">`;
20874
+ const t = sanitizeText(b.text ?? "");
20875
+ return t ? t.replace(/\n/g, "<br>") : "";
20876
+ }).filter(Boolean).join("<br>");
20877
+ }
20878
+ return sanitizeText(cell.text).replace(/\n/g, "<br>");
20879
+ }
20880
+ function replicateTableToHtml(table) {
20881
+ const rows = replicateHtmlTable(table);
20882
+ const lines = ["<table>"];
20883
+ for (let r = 0; r < rows.length; r++) {
20884
+ const tag = rows[r].tag;
20885
+ const rowHtml = rows[r].cells.map((cell) => {
20886
+ const attrs = [];
20887
+ if (cell.colSpan > 1) attrs.push(`colspan="${cell.colSpan}"`);
20888
+ if (cell.rowSpan > 1) attrs.push(`rowspan="${cell.rowSpan}"`);
20889
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
20890
+ return `<${tag}${attrStr}>${cell.inner}</${tag}>`;
20891
+ });
20892
+ if (rowHtml.length) lines.push(`<tr>${rowHtml.join("")}</tr>`);
20893
+ }
20894
+ lines.push("</table>");
20895
+ return lines.join("\n");
20896
+ }
20897
+ function replicateHtmlTable(table) {
20898
+ const { cells, rows: numRows, cols: numCols } = table;
20899
+ const skip = /* @__PURE__ */ new Set();
20900
+ const result = [];
20901
+ for (let r = 0; r < numRows; r++) {
20902
+ const tag = r === 0 ? "th" : "td";
20903
+ const rowCells = [];
20904
+ for (let c = 0; c < numCols; c++) {
20905
+ if (skip.has(`${r},${c}`)) continue;
20906
+ const cell = cells[r]?.[c];
20907
+ if (!cell) continue;
20908
+ for (let dr = 0; dr < cell.rowSpan; dr++) {
20909
+ for (let dc = 0; dc < cell.colSpan; dc++) {
20910
+ if (dr === 0 && dc === 0) continue;
20911
+ if (r + dr < numRows && c + dc < numCols) skip.add(`${r + dr},${c + dc}`);
20912
+ }
20913
+ }
20914
+ rowCells.push({
20915
+ inner: replicateCellInnerHtml(cell),
20916
+ colSpan: cell.colSpan,
20917
+ rowSpan: cell.rowSpan,
20918
+ gridR: r,
20919
+ gridC: c
20920
+ });
20921
+ }
20922
+ if (rowCells.length) result.push({ tag, cells: rowCells });
20923
+ }
20924
+ return result;
20925
+ }
20926
+ function parseHtmlTable(raw) {
20927
+ const re = /<(\/?)(table|tr|td|th)((?:"[^"]*"|'[^']*'|[^>"'])*?)>/gi;
20928
+ let depth = 0;
20929
+ let currentRow = null;
20930
+ let cellStart = -1;
20931
+ let cellInfo = null;
20932
+ const rows = [];
20933
+ let m;
20934
+ while ((m = re.exec(raw)) !== null) {
20935
+ const isClose = m[1] === "/";
20936
+ const tag = m[2].toLowerCase();
20937
+ const attrs = m[3] || "";
20938
+ if (tag === "table") {
20939
+ depth += isClose ? -1 : 1;
20940
+ if (depth < 0) return null;
20941
+ continue;
20942
+ }
20943
+ if (depth !== 1) continue;
20944
+ if (tag === "tr") {
20945
+ if (!isClose) currentRow = [];
20946
+ else if (currentRow) {
20947
+ rows.push({ tag: rows.length === 0 ? "th" : "td", cells: currentRow });
20948
+ currentRow = null;
20949
+ }
20950
+ } else {
20951
+ if (!isClose) {
20952
+ const cs = parseInt(attrs.match(/colspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20953
+ const rs = parseInt(attrs.match(/rowspan\s*=\s*"(\d+)"/i)?.[1] || "1", 10);
20954
+ cellStart = m.index + m[0].length;
20955
+ cellInfo = { colSpan: isNaN(cs) ? 1 : cs, rowSpan: isNaN(rs) ? 1 : rs };
20956
+ } else if (cellStart >= 0 && cellInfo && currentRow) {
20957
+ currentRow.push({ inner: raw.slice(cellStart, m.index), colSpan: cellInfo.colSpan, rowSpan: cellInfo.rowSpan });
20958
+ cellStart = -1;
20959
+ cellInfo = null;
20960
+ }
20961
+ }
20962
+ }
20963
+ if (depth !== 0) return null;
20964
+ return rows;
20965
+ }
20966
+ var AUTONUM_PREFIX_RE = /^(?:[0-90-9a-zA-Z가-힣]{1,6}[.)\]:]|[([][0-90-9a-zA-Z가-힣]{1,6}[)\]][.:]?|[ⅰ-ⅹⅠ-Ⅹ①-⑮][.)\]:]?)$/u;
20967
+ function htmlCellInnerToLines(inner) {
20968
+ let hadNonText = false;
20969
+ let work = inner;
20970
+ if (/<table[\s>]/i.test(work)) {
20971
+ hadNonText = true;
20972
+ work = removeNestedTables(work);
20973
+ }
20974
+ if (/<img\s/i.test(work)) {
20975
+ hadNonText = true;
20976
+ work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
20977
+ }
20978
+ const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
20979
+ return { lines, hadNonText };
20980
+ }
20981
+ function extractTopLevelTables(html) {
20982
+ const result = [];
20983
+ let depth = 0;
20984
+ let start = -1;
20985
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
20986
+ let m;
20987
+ while ((m = re.exec(html)) !== null) {
20988
+ if (m[1] !== "/") {
20989
+ if (depth === 0) start = m.index;
20990
+ depth++;
20991
+ } else {
20992
+ depth--;
20993
+ if (depth === 0 && start >= 0) {
20994
+ result.push(html.slice(start, m.index + m[0].length));
20995
+ start = -1;
20996
+ }
20997
+ if (depth < 0) depth = 0;
20998
+ }
20999
+ }
21000
+ return result;
21001
+ }
21002
+ function removeNestedTables(html) {
21003
+ let result = "";
21004
+ let depth = 0;
21005
+ const re = /<(\/?)table(?:[\s>]|>)/gi;
21006
+ let last = 0;
21007
+ let m;
21008
+ while ((m = re.exec(html)) !== null) {
21009
+ if (m[1] !== "/") {
21010
+ if (depth === 0) result += html.slice(last, m.index);
21011
+ depth++;
21012
+ } else {
21013
+ depth--;
21014
+ if (depth === 0) last = m.index + m[0].length;
21015
+ if (depth < 0) depth = 0;
21016
+ }
21017
+ }
21018
+ if (depth === 0) result += html.slice(last);
21019
+ return result;
21020
+ }
21021
+
21022
+ // src/hwpx/generator.ts
21023
+ var NS_SECTION = "http://www.hancom.co.kr/hwpml/2011/section";
21024
+ var NS_PARA = "http://www.hancom.co.kr/hwpml/2011/paragraph";
21025
+ var NS_HEAD = "http://www.hancom.co.kr/hwpml/2011/head";
21026
+ var NS_CORE = "http://www.hancom.co.kr/hwpml/2011/core";
21027
+ var NS_OPF = "http://www.idpf.org/2007/opf/";
21028
+ var NS_HPF = "http://www.hancom.co.kr/schema/2011/hpf";
21029
+ var NS_OCF = "urn:oasis:names:tc:opendocument:xmlns:container";
21030
+ var CHAR_NORMAL = 0;
21031
+ var CHAR_BOLD = 1;
21032
+ var CHAR_ITALIC = 2;
21033
+ var CHAR_BOLD_ITALIC = 3;
21034
+ var CHAR_CODE = 4;
21035
+ var CHAR_H1 = 5;
21036
+ var CHAR_H2 = 6;
21037
+ var CHAR_H3 = 7;
21038
+ var CHAR_H4 = 8;
21039
+ var CHAR_TABLE_HEADER = 9;
21040
+ var CHAR_QUOTE = 10;
21041
+ var PARA_NORMAL = 0;
21042
+ var PARA_H1 = 1;
21043
+ var PARA_H2 = 2;
21044
+ var PARA_H3 = 3;
21045
+ var PARA_H4 = 4;
21046
+ var PARA_CODE = 5;
21047
+ var PARA_QUOTE = 6;
21048
+ var PARA_LIST = 7;
21049
+ var DEFAULT_TEXT_COLOR = "#000000";
21050
+ function resolveTheme(theme) {
21051
+ return {
21052
+ h1: theme?.headingColors?.[1] ?? DEFAULT_TEXT_COLOR,
21053
+ h2: theme?.headingColors?.[2] ?? DEFAULT_TEXT_COLOR,
21054
+ h3: theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
21055
+ h4: theme?.headingColors?.[4] ?? theme?.headingColors?.[3] ?? DEFAULT_TEXT_COLOR,
21056
+ body: theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
21057
+ quote: theme?.quoteColor ?? DEFAULT_TEXT_COLOR,
21058
+ /** quoteColor가 명시되었는지 — blockquote charPr 분기에 사용 (baseline 호환) */
21059
+ hasQuoteOption: theme?.quoteColor !== void 0,
21060
+ tableHeader: theme?.tableHeaderColor ?? theme?.bodyColor ?? DEFAULT_TEXT_COLOR,
21061
+ tableHeaderBold: !!theme?.tableHeaderBold
21062
+ };
21063
+ }
21064
+ async function markdownToHwpx(markdown, options) {
21065
+ const theme = resolveTheme(options?.theme);
21066
+ const gongmun = options?.gongmun ? resolveGongmun(options.gongmun) : null;
21067
+ const blocks = parseMarkdownToBlocks(markdown);
21068
+ const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
21069
+ const fit = gongmun && gongmunList ? computeGongmunFitPlan(blocks, gongmun, gongmunList) : null;
21070
+ const sectionXml = blocksToSectionXml(blocks, theme, gongmun, gongmunList, fit);
21071
+ const zip = new JSZip6();
21072
+ zip.file("mimetype", "application/hwp+zip", { compression: "STORE" });
21073
+ zip.file("META-INF/container.xml", generateContainerXml());
21074
+ zip.file("Contents/content.hpf", generateManifest());
21075
+ zip.file("Contents/header.xml", generateHeaderXml(theme, gongmun, fit?.variants ?? []));
21076
+ zip.file("Contents/section0.xml", sectionXml);
21077
+ zip.file("Preview/PrvText.txt", buildPrvText(blocks));
21078
+ return await zip.generateAsync({ type: "arraybuffer" });
21079
+ }
21080
+ function buildPrvText(blocks) {
21081
+ const lines = [];
21082
+ let bytes = 0;
21083
+ for (const b of blocks) {
21084
+ let text = b.text || (b.rows ? b.rows.map((r) => r.join(" ")).join("\n") : "");
21085
+ if (b.type === "html_table") text = text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
21086
+ if (!text) continue;
21087
+ lines.push(text);
21088
+ bytes += text.length * 3;
21089
+ if (bytes > 1024) break;
21090
+ }
21091
+ return lines.join("\n").slice(0, 1024);
21092
+ }
21093
+ function parseMarkdownToBlocks(md2) {
21094
+ const lines = md2.split("\n");
21095
+ const blocks = [];
21096
+ let i = 0;
21097
+ while (i < lines.length) {
21098
+ const line = lines[i];
21099
+ if (!line.trim()) {
21100
+ i++;
21101
+ continue;
21102
+ }
21103
+ const fenceMatch = line.match(/^(`{3,}|~{3,})(.*)$/);
21104
+ if (fenceMatch) {
21105
+ const fence = fenceMatch[1];
21106
+ const lang = fenceMatch[2].trim();
21107
+ const codeLines = [];
21108
+ i++;
21109
+ while (i < lines.length && !lines[i].startsWith(fence)) {
21110
+ codeLines.push(lines[i]);
21111
+ i++;
21112
+ }
21113
+ if (i < lines.length) i++;
21114
+ blocks.push({ type: "code_block", text: codeLines.join("\n"), lang });
21115
+ continue;
21116
+ }
21117
+ if (/^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim())) {
20244
21118
  blocks.push({ type: "hr" });
20245
21119
  i++;
20246
21120
  continue;
@@ -20251,6 +21125,20 @@ function parseMarkdownToBlocks(md2) {
20251
21125
  i++;
20252
21126
  continue;
20253
21127
  }
21128
+ if (/^<table[\s>]/i.test(line.trimStart())) {
21129
+ const htmlLines = [];
21130
+ let depth = 0;
21131
+ while (i < lines.length) {
21132
+ const l = lines[i];
21133
+ htmlLines.push(l);
21134
+ depth += (l.match(/<table[\s>]/gi) ?? []).length;
21135
+ depth -= (l.match(/<\/table>/gi) ?? []).length;
21136
+ i++;
21137
+ if (depth <= 0) break;
21138
+ }
21139
+ blocks.push({ type: "html_table", text: htmlLines.join("\n") });
21140
+ continue;
21141
+ }
20254
21142
  if (line.trimStart().startsWith("|")) {
20255
21143
  const tableRows = [];
20256
21144
  while (i < lines.length && lines[i].trimStart().startsWith("|")) {
@@ -20332,18 +21220,19 @@ function spanToCharPrId(span) {
20332
21220
  function escapeXml(text) {
20333
21221
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20334
21222
  }
20335
- function generateRuns(text, defaultCharPr = CHAR_NORMAL) {
21223
+ function generateRuns(text, defaultCharPr = CHAR_NORMAL, mapCharId) {
20336
21224
  const spans = parseInlineMarkdown(text);
20337
21225
  return spans.map((span) => {
20338
- const charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
21226
+ let charId = span.code || span.bold || span.italic ? spanToCharPrId(span) : defaultCharPr;
21227
+ if (mapCharId) charId = mapCharId(charId);
20339
21228
  return `<hp:run charPrIDRef="${charId}"><hp:t>${escapeXml(span.text)}</hp:t></hp:run>`;
20340
21229
  }).join("");
20341
21230
  }
20342
- function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL) {
21231
+ function generateParagraph(text, paraPrId = PARA_NORMAL, charPrId = CHAR_NORMAL, mapCharId) {
20343
21232
  if (paraPrId === PARA_CODE) {
20344
21233
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0"><hp:run charPrIDRef="${CHAR_CODE}"><hp:t>${escapeXml(text)}</hp:t></hp:run></hp:p>`;
20345
21234
  }
20346
- const runs = generateRuns(text, charPrId);
21235
+ const runs = generateRuns(text, charPrId, mapCharId);
20347
21236
  return `<hp:p paraPrIDRef="${paraPrId}" styleIDRef="0">${runs}</hp:p>`;
20348
21237
  }
20349
21238
  function headingParaPrId(level) {
@@ -20383,7 +21272,7 @@ function charPr(id, height, bold, italic, fontId = 0, textColor = DEFAULT_TEXT_C
20383
21272
  const boldAttr = bold ? ` bold="1"` : "";
20384
21273
  const italicAttr = italic ? ` italic="1"` : "";
20385
21274
  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}>
21275
+ return ` <hh:charPr id="${id}" height="${height}" textColor="${textColor}" shadeColor="none" useFontSpace="0" useKerning="0" symMark="NONE" borderFillIDRef="1"${boldAttr}${italicAttr}>
20387
21276
  <hh:fontRef hangul="${effFont}" latin="${effFont}" hanja="${effFont}" japanese="${effFont}" other="${effFont}" symbol="${effFont}" user="${effFont}"/>
20388
21277
  <hh:ratio hangul="${ratioPct}" latin="${ratioPct}" hanja="${ratioPct}" japanese="100" other="100" symbol="100" user="100"/>
20389
21278
  <hh:spacing hangul="0" latin="0" hanja="0" japanese="0" other="0" symbol="0" user="0"/>
@@ -20402,13 +21291,58 @@ function paraPr(id, opts = {}) {
20402
21291
  <hh:autoSpacing eAsianEng="0" eAsianNum="0"/>
20403
21292
  <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
21293
  <hh:lineSpacing type="PERCENT" value="${lineSpacing}"/>
20405
- <hh:border borderFillIDRef="0" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
21294
+ <hh:border borderFillIDRef="1" offsetLeft="0" offsetRight="0" offsetTop="0" offsetBottom="0" connect="0" ignoreMargin="0"/>
20406
21295
  </hh:paraPr>`;
20407
21296
  }
20408
21297
  var GONGMUN_LIST_BASE = 8;
20409
21298
  var GONGMUN_LIST_LEVELS = 8;
20410
21299
  var GONGMUN_CENTER = GONGMUN_LIST_BASE + GONGMUN_LIST_LEVELS;
20411
- function buildCharProperties(theme, gongmun) {
21300
+ var CHAR_VARIANT_BASE = 11;
21301
+ var GONGMUN_BODY_RATIO = 95;
21302
+ function plainRenderText(text) {
21303
+ return parseInlineMarkdown(text).map((s) => s.text).join("");
21304
+ }
21305
+ function computeGongmunFitPlan(blocks, gongmun, gongmunList) {
21306
+ const minRatio = gongmun.autoFitMinRatio;
21307
+ if (minRatio === null || minRatio >= GONGMUN_BODY_RATIO) return null;
21308
+ const pageW = 59528 - mmToHwpunit(gongmun.margins.left) - mmToHwpunit(gongmun.margins.right);
21309
+ const ratioByBlock = /* @__PURE__ */ new Map();
21310
+ const variants = [];
21311
+ for (let i = 0; i < blocks.length; i++) {
21312
+ const block = blocks[i];
21313
+ let text;
21314
+ let firstW;
21315
+ let contW;
21316
+ if (block.type === "list_item" && gongmunList.has(i)) {
21317
+ const { marker, depth } = gongmunList.get(i);
21318
+ const content = plainRenderText(block.text || "");
21319
+ text = marker ? `${marker} ${content}` : content;
21320
+ const { left, indent } = levelIndent(depth, gongmun.bodyHeight, gongmun.numbering);
21321
+ firstW = pageW - left - Math.max(indent, 0);
21322
+ contW = pageW - left - Math.max(-indent, 0);
21323
+ } else if (block.type === "paragraph") {
21324
+ const raw = (block.text || "").trim();
21325
+ if (/^<center>[\s\S]*<\/center>$/i.test(raw)) continue;
21326
+ text = plainRenderText(raw);
21327
+ firstW = contW = pageW;
21328
+ } else {
21329
+ continue;
21330
+ }
21331
+ if (!text) continue;
21332
+ const r = fitRatioForFewerLines(text, firstW, contW, gongmun.bodyHeight, GONGMUN_BODY_RATIO, minRatio);
21333
+ if (r === null) continue;
21334
+ ratioByBlock.set(i, r);
21335
+ if (!variants.includes(r)) variants.push(r);
21336
+ }
21337
+ return ratioByBlock.size > 0 ? { ratioByBlock, variants } : null;
21338
+ }
21339
+ function variantMapper(fit, blockIdx) {
21340
+ const r = fit.ratioByBlock.get(blockIdx);
21341
+ if (r === void 0) return void 0;
21342
+ const vi = fit.variants.indexOf(r);
21343
+ return (id) => id >= 0 && id <= 3 ? CHAR_VARIANT_BASE + vi * 4 + id : id;
21344
+ }
21345
+ function buildCharProperties(theme, gongmun, ratioVariants = []) {
20412
21346
  let body = 1e3, code = 900, h1 = 1800, h2 = 1400, h3 = 1200, h4 = 1100;
20413
21347
  if (gongmun) {
20414
21348
  body = gongmun.bodyHeight;
@@ -20418,7 +21352,7 @@ function buildCharProperties(theme, gongmun) {
20418
21352
  h3 = body;
20419
21353
  h4 = Math.max(body - 100, 1300);
20420
21354
  }
20421
- const bodyRatio = gongmun ? 95 : 100;
21355
+ const bodyRatio = gongmun ? GONGMUN_BODY_RATIO : 100;
20422
21356
  const rows = [
20423
21357
  charPr(0, body, false, false, 0, theme.body, bodyRatio),
20424
21358
  charPr(1, body, true, false, 0, theme.body, bodyRatio),
@@ -20432,6 +21366,14 @@ function buildCharProperties(theme, gongmun) {
20432
21366
  charPr(CHAR_TABLE_HEADER, body, theme.tableHeaderBold, false, 0, theme.tableHeader),
20433
21367
  charPr(CHAR_QUOTE, body, false, true, 0, theme.quote)
20434
21368
  ];
21369
+ for (const r of ratioVariants) {
21370
+ rows.push(
21371
+ charPr(rows.length, body, false, false, 0, theme.body, r),
21372
+ charPr(rows.length + 1, body, true, false, 0, theme.body, r),
21373
+ charPr(rows.length + 2, body, false, true, 0, theme.body, r),
21374
+ charPr(rows.length + 3, body, true, true, 0, theme.body, r)
21375
+ );
21376
+ }
20435
21377
  return `<hh:charProperties itemCnt="${rows.length}">
20436
21378
  ${rows.join("\n")}
20437
21379
  </hh:charProperties>`;
@@ -20474,9 +21416,9 @@ ${base2.join("\n")}
20474
21416
  ${base.join("\n")}
20475
21417
  </hh:paraProperties>`;
20476
21418
  }
20477
- function generateHeaderXml(theme, gongmun) {
21419
+ function generateHeaderXml(theme, gongmun, ratioVariants = []) {
20478
21420
  const bodyFace = gongmun?.bodyFont === "gothic" ? "\uB9D1\uC740 \uACE0\uB515" : "\uD568\uCD08\uB86C\uBC14\uD0D5";
20479
- const charPropsXml = buildCharProperties(theme, gongmun);
21421
+ const charPropsXml = buildCharProperties(theme, gongmun, ratioVariants);
20480
21422
  const paraPropsXml = buildParaProperties(gongmun);
20481
21423
  return `<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
20482
21424
  <hh:head xmlns:hh="${NS_HEAD}" xmlns:hp="${NS_PARA}" xmlns:hc="${NS_CORE}" version="1.4" secCnt="1">
@@ -20532,25 +21474,21 @@ function generateHeaderXml(theme, gongmun) {
20532
21474
  </hh:fontface>
20533
21475
  </hh:fontfaces>
20534
21476
  <hh:borderFills itemCnt="2">
20535
- <hh:borderFill id="0" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
21477
+ <hh:borderFill id="1" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20536
21478
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20537
21479
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20538
21480
  <hh:leftBorder type="NONE" width="0.1 mm" color="#000000"/>
20539
21481
  <hh:rightBorder type="NONE" width="0.1 mm" color="#000000"/>
20540
21482
  <hh:topBorder type="NONE" width="0.1 mm" color="#000000"/>
20541
21483
  <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
21484
  </hh:borderFill>
20545
- <hh:borderFill id="1" threeD="0" shadow="0" centerLine="0" breakCellSeparateLine="0">
21485
+ <hh:borderFill id="2" threeD="0" shadow="0" centerLine="NONE" breakCellSeparateLine="0">
20546
21486
  <hh:slash type="NONE" Crooked="0" isCounter="0"/>
20547
21487
  <hh:backSlash type="NONE" Crooked="0" isCounter="0"/>
20548
21488
  <hh:leftBorder type="SOLID" width="0.12 mm" color="#000000"/>
20549
21489
  <hh:rightBorder type="SOLID" width="0.12 mm" color="#000000"/>
20550
21490
  <hh:topBorder type="SOLID" width="0.12 mm" color="#000000"/>
20551
21491
  <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
21492
  </hh:borderFill>
20555
21493
  </hh:borderFills>
20556
21494
  ${charPropsXml}
@@ -20597,14 +21535,74 @@ function generateTable(rows, theme) {
20597
21535
  const tdElements = cells.map((cell, colIdx) => {
20598
21536
  const runs = generateRuns(cell, headerCharPr);
20599
21537
  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>`;
21538
+ 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
21539
  }).join("");
20602
21540
  return `<hp:tr>${tdElements}</hp:tr>`;
20603
21541
  }).join("");
20604
21542
  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>`;
21543
+ 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
21544
  return `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
20607
21545
  }
21546
+ function layoutHtmlRows(rows) {
21547
+ const occupied = /* @__PURE__ */ new Set();
21548
+ const placed = [];
21549
+ let colCnt = 0;
21550
+ for (let r = 0; r < rows.length; r++) {
21551
+ let c = 0;
21552
+ for (const cell of rows[r].cells) {
21553
+ while (occupied.has(`${r},${c}`)) c++;
21554
+ const colSpan = Math.max(1, cell.colSpan);
21555
+ const rowSpan = Math.max(1, cell.rowSpan);
21556
+ placed.push({ r, c, colSpan, rowSpan, inner: cell.inner, isHeader: rows[r].tag === "th" });
21557
+ for (let dr = 0; dr < rowSpan; dr++) {
21558
+ for (let dc = 0; dc < colSpan; dc++) occupied.add(`${r + dr},${c + dc}`);
21559
+ }
21560
+ c += colSpan;
21561
+ colCnt = Math.max(colCnt, c);
21562
+ }
21563
+ }
21564
+ return { placed, rowCnt: rows.length, colCnt };
21565
+ }
21566
+ function unescapeHtml(s) {
21567
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
21568
+ }
21569
+ function generateHtmlTableXml(rawHtml, theme, totalWidth = 44e3) {
21570
+ const rows = parseHtmlTable(rawHtml);
21571
+ if (!rows || rows.length === 0) return null;
21572
+ const { placed, rowCnt, colCnt } = layoutHtmlRows(rows);
21573
+ if (rowCnt === 0 || colCnt === 0) return null;
21574
+ const colW = Math.floor(totalWidth / colCnt);
21575
+ const cellH = 1500;
21576
+ const tblW = colW * colCnt;
21577
+ const tblId = nextTableId();
21578
+ const useHeaderStyle = theme.tableHeader !== theme.body || theme.tableHeaderBold;
21579
+ const tcXmls = placed.map((cell) => {
21580
+ const headerCharPr = cell.isHeader && useHeaderStyle ? CHAR_TABLE_HEADER : CHAR_NORMAL;
21581
+ const { lines } = htmlCellInnerToLines(cell.inner);
21582
+ const paras = lines.map(
21583
+ (line) => `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="${headerCharPr}"><hp:t>${escapeXml(unescapeHtml(line))}</hp:t></hp:run></hp:p>`
21584
+ );
21585
+ let nestedH = 0;
21586
+ for (const nested of extractTopLevelTables(cell.inner)) {
21587
+ const nestedXml = generateHtmlTableXml(nested, theme, Math.max(colW * cell.colSpan - 1020, 4e3));
21588
+ if (nestedXml) {
21589
+ paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${nestedXml}</hp:run></hp:p>`);
21590
+ nestedH += (nested.match(/<tr[\s>]/gi) ?? []).length * cellH + 300;
21591
+ }
21592
+ }
21593
+ if (paras.length === 0) {
21594
+ paras.push(`<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="${headerCharPr}"><hp:t></hp:t></hp:run></hp:p>`);
21595
+ }
21596
+ const cellHeight = Math.max(cellH * cell.rowSpan, Math.max(lines.length, 1) * 800 + nestedH);
21597
+ 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>`;
21598
+ });
21599
+ const trXmls = [];
21600
+ for (let r = 0; r < rowCnt; r++) {
21601
+ const rowTcs = tcXmls.filter((_, i) => placed[i].r === r);
21602
+ trXmls.push(`<hp:tr>${rowTcs.join("")}</hp:tr>`);
21603
+ }
21604
+ 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>`;
21605
+ }
20608
21606
  function precomputeGongmunList(blocks, gongmun) {
20609
21607
  const result = /* @__PURE__ */ new Map();
20610
21608
  let i = 0;
@@ -20614,9 +21612,22 @@ function precomputeGongmunList(blocks, gongmun) {
20614
21612
  continue;
20615
21613
  }
20616
21614
  const run = [];
20617
- while (i < blocks.length && blocks[i].type === "list_item") {
20618
- run.push(i);
20619
- i++;
21615
+ while (i < blocks.length) {
21616
+ const t = blocks[i].type;
21617
+ if (t === "list_item") {
21618
+ run.push(i);
21619
+ i++;
21620
+ continue;
21621
+ }
21622
+ if (t === "table" || t === "html_table") {
21623
+ let j = i + 1;
21624
+ while (j < blocks.length && (blocks[j].type === "table" || blocks[j].type === "html_table")) j++;
21625
+ if (j < blocks.length && blocks[j].type === "list_item") {
21626
+ i = j;
21627
+ continue;
21628
+ }
21629
+ }
21630
+ break;
20620
21631
  }
20621
21632
  const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
20622
21633
  const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
@@ -20628,12 +21639,11 @@ function precomputeGongmunList(blocks, gongmun) {
20628
21639
  }
20629
21640
  return result;
20630
21641
  }
20631
- function blocksToSectionXml(blocks, theme, gongmun) {
21642
+ function blocksToSectionXml(blocks, theme, gongmun, gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null, fit = null) {
20632
21643
  const paraXmls = [];
20633
21644
  let isFirst = true;
20634
21645
  const orderedCounters = {};
20635
21646
  let prevWasOrdered = false;
20636
- const gongmunList = gongmun ? precomputeGongmunList(blocks, gongmun) : null;
20637
21647
  for (let blockIdx = 0; blockIdx < blocks.length; blockIdx++) {
20638
21648
  const block = blocks[blockIdx];
20639
21649
  let xml = "";
@@ -20655,7 +21665,7 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20655
21665
  if (ctr) {
20656
21666
  xml = generateParagraph(ctr[1].trim(), GONGMUN_CENTER);
20657
21667
  } else {
20658
- xml = generateParagraph(block.text || "");
21668
+ xml = generateParagraph(block.text || "", PARA_NORMAL, CHAR_NORMAL, fit ? variantMapper(fit, blockIdx) : void 0);
20659
21669
  }
20660
21670
  break;
20661
21671
  }
@@ -20679,7 +21689,7 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20679
21689
  const content = block.text || "";
20680
21690
  const text = marker2 ? `${marker2} ${content}` : content;
20681
21691
  const listCharPr = gongmun.numbering === "report" && depth === 0 ? CHAR_BOLD : CHAR_NORMAL;
20682
- xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr);
21692
+ xml = generateParagraph(text, GONGMUN_LIST_BASE + depth, listCharPr, fit ? variantMapper(fit, blockIdx) : void 0);
20683
21693
  break;
20684
21694
  }
20685
21695
  const indent = block.indent || 0;
@@ -20705,16 +21715,31 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20705
21715
  case "hr":
20706
21716
  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
21717
  break;
20708
- case "table":
20709
- if (block.rows) {
21718
+ case "table":
21719
+ if (block.rows) {
21720
+ if (isFirst) {
21721
+ const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
21722
+ paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
21723
+ isFirst = false;
21724
+ }
21725
+ xml = generateTable(block.rows, theme);
21726
+ }
21727
+ break;
21728
+ case "html_table": {
21729
+ const tbl = generateHtmlTableXml(block.text || "", theme);
21730
+ if (tbl) {
20710
21731
  if (isFirst) {
20711
21732
  const secRun = `<hp:run charPrIDRef="0">${generateSecPr(gongmun)}<hp:t></hp:t></hp:run>`;
20712
21733
  paraXmls.push(`<hp:p paraPrIDRef="0" styleIDRef="0">${secRun}</hp:p>`);
20713
21734
  isFirst = false;
20714
21735
  }
20715
- xml = generateTable(block.rows, theme);
21736
+ xml = `<hp:p paraPrIDRef="0" styleIDRef="0"><hp:run charPrIDRef="0">${tbl}</hp:run></hp:p>`;
21737
+ } else {
21738
+ const plain = (block.text || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
21739
+ xml = plain ? generateParagraph(plain) : "";
20716
21740
  }
20717
21741
  break;
21742
+ }
20718
21743
  }
20719
21744
  if (!xml) continue;
20720
21745
  if (isFirst && block.type !== "table") {
@@ -20735,49 +21760,6 @@ function blocksToSectionXml(blocks, theme, gongmun) {
20735
21760
  </hs:sec>`;
20736
21761
  }
20737
21762
 
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]);
20773
- }
20774
- }
20775
- ;
20776
- [prev, curr] = [curr, prev];
20777
- }
20778
- return prev[m];
20779
- }
20780
-
20781
21763
  // src/diff/compare.ts
20782
21764
  var SIMILARITY_THRESHOLD = 0.4;
20783
21765
  async function compare(bufferA, bufferB, options) {
@@ -20907,308 +21889,239 @@ function diffTableCells(a, b) {
20907
21889
  else type = "modified";
20908
21890
  row.push({ type, before: cellA, after: cellB });
20909
21891
  }
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
- });
21114
- }
21115
- if (rowCells.length) result.push({ tag, cells: rowCells });
21892
+ result.push(row);
21116
21893
  }
21117
21894
  return result;
21118
21895
  }
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;
21896
+
21897
+ // src/roundtrip/patcher.ts
21898
+ import JSZip7 from "jszip";
21899
+
21900
+ // src/roundtrip/table-rows.ts
21901
+ var ROW_OBJECT_RE = /<(?:[A-Za-z0-9_]+:)?(?:tbl|pic|equation|ole|container|shape|drawingObject|drawText|video|chart|fieldBegin|fieldEnd|ctrl)\b/;
21902
+ var TAG_AT_RE = /<[A-Za-z0-9_:]+(?:"[^"]*"|'[^']*'|[^>"'])*>/y;
21903
+ function patchTableRows(input) {
21904
+ const { table, scanTable, ctx, skip, origKeys, editedKeys } = input;
21905
+ const xml = ctx.scans[scanTable.sectionIndex]?.xml;
21906
+ if (!xml) return skip("\uC139\uC158 XML \uB9E4\uD551 \uC2E4\uD328");
21907
+ const numRows = table.rows;
21908
+ if (origKeys.length !== numRows) return skip("\uD45C \uD589 \uC88C\uD45C \uBD88\uC77C\uCE58 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21909
+ if (scanTable.rows.length !== numRows || scanTable.rowRanges.length !== numRows) {
21910
+ 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");
21911
+ }
21912
+ for (let r = 0; r < numRows; r++) {
21913
+ if (scanTable.rows[r].some((c) => c.rowAddr !== r)) {
21914
+ return skip("\uD589 \uC8FC\uC18C \uBE44\uC5F0\uC18D \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21135
21915
  }
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;
21916
+ }
21917
+ const allCells = scanTable.rows.flat();
21918
+ const explicitAddr = (c) => !!c.addrTagRange && /\browAddr\s*=\s*"/.test(xml.slice(c.addrTagRange.start, c.addrTagRange.end));
21919
+ const explicitCount = allCells.filter(explicitAddr).length;
21920
+ if (explicitCount !== 0 && explicitCount !== allCells.length) {
21921
+ return skip("\uC140 \uC8FC\uC18C(cellAddr) \uD45C\uAE30 \uD63C\uC7AC \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21922
+ }
21923
+ const hasExplicitAddr = explicitCount > 0;
21924
+ const pairs = alignUnits(origKeys, editedKeys);
21925
+ const seq = [];
21926
+ let lastOrig = -1;
21927
+ for (const [oi, ei] of pairs) {
21928
+ if (oi !== null && ei !== null) {
21929
+ seq.push({ kind: "keep", oi, ei });
21930
+ lastOrig = oi;
21931
+ } else if (oi !== null) {
21932
+ seq.push({ kind: "del", oi });
21933
+ lastOrig = oi;
21934
+ } else if (ei !== null) seq.push({ kind: "ins", ei, insertAt: lastOrig + 1 });
21935
+ }
21936
+ const dels = seq.filter((e) => e.kind === "del");
21937
+ const inss = seq.filter((e) => e.kind === "ins");
21938
+ const keeps = seq.filter((e) => e.kind === "keep");
21939
+ if (dels.length === 0 && inss.length === 0) {
21940
+ return skip("\uD45C \uD589 \uC815\uB82C \uC2E4\uD328 \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
21941
+ }
21942
+ if (keeps.length + inss.length === 0) return skip("\uBAA8\uB4E0 \uD589 \uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
21943
+ if (keeps.length === 0) return skip("\uD589 \uC804\uBA74 \uAD50\uCCB4 \u2014 \uC11C\uC2DD \uAE30\uC900 \uD589\uC774 \uC5C6\uC5B4 \uBBF8\uC9C0\uC6D0");
21944
+ const spans = allCells.filter((c) => (c.rowSpan ?? 1) > 1);
21945
+ for (const d of dels) {
21946
+ if (spans.some((s) => s.rowAddr <= d.oi && s.rowAddr + s.rowSpan > d.oi)) {
21947
+ return skip(`\uD45C ${d.oi + 1}\uD589 \uC0AD\uC81C\uAC00 \uC138\uB85C \uBCD1\uD569\uACFC \uACB9\uCE68 \u2014 \uBBF8\uC9C0\uC6D0`);
21948
+ }
21949
+ }
21950
+ for (const ins of inss) {
21951
+ const p = ins.insertAt;
21952
+ if (spans.some((s) => s.rowAddr < p && s.rowAddr + s.rowSpan > p)) {
21953
+ return skip("\uD589 \uC0BD\uC785 \uC704\uCE58\uAC00 \uC138\uB85C \uBCD1\uD569 \uB0B4\uBD80 \u2014 \uBBF8\uC9C0\uC6D0");
21954
+ }
21955
+ }
21956
+ for (const d of dels) {
21957
+ const rr = scanTable.rowRanges[d.oi];
21958
+ if (ROW_OBJECT_RE.test(xml.slice(rr.start, rr.end))) {
21959
+ 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`);
21960
+ }
21961
+ }
21962
+ const keptSet = new Set(keeps.map((k) => k.oi));
21963
+ const insertPlans = [];
21964
+ for (const ins of inss) {
21965
+ const p = ins.insertAt;
21966
+ const kept = [...keptSet].sort((a, b) => a - b);
21967
+ let template = -1;
21968
+ for (const k of kept) {
21969
+ if (k < p) template = k;
21970
+ }
21971
+ if (template <= 0 && p >= 1) {
21972
+ const following = kept.find((k) => k >= 1);
21973
+ if (following !== void 0) template = following;
21974
+ }
21975
+ if (template < 0) template = kept[0];
21976
+ const cells = input.editedCells(ins.ei);
21977
+ if (!cells) return skip("\uC0BD\uC785 \uD589\uC5D0 \uC774\uBBF8\uC9C0/\uC911\uCCA9\uD45C \uD3EC\uD568 \u2014 \uD589 \uCD94\uAC00 \uBBF8\uC9C0\uC6D0");
21978
+ for (const cell of cells) {
21979
+ const unstable = cell.lines.find((l) => sanitizeText(l) !== l);
21980
+ 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");
21981
+ }
21982
+ const tmplCells = scanTable.rows[template];
21983
+ const rr = scanTable.rowRanges[template];
21984
+ if (ROW_OBJECT_RE.test(xml.slice(rr.start, rr.end))) {
21985
+ 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");
21986
+ }
21987
+ 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");
21988
+ const tmplWidth = tmplCells.reduce((s, c) => s + c.colSpan, 0);
21989
+ 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");
21990
+ if (cells.length !== tmplCells.length) {
21991
+ return skip(`\uC0BD\uC785 \uD589 \uC140 \uC218(${cells.length}) \u2260 \uAE30\uC900 \uD589 \uC140 \uC218(${tmplCells.length}) \u2014 \uBBF8\uC9C0\uC6D0`);
21992
+ }
21993
+ for (let i = 0; i < cells.length; i++) {
21994
+ if (cells[i].rowSpan !== 1 || cells[i].colSpan !== tmplCells[i].colSpan) {
21995
+ return skip("\uC0BD\uC785 \uD589 \uBCD1\uD569 \uAD6C\uC870\uAC00 \uAE30\uC900 \uD589\uACFC \uB2E4\uB984 \u2014 \uBBF8\uC9C0\uC6D0");
21142
21996
  }
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;
21997
+ }
21998
+ insertPlans.push({ entry: ins, template, cells });
21999
+ }
22000
+ const net = inss.length - dels.length;
22001
+ TAG_AT_RE.lastIndex = scanTable.start;
22002
+ const tblOpen = TAG_AT_RE.exec(xml);
22003
+ if (!tblOpen || tblOpen.index !== scanTable.start) return skip("\uD45C \uC5EC\uB294 \uD0DC\uADF8 \uD574\uC11D \uC2E4\uD328");
22004
+ const rowCntM = tblOpen[0].match(/\browCnt\s*=\s*"(\d+)"/);
22005
+ if (!rowCntM || rowCntM.index === void 0) return skip("\uD45C rowCnt \uC18D\uC131 \uC5C6\uC74C \u2014 \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
22006
+ 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");
22007
+ const finalIndex = /* @__PURE__ */ new Map();
22008
+ {
22009
+ let fi = 0;
22010
+ for (const e of seq) {
22011
+ if (e.kind !== "del") finalIndex.set(e, fi++);
22012
+ }
22013
+ }
22014
+ const splices = [];
22015
+ let applied = 0;
22016
+ for (const d of dels) {
22017
+ const rr = scanTable.rowRanges[d.oi];
22018
+ splices.push({ start: rr.start, end: rr.end, replacement: "" });
22019
+ applied++;
22020
+ }
22021
+ const fragmentsByAnchor = /* @__PURE__ */ new Map();
22022
+ let heightDelta = 0;
22023
+ for (const plan of insertPlans) {
22024
+ const finalRow = finalIndex.get(plan.entry);
22025
+ const fragment = buildRowFragment(xml, scanTable, plan.template, plan.cells, finalRow, hasExplicitAddr, ctx);
22026
+ if (fragment === null) return skip("\uD589 \uBCF5\uC81C \uC2E4\uD328 (\uC140 \uBB38\uB2E8 \uAD6C\uC870 \uBBF8\uC9C0\uC6D0)");
22027
+ const p = plan.entry.insertAt;
22028
+ const anchor = p === 0 ? scanTable.rowRanges[0].start : scanTable.rowRanges[p - 1].end;
22029
+ let list = fragmentsByAnchor.get(anchor);
22030
+ if (!list) fragmentsByAnchor.set(anchor, list = []);
22031
+ list.push(fragment);
22032
+ heightDelta += rowHeightOf(fragment);
22033
+ applied++;
22034
+ }
22035
+ for (const [anchor, fragments] of fragmentsByAnchor) {
22036
+ splices.push({ start: anchor, end: anchor, replacement: fragments.join("") });
22037
+ }
22038
+ for (const d of dels) {
22039
+ const rr = scanTable.rowRanges[d.oi];
22040
+ heightDelta -= rowHeightOf(xml.slice(rr.start, rr.end));
22041
+ }
22042
+ if (hasExplicitAddr) {
22043
+ for (const k of keeps) {
22044
+ const fi = finalIndex.get(k);
22045
+ if (fi === k.oi) continue;
22046
+ for (const cell of scanTable.rows[k.oi]) {
22047
+ const sp = rowAddrRewrite(xml, cell, fi);
22048
+ if (sp) splices.push(sp);
21153
22049
  }
21154
22050
  }
21155
22051
  }
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);
22052
+ {
22053
+ const valStart = scanTable.start + rowCntM.index + rowCntM[0].indexOf('"') + 1;
22054
+ splices.push({ start: valStart, end: valStart + rowCntM[1].length, replacement: String(numRows + net) });
21165
22055
  }
21166
- if (/<img\s/i.test(work)) {
21167
- hadNonText = true;
21168
- work = work.replace(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi, "");
22056
+ if (heightDelta !== 0) {
22057
+ const sp = tableSzHeightSplice(xml, scanTable, tblOpen[0].length, heightDelta);
22058
+ if (sp) splices.push(sp);
21169
22059
  }
21170
- const lines = work.split(/<br\s*\/?>/gi).map((s) => s.trim()).filter((s) => s.length > 0);
21171
- return { lines, hadNonText };
22060
+ ctx.sectionSplices[scanTable.sectionIndex].push(...splices);
22061
+ for (const k of keeps) {
22062
+ if (origKeys[k.oi] !== editedKeys[k.ei]) applied += input.patchMatched(k.oi, k.ei);
22063
+ }
22064
+ return applied;
21172
22065
  }
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;
22066
+ var FRAG_OPEN = "<hp:tbl>";
22067
+ var FRAG_CLOSE = "</hp:tbl>";
22068
+ function buildRowFragment(xml, scanTable, template, cells, finalRow, hasExplicitAddr, ctx) {
22069
+ const rr = scanTable.rowRanges[template];
22070
+ const wrapped = FRAG_OPEN + xml.slice(rr.start, rr.end) + FRAG_CLOSE;
22071
+ const scan = scanSectionXml(wrapped, 0);
22072
+ const row = scan.tables[0]?.rows[0];
22073
+ if (!row || row.length !== cells.length) return null;
22074
+ const splices = allLinesegRemovalSplices(wrapped);
22075
+ for (let i = 0; i < cells.length; i++) {
22076
+ const paras = row[i].paragraphs;
22077
+ let lines = cells[i].lines;
22078
+ if (lines.length > 0 && paras.length === 0) return null;
22079
+ if (lines.length > paras.length) {
22080
+ lines = [...lines.slice(0, paras.length - 1), lines.slice(paras.length - 1).join(" ")];
22081
+ 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 });
22082
+ }
22083
+ for (let p = 0; p < paras.length; p++) {
22084
+ const sp = buildParagraphSplices(paras[p], lines[p] ?? "", wrapped);
22085
+ if (sp === null) return null;
22086
+ splices.push(...sp);
22087
+ }
22088
+ if (hasExplicitAddr) {
22089
+ const sp = rowAddrRewrite(wrapped, row[i], finalRow);
22090
+ if (sp) splices.push(sp);
21190
22091
  }
21191
22092
  }
21192
- return result;
22093
+ const patched = applySplices(wrapped, splices);
22094
+ return patched.slice(FRAG_OPEN.length, patched.length - FRAG_CLOSE.length);
21193
22095
  }
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
- }
22096
+ function rowAddrRewrite(xml, cell, newRow) {
22097
+ if (!cell.addrTagRange) return null;
22098
+ const tag = xml.slice(cell.addrTagRange.start, cell.addrTagRange.end);
22099
+ const m = tag.match(/\browAddr\s*=\s*"(\d+)"/);
22100
+ if (!m || m.index === void 0) return null;
22101
+ if (parseInt(m[1], 10) === newRow) return null;
22102
+ const valStart = cell.addrTagRange.start + m.index + m[0].indexOf('"') + 1;
22103
+ return { start: valStart, end: valStart + m[1].length, replacement: String(newRow) };
22104
+ }
22105
+ function rowHeightOf(fragment) {
22106
+ let max = 0;
22107
+ for (const m of fragment.matchAll(/<(?:[A-Za-z0-9_]+:)?cellSz\b(?:"[^"]*"|'[^']*'|[^>"'])*>/g)) {
22108
+ const h = m[0].match(/\bheight\s*=\s*"(\d+)"/);
22109
+ if (h) max = Math.max(max, parseInt(h[1], 10));
21209
22110
  }
21210
- if (depth === 0) result += html.slice(last);
21211
- return result;
22111
+ return max;
22112
+ }
22113
+ function tableSzHeightSplice(xml, scanTable, tblOpenLen, delta) {
22114
+ const from = scanTable.start + tblOpenLen;
22115
+ const to = scanTable.rowRanges[0]?.start ?? from;
22116
+ const slice = xml.slice(from, to);
22117
+ const szM = slice.match(/<(?:[A-Za-z0-9_]+:)?sz\b(?:"[^"]*"|'[^']*'|[^>"'])*>/);
22118
+ if (!szM || szM.index === void 0) return null;
22119
+ const hM = szM[0].match(/\bheight\s*=\s*"(\d+)"/);
22120
+ if (!hM || hM.index === void 0) return null;
22121
+ const oldH = parseInt(hM[1], 10);
22122
+ const newH = Math.max(0, oldH + delta);
22123
+ const valStart = from + szM.index + hM.index + hM[0].indexOf('"') + 1;
22124
+ return { start: valStart, end: valStart + hM[1].length, replacement: String(newH) };
21212
22125
  }
21213
22126
 
21214
22127
  // src/roundtrip/table-patch.ts
@@ -21220,37 +22133,89 @@ function patchGfmTable(table, scanTable, orig, edited, ctx, skip) {
21220
22133
  if (replica.length !== origRows.length || replica.some((row, r) => row.length !== origRows[r].length || row.some((c, j) => c.text !== origRows[r][j]))) {
21221
22134
  return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
21222
22135
  }
21223
- if (editedRows.length !== origRows.length) return skip("\uD45C \uD589 \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0 (\uD45C \uAD6C\uC870 \uBCC0\uACBD)");
22136
+ if (editedRows.length !== origRows.length) {
22137
+ return patchGfmTableRows(table, scanTable, origRows, editedRows, replica, ctx, skip);
22138
+ }
21224
22139
  let applied = 0;
21225
22140
  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`);
22141
+ applied += patchGfmRowPair(table, scanTable, origRows, editedRows, replica, r, r, ctx, skip);
22142
+ }
22143
+ return applied;
22144
+ }
22145
+ function patchGfmRowPair(table, scanTable, origRows, editedRows, replica, r, er, ctx, skip) {
22146
+ if (editedRows[er].length !== origRows[r].length) {
22147
+ skip(`\uD45C ${r + 1}\uD589 \uC5F4 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22148
+ return 0;
22149
+ }
22150
+ let applied = 0;
22151
+ for (let c = 0; c < origRows[r].length; c++) {
22152
+ if (origRows[r][c] === editedRows[er][c]) continue;
22153
+ const { gridR, gridC } = replica[r][c];
22154
+ const origTokens = extractCellTokens(origRows[r][c]);
22155
+ const editedTokens = extractCellTokens(editedRows[er][c]);
22156
+ if (origTokens !== editedTokens) {
22157
+ skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21228
22158
  continue;
21229
22159
  }
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;
22160
+ const newLines = unescapeGfmCell(stripCellTokens(editedRows[er][c])).split("\n").map((s) => s.trim()).filter(Boolean);
22161
+ const origLines = unescapeGfmCell(stripCellTokens(origRows[r][c])).split("\n").map((s) => s.trim()).filter(Boolean);
22162
+ const n = applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, origRows[r][c], editedRows[er][c], origLines.length);
22163
+ if (n > 0 && origTokens) {
22164
+ ctx.skipped.push({
22165
+ 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)",
22166
+ before: summarize(origRows[r][c]),
22167
+ after: summarize(editedRows[er][c]),
22168
+ partial: true
22169
+ });
21250
22170
  }
22171
+ applied += n;
21251
22172
  }
21252
22173
  return applied;
21253
22174
  }
22175
+ function patchGfmTableRows(table, scanTable, origRows, editedRows, replica, ctx, skip) {
22176
+ if (replica.length !== table.rows || replica.some((row, r) => row.some((c) => c.gridR !== r))) {
22177
+ 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");
22178
+ }
22179
+ if (table.cells.some((row) => row.some((c) => c && (c.colSpan > 1 || c.rowSpan > 1)))) {
22180
+ return skip("\uBCD1\uD569 \uC140 \uD45C \u2014 GFM \uD589 \uCD94\uAC00/\uC0AD\uC81C \uBBF8\uC9C0\uC6D0");
22181
+ }
22182
+ if (!gfmRenderStable(editedRows, table.cols)) {
22183
+ 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");
22184
+ }
22185
+ const key = (row) => row.join("\0");
22186
+ return patchTableRows({
22187
+ table,
22188
+ scanTable,
22189
+ ctx,
22190
+ skip,
22191
+ origKeys: origRows.map(key),
22192
+ editedKeys: editedRows.map(key),
22193
+ editedCells: (ei) => {
22194
+ const cells = [];
22195
+ for (const cellMd of editedRows[ei]) {
22196
+ if (extractCellTokens(cellMd)) return null;
22197
+ const lines = unescapeGfmCell(cellMd).split("\n").map((s) => s.trim()).filter(Boolean);
22198
+ cells.push({ lines, colSpan: 1, rowSpan: 1 });
22199
+ }
22200
+ return cells;
22201
+ },
22202
+ patchMatched: (oi, ei) => patchGfmRowPair(table, scanTable, origRows, editedRows, replica, oi, ei, ctx, skip)
22203
+ });
22204
+ }
22205
+ function gfmRenderStable(editedRows, cols) {
22206
+ const sim = {
22207
+ rows: editedRows.length,
22208
+ cols,
22209
+ hasHeader: editedRows.length > 1,
22210
+ cells: editedRows.map((row) => {
22211
+ const padded = row.length < cols ? [...row, ...Array(cols - row.length).fill("")] : row;
22212
+ return padded.map((md2) => ({ text: unescapeGfmCell(md2), colSpan: 1, rowSpan: 1 }));
22213
+ })
22214
+ };
22215
+ const replica = replicateGfmTable(sim);
22216
+ if (!replica || replica.length !== editedRows.length) return false;
22217
+ return replica.every((row, r) => row.length === editedRows[r].length && row.every((c, j) => c.text === editedRows[r][j]));
22218
+ }
21254
22219
  function patchHtmlTable(table, scanTable, orig, edited, ctx, skip) {
21255
22220
  return patchHtmlTableRaw(table, scanTable, orig.raw, edited.raw, ctx, skip, 0);
21256
22221
  }
@@ -21264,46 +22229,77 @@ function patchHtmlTableRaw(table, scanTable, origRaw, editedRaw, ctx, skip, dept
21264
22229
  }
21265
22230
  const editedRows = parseHtmlTable(editedRaw);
21266
22231
  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)");
22232
+ if (editedRows.length !== replica.length) {
22233
+ return patchHtmlTableRows(table, scanTable, replica, editedRows, ctx, skip, depth);
22234
+ }
21268
22235
  let applied = 0;
21269
22236
  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`);
22237
+ applied += patchHtmlRowPair(table, scanTable, replica, editedRows, r, r, ctx, skip, depth);
22238
+ }
22239
+ return applied;
22240
+ }
22241
+ function patchHtmlRowPair(table, scanTable, replica, editedRows, r, er, ctx, skip, depth) {
22242
+ if (editedRows[er].cells.length !== replica[r].cells.length) {
22243
+ skip(`\uD45C ${r + 1}\uD589 \uC140 \uC218 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
22244
+ return 0;
22245
+ }
22246
+ let applied = 0;
22247
+ for (let c = 0; c < replica[r].cells.length; c++) {
22248
+ const oc = replica[r].cells[c];
22249
+ const ec = editedRows[er].cells[c];
22250
+ if (oc.colSpan !== ec.colSpan || oc.rowSpan !== ec.rowSpan) {
22251
+ skip(`\uC140 \uBCD1\uD569(colspan/rowspan) \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0`);
21272
22252
  continue;
21273
22253
  }
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`);
22254
+ if (oc.inner === ec.inner) continue;
22255
+ const origContent = htmlCellInnerToLines(oc.inner);
22256
+ const editedContent = htmlCellInnerToLines(ec.inner);
22257
+ if (origContent.hadNonText || editedContent.hadNonText) {
22258
+ if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
22259
+ skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
21279
22260
  continue;
21280
22261
  }
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
- }
22262
+ const origTables = extractTopLevelTables(oc.inner);
22263
+ const editedTables = extractTopLevelTables(ec.inner);
22264
+ if (origTables.length !== editedTables.length) {
22265
+ skip("\uC140 \uB0B4 \uC911\uCCA9\uD45C \uCD94\uAC00/\uC0AD\uC81C\uB294 \uBBF8\uC9C0\uC6D0");
22266
+ continue;
21298
22267
  }
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);
22268
+ if (origTables.join("\n") !== editedTables.join("\n")) {
22269
+ applied += patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth);
21302
22270
  }
21303
22271
  }
22272
+ if (origContent.lines.join("\n") !== editedContent.lines.join("\n")) {
22273
+ const newLines = editedContent.lines.map((l) => unescapeGfm(l));
22274
+ applied += applyCellEdit(table, scanTable, oc.gridR, oc.gridC, newLines, ctx, oc.inner, ec.inner, origContent.lines.length);
22275
+ }
21304
22276
  }
21305
22277
  return applied;
21306
22278
  }
22279
+ function patchHtmlTableRows(table, scanTable, replica, editedRows, ctx, skip, depth) {
22280
+ if (replica.length !== table.rows || replica.some((row, r) => row.cells.some((c) => c.gridR !== r))) {
22281
+ 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");
22282
+ }
22283
+ const key = (row) => row.cells.map((c) => `${c.colSpan}x${c.rowSpan}:${c.inner}`).join("\0");
22284
+ return patchTableRows({
22285
+ table,
22286
+ scanTable,
22287
+ ctx,
22288
+ skip,
22289
+ origKeys: replica.map(key),
22290
+ editedKeys: editedRows.map(key),
22291
+ editedCells: (ei) => {
22292
+ const cells = [];
22293
+ for (const cell of editedRows[ei].cells) {
22294
+ const content = htmlCellInnerToLines(cell.inner);
22295
+ if (content.hadNonText) return null;
22296
+ cells.push({ lines: content.lines.map((l) => unescapeGfm(l)), colSpan: cell.colSpan, rowSpan: cell.rowSpan });
22297
+ }
22298
+ return cells;
22299
+ },
22300
+ patchMatched: (oi, ei) => patchHtmlRowPair(table, scanTable, replica, editedRows, oi, ei, ctx, skip, depth)
22301
+ });
22302
+ }
21307
22303
  function patchNestedTables(table, scanTable, oc, origTables, editedTables, ctx, skip, depth) {
21308
22304
  const irCell = table.cells[oc.gridR]?.[oc.gridC];
21309
22305
  const scanCell = scanTable.cellByAnchor.get(`${oc.gridR},${oc.gridC}`);
@@ -21403,7 +22399,7 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21403
22399
  splices.push(...sp);
21404
22400
  sectionIndex = target.sectionIndex;
21405
22401
  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) });
22402
+ 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
22403
  }
21408
22404
  } else {
21409
22405
  const assigned = [];
@@ -21415,7 +22411,9 @@ function applyCellEdit(table, scanTable, gridR, gridC, newLines, ctx, before, af
21415
22411
  }
21416
22412
  }
21417
22413
  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) });
22414
+ 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 });
22415
+ } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
22416
+ 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
22417
  }
21420
22418
  for (let i = 0; i < nonEmpty.length; i++) {
21421
22419
  if (assigned[i] === nonEmpty[i].text || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].text)) continue;
@@ -21588,7 +22586,8 @@ async function patchHwpx(original, editedMarkdown, options) {
21588
22586
  try {
21589
22587
  for (let i = 0; i < scans.length; i++) {
21590
22588
  if (sectionSplices[i].length === 0) continue;
21591
- sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml));
22589
+ const claimed = sectionSplices[i].filter((s) => s.end > s.start);
22590
+ sectionSplices[i].push(...allLinesegRemovalSplices(scans[i].xml).filter((ls) => !claimed.some((c) => ls.start >= c.start && ls.end <= c.end)));
21592
22591
  const newXml = applySplices(scans[i].xml, sectionSplices[i]);
21593
22592
  replacements.set(sectionPaths[i], encoder.encode(newXml));
21594
22593
  }
@@ -21656,82 +22655,6 @@ function buildTableOrdinals(blocks) {
21656
22655
  }
21657
22656
  return map;
21658
22657
  }
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
22658
  function resolveParagraphMappings(blocks, scans) {
21736
22659
  const buckets = /* @__PURE__ */ new Map();
21737
22660
  for (const scan of scans) {
@@ -21874,7 +22797,7 @@ function patchParagraphUnit(block, orig, edited, ctx, skip) {
21874
22797
  const origPrefix = block.text.split(" ", 1)[0];
21875
22798
  const sp = newPlain.indexOf(" ");
21876
22799
  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)) {
22800
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
21878
22801
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
21879
22802
  } else {
21880
22803
  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) });
@@ -22273,21 +23196,24 @@ function readRecordsStrict(stream) {
22273
23196
  }
22274
23197
  return recs;
22275
23198
  }
22276
- function serializeRecords(recs, repl) {
23199
+ function serializeRecords(recs, repl, inserts) {
22277
23200
  const parts = [];
22278
- for (let i = 0; i < recs.length; i++) {
22279
- const data = repl?.get(i) ?? recs[i].data;
23201
+ const push = (tagId, level, data) => {
22280
23202
  const ext = data.length >= 4095;
22281
23203
  const header = Buffer.alloc(ext ? 8 : 4);
22282
- header.writeUInt32LE((recs[i].tagId & 1023 | (recs[i].level & 1023) << 10 | (ext ? 4095 : data.length) << 20) >>> 0, 0);
23204
+ header.writeUInt32LE((tagId & 1023 | (level & 1023) << 10 | (ext ? 4095 : data.length) << 20) >>> 0, 0);
22283
23205
  if (ext) header.writeUInt32LE(data.length, 4);
22284
23206
  parts.push(header, data);
23207
+ };
23208
+ for (let i = 0; i < recs.length; i++) {
23209
+ for (const ins of inserts?.get(i) ?? []) push(ins.tagId, ins.level, ins.data);
23210
+ push(recs[i].tagId, recs[i].level, repl?.get(i) ?? recs[i].data);
22285
23211
  }
22286
23212
  return Buffer.concat(parts);
22287
23213
  }
22288
23214
  function scanSection(stream, sectionIndex, compressed) {
22289
23215
  const records = readRecordsStrict(stream);
22290
- if (!records) return { records: [], safe: false, paras: [], tables: [], compressed, repl: /* @__PURE__ */ new Map() };
23216
+ if (!records) return { records: [], safe: false, paras: [], tables: [], compressed, repl: /* @__PURE__ */ new Map(), inserts: /* @__PURE__ */ new Map() };
22291
23217
  const safe = serializeRecords(records).equals(stream);
22292
23218
  const parent = new Int32Array(records.length).fill(-1);
22293
23219
  const stack = [];
@@ -22386,7 +23312,7 @@ function scanSection(stream, sectionIndex, compressed) {
22386
23312
  }
22387
23313
  tables.push({ sectionIndex, rows, cols, cells });
22388
23314
  }
22389
- return { records, safe, paras, tables, compressed, repl: /* @__PURE__ */ new Map() };
23315
+ return { records, safe, paras, tables, compressed, repl: /* @__PURE__ */ new Map(), inserts: /* @__PURE__ */ new Map() };
22390
23316
  }
22391
23317
  async function patchHwp(original, editedMarkdown, options) {
22392
23318
  const skipped = [];
@@ -22454,15 +23380,15 @@ async function patchHwp(original, editedMarkdown, options) {
22454
23380
  }
22455
23381
  }
22456
23382
  let data;
22457
- const dirty = scans.some((s) => s.repl.size > 0);
23383
+ const dirty = scans.some((s) => s.repl.size > 0 || s.inserts.size > 0);
22458
23384
  if (!dirty) {
22459
23385
  data = new Uint8Array(original);
22460
23386
  } else {
22461
23387
  try {
22462
23388
  let out = originalBuf;
22463
23389
  for (let i = 0; i < scans.length; i++) {
22464
- if (scans[i].repl.size === 0) continue;
22465
- const newStream = serializeRecords(scans[i].records, scans[i].repl);
23390
+ if (scans[i].repl.size === 0 && scans[i].inserts.size === 0) continue;
23391
+ const newStream = serializeRecords(scans[i].records, scans[i].repl, scans[i].inserts);
22466
23392
  const content = compressed ? deflateRawSync2(newStream) : newStream;
22467
23393
  out = replaceOleStream(out, sectionPaths[i], content);
22468
23394
  }
@@ -22617,7 +23543,7 @@ function patchParagraph(block, orig, edited, ctx, skip) {
22617
23543
  const origPrefix = block.text.split(" ", 1)[0];
22618
23544
  const sp = newPlain.indexOf(" ");
22619
23545
  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)) {
23546
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
22621
23547
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
22622
23548
  } else {
22623
23549
  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 +23608,6 @@ function patchGfmCells(scanTable, orig, edited, ctx, skip) {
22682
23608
  }
22683
23609
  return applied;
22684
23610
  }
22685
- function extractImgTags5(inner) {
22686
- return (inner.match(/<img\s(?:"[^"]*"|'[^']*'|[^>"'])*?>/gi) || []).join(" ");
22687
- }
22688
23611
  function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
22689
23612
  if (replicateTableToHtml(table) !== orig.raw) return skip("\uD45C \uC88C\uD45C \uC7AC\uD604 \uBD88\uC77C\uCE58 \u2014 \uB9E4\uD551 \uC2E0\uB8B0 \uBD88\uAC00");
22690
23613
  const replica = replicateHtmlTable(table);
@@ -22712,7 +23635,7 @@ function patchHtmlCells5(table, scanTable, orig, edited, ctx, skip) {
22712
23635
  const origContent = htmlCellInnerToLines(oc.inner);
22713
23636
  const editedContent = htmlCellInnerToLines(ec.inner);
22714
23637
  if (origContent.hadNonText || editedContent.hadNonText) {
22715
- if (extractImgTags5(oc.inner) !== extractImgTags5(ec.inner)) {
23638
+ if (extractImgTags(oc.inner) !== extractImgTags(ec.inner)) {
22716
23639
  skip("\uC140 \uB0B4 \uC774\uBBF8\uC9C0 \uBCC0\uACBD\uC740 \uBBF8\uC9C0\uC6D0");
22717
23640
  continue;
22718
23641
  }
@@ -22793,22 +23716,25 @@ function applyCellEdit5(table, scanTable, gridR, gridC, newLines, ctx, before, a
22793
23716
  }
22794
23717
  const unstable = newLines.find((l) => sanitizeText(l) !== l);
22795
23718
  if (unstable !== void 0) return skip("\uACF5\uBC31 \uC815\uADDC\uD654 \uBD88\uC548\uC815 \uD14D\uC2A4\uD2B8 \u2014 \uD328\uCE58 \uC2DC \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
22796
- if (nonEmpty.length === 0) return skip("\uBE48 \uC140 \uD14D\uC2A4\uD2B8 \uCC44\uC6B0\uAE30\uB294 HWP5 \uBBF8\uC9C0\uC6D0 (v1) \u2014 \uBB38\uB2E8 \uC0DD\uC131 \uD544\uC694");
23719
+ const targets = nonEmpty.length > 0 ? nonEmpty : cell.paras;
23720
+ if (targets.length === 0) return skip("\uC140\uC5D0 \uBB38\uB2E8\uC774 \uC5C6\uC74C \u2014 \uBBF8\uC9C0\uC6D0");
22797
23721
  const assigned = [];
22798
- for (let i = 0; i < nonEmpty.length; i++) {
23722
+ for (let i = 0; i < targets.length; i++) {
22799
23723
  if (i < newLines.length) {
22800
- assigned.push(i === nonEmpty.length - 1 && newLines.length > nonEmpty.length ? newLines.slice(i).join(" ") : newLines[i]);
23724
+ assigned.push(i === targets.length - 1 && newLines.length > targets.length ? newLines.slice(i).join(" ") : newLines[i]);
22801
23725
  } else {
22802
23726
  assigned.push("");
22803
23727
  }
22804
23728
  }
22805
- 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) });
23729
+ if (newLines.length > targets.length) {
23730
+ 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 });
23731
+ } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
23732
+ 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
23733
  }
22808
23734
  let staged = 0;
22809
- for (let i = 0; i < nonEmpty.length; i++) {
22810
- if (assigned[i] === nonEmpty[i].rawText || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].rawText)) continue;
22811
- staged += stageParaPatch(ctx.scans[nonEmpty[i].sectionIndex], nonEmpty[i], assigned[i], skip);
23735
+ for (let i = 0; i < targets.length; i++) {
23736
+ if (assigned[i] === targets[i].rawText || normForMatch(assigned[i]) === normForMatch(targets[i].rawText)) continue;
23737
+ staged += stageParaPatch(ctx.scans[targets[i].sectionIndex], targets[i], assigned[i], skip);
22812
23738
  }
22813
23739
  return staged > 0 ? 1 : 0;
22814
23740
  }
@@ -22878,7 +23804,21 @@ function splitParaText(data) {
22878
23804
  if (firstP < 0) firstP = k;
22879
23805
  lastP = k;
22880
23806
  }
22881
- if (firstP < 0) return null;
23807
+ if (firstP < 0) {
23808
+ if (toks.some((t) => t.visible)) return null;
23809
+ let cut = data.length;
23810
+ for (const t of toks) if (data.readUInt16LE(t.start) === 13) {
23811
+ cut = t.start;
23812
+ break;
23813
+ }
23814
+ return {
23815
+ prefix: data.subarray(0, cut),
23816
+ prefixUnits: cut / 2,
23817
+ core: "",
23818
+ suffix: data.subarray(cut),
23819
+ suffixUnits: (data.length - cut) / 2
23820
+ };
23821
+ }
22882
23822
  for (let k = firstP; k <= lastP; k++) if (!toks[k].plain) return null;
22883
23823
  for (let k = 0; k < firstP; k++) if (toks[k].visible) return null;
22884
23824
  for (let k = lastP + 1; k < toks.length; k++) if (toks[k].visible) return null;
@@ -22911,7 +23851,6 @@ function rebuildCharShape(csData, coreStartUnit) {
22911
23851
  }
22912
23852
  function stageParaPatch(scan, para, newPlain, skip) {
22913
23853
  if (!scan.safe) return skip("\uC139\uC158 \uB808\uCF54\uB4DC \uC7AC\uC9C1\uB82C\uD654 \uBD88\uC77C\uCE58 \u2014 \uC548\uC804\uC744 \uC704\uD574 \uC774 \uC139\uC158\uC740 \uBBF8\uC9C0\uC6D0");
22914
- if (para.textIdx === -1) return skip("\uBE48 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
22915
23854
  if (para.textIdx === -2) return skip("\uBCF5\uC218 PARA_TEXT \uB808\uCF54\uB4DC \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
22916
23855
  if (para.rangeTagCount > 0) return skip("\uBC94\uC704 \uD0DC\uADF8(\uD615\uAD11\uD39C/\uAD50\uC815\uBD80\uD638) \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
22917
23856
  if (para.charShapeIdx < 0 || para.lineSegIdx < 0) return skip("\uBB38\uB2E8 \uB808\uCF54\uB4DC \uAD6C\uC131 \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
@@ -22919,11 +23858,27 @@ function stageParaPatch(scan, para, newPlain, skip) {
22919
23858
  if (/[\u0000-\u001f]/.test(newPlain)) return skip("\uC0C8 \uD14D\uC2A4\uD2B8\uC5D0 \uC81C\uC5B4\uBB38\uC790 \uD3EC\uD568 \u2014 \uBBF8\uC9C0\uC6D0");
22920
23859
  const records = scan.records;
22921
23860
  const headerRec = records[para.headerIdx];
22922
- const textRec = records[para.textIdx];
22923
23861
  const charShapeRec = records[para.charShapeIdx];
22924
23862
  if (charShapeRec.data.length < 8) {
22925
23863
  return skip("CHAR_SHAPE \uB808\uCF54\uB4DC \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
22926
23864
  }
23865
+ if (para.textIdx === -1) {
23866
+ const nCharsLow = para.nCharsRaw & 2147483647;
23867
+ if (nCharsLow > 1) return skip("PARA_TEXT \uC5C6\uB294 \uBB38\uB2E8\uC758 nChars \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
23868
+ const paraEnd = nCharsLow === 1 ? Buffer.from([13, 0]) : Buffer.alloc(0);
23869
+ const at = para.headerIdx + 1;
23870
+ const list = scan.inserts.get(at) ?? [];
23871
+ list.push({ tagId: TAG_PARA_TEXT, level: headerRec.level + 1, data: Buffer.concat([Buffer.from(newPlain, "utf16le"), paraEnd]) });
23872
+ scan.inserts.set(at, list);
23873
+ const newHeader2 = Buffer.from(headerRec.data);
23874
+ newHeader2.writeUInt32LE((para.nCharsRaw & 2147483648 | newPlain.length + nCharsLow) >>> 0, 0);
23875
+ const cs2 = rebuildCharShape(charShapeRec.data, 0);
23876
+ scan.repl.set(para.charShapeIdx, cs2.buf);
23877
+ newHeader2.writeUInt16LE(cs2.count, 12);
23878
+ scan.repl.set(para.headerIdx, newHeader2);
23879
+ return 1;
23880
+ }
23881
+ const textRec = records[para.textIdx];
22927
23882
  const seg = splitParaText(textRec.data);
22928
23883
  if (!seg) {
22929
23884
  return skip(para.ctrlMask !== 0 ? "\uCEE8\uD2B8\uB864 \uBB38\uC790(\uD0ED/\uD544\uB4DC/\uD2B9\uC218\uACF5\uBC31 \uB4F1 \uD14D\uC2A4\uD2B8 \uC911\uAC04) \uD3EC\uD568 \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)" : "PARA_TEXT \uC7AC\uAD6C\uC131 \uBD88\uC77C\uCE58 \u2014 \uC6D0\uBB38 \uBCF4\uC874 \uBD88\uAC00\uB85C \uBBF8\uC9C0\uC6D0");
@@ -23178,6 +24133,7 @@ var HwpxSession = class _HwpxSession {
23178
24133
  try {
23179
24134
  for (let s = 0; s < st.scans.length; s++) {
23180
24135
  if (sectionSplices[s].length === 0) continue;
24136
+ sectionSplices[s].push(...allLinesegRemovalSplices(st.scans[s].xml));
23181
24137
  replacements.set(st.sectionPaths[s], encoder.encode(applySplices(st.scans[s].xml, sectionSplices[s])));
23182
24138
  }
23183
24139
  } catch (err) {
@@ -23234,7 +24190,7 @@ var HwpxSession = class _HwpxSession {
23234
24190
  const origPrefix = block.text.split(" ", 1)[0];
23235
24191
  const sp = newPlain.indexOf(" ");
23236
24192
  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)) {
24193
+ if (newFirst === origPrefix || AUTONUM_PREFIX_RE.test(newFirst)) {
23238
24194
  newPlain = sp > 0 ? newPlain.slice(sp + 1) : "";
23239
24195
  } else {
23240
24196
  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 +24484,7 @@ async function parseHwp(buffer, options) {
23528
24484
  async function parsePdf(buffer, options) {
23529
24485
  let parsePdfDocument;
23530
24486
  try {
23531
- const mod = await import("./parser-XT65AEDJ.js");
24487
+ const mod = await import("./parser-DCK42RMA.js");
23532
24488
  parsePdfDocument = mod.parsePdfDocument;
23533
24489
  } catch {
23534
24490
  return {
@@ -23620,12 +24576,16 @@ async function fillForm(input, values, outputFormat = "markdown") {
23620
24576
  export {
23621
24577
  HwpxSession,
23622
24578
  PRESET_ALIAS,
24579
+ SPACE_EM_FIXED,
24580
+ SPACE_EM_FONT,
23623
24581
  VERSION,
24582
+ ValueCursor,
23624
24583
  applySplices,
23625
24584
  blocksToMarkdown,
23626
24585
  blocksToPdf,
23627
24586
  buildParagraphSplices,
23628
24587
  buildRangeSplices,
24588
+ charWidthEm1000,
23629
24589
  compare,
23630
24590
  detectFormat,
23631
24591
  detectOle2Format,
@@ -23636,6 +24596,7 @@ export {
23636
24596
  fillForm,
23637
24597
  fillFormFields,
23638
24598
  fillHwpx,
24599
+ fitRatioForFewerLines,
23639
24600
  inferFieldType,
23640
24601
  isHwpxFile,
23641
24602
  isLabelCell,
@@ -23644,6 +24605,7 @@ export {
23644
24605
  isZipFile,
23645
24606
  markdownToHwpx,
23646
24607
  markdownToPdf,
24608
+ measureTextWidth,
23647
24609
  normalizeGongmunPreset,
23648
24610
  openHwpxDocument,
23649
24611
  parse,
@@ -23659,6 +24621,8 @@ export {
23659
24621
  patchHwpx,
23660
24622
  patchHwpxBlocks,
23661
24623
  renderHtml,
23662
- scanSectionXml
24624
+ scanSectionXml,
24625
+ simulateWrap,
24626
+ simulateWrapKeepWord
23663
24627
  };
23664
24628
  //# sourceMappingURL=index.js.map