kordoc 3.7.0 → 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.
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  sanitizeHref,
20
20
  stripDtd,
21
21
  toArrayBuffer
22
- } from "./chunk-MEVKYW55.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
  }
@@ -2628,32 +2633,42 @@ function resolveImageBlocks(binDataMap, blocks, warnings) {
2628
2633
  if (imageBlocks.length === 0) return [];
2629
2634
  const images = [];
2630
2635
  const renamed = /* @__PURE__ */ new Map();
2636
+ const resolved = /* @__PURE__ */ new Map();
2631
2637
  let imageIndex = 0;
2632
2638
  for (const block of imageBlocks) {
2633
2639
  if (!block.text) continue;
2634
2640
  const storageId = parseInt(block.text, 10);
2635
2641
  if (isNaN(storageId)) continue;
2636
- const bin = binDataMap.get(storageId);
2637
- if (!bin) {
2638
- warnings.push({ page: block.pageNumber, message: `BinData ${storageId} \uC5C6\uC74C`, code: "SKIPPED_IMAGE" });
2639
- block.type = "paragraph";
2640
- block.text = `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
2641
- 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);
2642
2663
  }
2643
- const mime = detectImageMime(bin.data);
2644
- if (!mime) {
2645
- 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);
2646
2666
  block.type = "paragraph";
2647
- block.text = `[\uC774\uBBF8\uC9C0: ${bin.name}]`;
2667
+ block.text = bin ? `[\uC774\uBBF8\uC9C0: ${bin.name}]` : `[\uC774\uBBF8\uC9C0: BinData ${storageId}]`;
2648
2668
  continue;
2649
2669
  }
2650
- imageIndex++;
2651
- const ext = mime.includes("jpeg") ? "jpg" : mime.includes("png") ? "png" : mime.includes("gif") ? "gif" : mime.includes("bmp") ? "bmp" : "bin";
2652
- const filename = `image_${String(imageIndex).padStart(3, "0")}.${ext}`;
2653
- images.push({ filename, data: new Uint8Array(bin.data), mimeType: mime });
2654
- renamed.set(storageId, filename);
2655
- block.text = filename;
2656
- 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 };
2657
2672
  }
2658
2673
  resolveCellImageSentinels(blocks, renamed);
2659
2674
  return images;
@@ -18341,7 +18356,7 @@ function parseTable(tbl, styles, numbering, footnotes, rels) {
18341
18356
  };
18342
18357
  return { type: "table", table };
18343
18358
  }
18344
- async function extractImages(zip, rels, doc) {
18359
+ async function extractImages(zip, rels, doc, warnings) {
18345
18360
  const blocks = [];
18346
18361
  const images = [];
18347
18362
  const drawingElements = findElements(doc.documentElement, "drawing");
@@ -18372,7 +18387,11 @@ async function extractImages(zip, rels, doc) {
18372
18387
  const filename = `image_${String(imgIdx).padStart(3, "0")}.${ext}`;
18373
18388
  images.push({ filename, data, mimeType: mimeMap[ext] ?? "image/png" });
18374
18389
  blocks.push({ type: "image", text: filename });
18375
- } 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
+ });
18376
18395
  }
18377
18396
  }
18378
18397
  }
@@ -18396,7 +18415,11 @@ async function parseDocxDocument(buffer, options) {
18396
18415
  if (stylesFile) {
18397
18416
  try {
18398
18417
  styles = parseStyles(await stylesFile.async("text"));
18399
- } 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
+ });
18400
18423
  }
18401
18424
  }
18402
18425
  let numbering = /* @__PURE__ */ new Map();
@@ -18404,7 +18427,11 @@ async function parseDocxDocument(buffer, options) {
18404
18427
  if (numFile) {
18405
18428
  try {
18406
18429
  numbering = parseNumbering(await numFile.async("text"));
18407
- } 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
+ });
18408
18435
  }
18409
18436
  }
18410
18437
  let footnotes = /* @__PURE__ */ new Map();
@@ -18412,7 +18439,11 @@ async function parseDocxDocument(buffer, options) {
18412
18439
  if (fnFile) {
18413
18440
  try {
18414
18441
  footnotes = parseFootnotes(await fnFile.async("text"));
18415
- } 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
+ });
18416
18447
  }
18417
18448
  }
18418
18449
  const docXml = await docFile.async("text");
@@ -18434,7 +18465,7 @@ async function parseDocxDocument(buffer, options) {
18434
18465
  if (block) blocks.push(block);
18435
18466
  }
18436
18467
  }
18437
- const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc);
18468
+ const { blocks: imgBlocks, images } = await extractImages(zip, rels, doc, warnings);
18438
18469
  const metadata = {};
18439
18470
  const coreFile = zip.file("docProps/core.xml");
18440
18471
  if (coreFile) {
@@ -18452,7 +18483,11 @@ async function parseDocxDocument(buffer, options) {
18452
18483
  if (created) metadata.createdAt = created;
18453
18484
  const modified = getFirst("dcterms:modified");
18454
18485
  if (modified) metadata.modifiedAt = modified;
18455
- } 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
+ });
18456
18491
  }
18457
18492
  }
18458
18493
  const outline = blocks.filter((b) => b.type === "heading").map((b) => ({ level: b.level ?? 2, text: b.text ?? "" }));
@@ -21577,9 +21612,22 @@ function precomputeGongmunList(blocks, gongmun) {
21577
21612
  continue;
21578
21613
  }
21579
21614
  const run = [];
21580
- while (i < blocks.length && blocks[i].type === "list_item") {
21581
- run.push(i);
21582
- 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;
21583
21631
  }
21584
21632
  const depths = run.map((bi) => Math.min(Math.max(blocks[bi].indent || 0, 0), GONGMUN_LIST_LEVELS - 1));
21585
21633
  const suppress = gongmun.numbering === "standard" ? computeSuppression(depths) : depths.map(() => false);
@@ -23148,21 +23196,24 @@ function readRecordsStrict(stream) {
23148
23196
  }
23149
23197
  return recs;
23150
23198
  }
23151
- function serializeRecords(recs, repl) {
23199
+ function serializeRecords(recs, repl, inserts) {
23152
23200
  const parts = [];
23153
- for (let i = 0; i < recs.length; i++) {
23154
- const data = repl?.get(i) ?? recs[i].data;
23201
+ const push = (tagId, level, data) => {
23155
23202
  const ext = data.length >= 4095;
23156
23203
  const header = Buffer.alloc(ext ? 8 : 4);
23157
- 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);
23158
23205
  if (ext) header.writeUInt32LE(data.length, 4);
23159
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);
23160
23211
  }
23161
23212
  return Buffer.concat(parts);
23162
23213
  }
23163
23214
  function scanSection(stream, sectionIndex, compressed) {
23164
23215
  const records = readRecordsStrict(stream);
23165
- 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() };
23166
23217
  const safe = serializeRecords(records).equals(stream);
23167
23218
  const parent = new Int32Array(records.length).fill(-1);
23168
23219
  const stack = [];
@@ -23261,7 +23312,7 @@ function scanSection(stream, sectionIndex, compressed) {
23261
23312
  }
23262
23313
  tables.push({ sectionIndex, rows, cols, cells });
23263
23314
  }
23264
- 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() };
23265
23316
  }
23266
23317
  async function patchHwp(original, editedMarkdown, options) {
23267
23318
  const skipped = [];
@@ -23329,15 +23380,15 @@ async function patchHwp(original, editedMarkdown, options) {
23329
23380
  }
23330
23381
  }
23331
23382
  let data;
23332
- const dirty = scans.some((s) => s.repl.size > 0);
23383
+ const dirty = scans.some((s) => s.repl.size > 0 || s.inserts.size > 0);
23333
23384
  if (!dirty) {
23334
23385
  data = new Uint8Array(original);
23335
23386
  } else {
23336
23387
  try {
23337
23388
  let out = originalBuf;
23338
23389
  for (let i = 0; i < scans.length; i++) {
23339
- if (scans[i].repl.size === 0) continue;
23340
- 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);
23341
23392
  const content = compressed ? deflateRawSync2(newStream) : newStream;
23342
23393
  out = replaceOleStream(out, sectionPaths[i], content);
23343
23394
  }
@@ -23665,24 +23716,25 @@ function applyCellEdit5(table, scanTable, gridR, gridC, newLines, ctx, before, a
23665
23716
  }
23666
23717
  const unstable = newLines.find((l) => sanitizeText(l) !== l);
23667
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");
23668
- 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");
23669
23721
  const assigned = [];
23670
- for (let i = 0; i < nonEmpty.length; i++) {
23722
+ for (let i = 0; i < targets.length; i++) {
23671
23723
  if (i < newLines.length) {
23672
- 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]);
23673
23725
  } else {
23674
23726
  assigned.push("");
23675
23727
  }
23676
23728
  }
23677
- if (newLines.length > nonEmpty.length) {
23729
+ if (newLines.length > targets.length) {
23678
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 });
23679
23731
  } else if (newLines.length < nonEmpty.length && nonEmpty.length > 1) {
23680
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 });
23681
23733
  }
23682
23734
  let staged = 0;
23683
- for (let i = 0; i < nonEmpty.length; i++) {
23684
- if (assigned[i] === nonEmpty[i].rawText || normForMatch(assigned[i]) === normForMatch(nonEmpty[i].rawText)) continue;
23685
- 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);
23686
23738
  }
23687
23739
  return staged > 0 ? 1 : 0;
23688
23740
  }
@@ -23752,7 +23804,21 @@ function splitParaText(data) {
23752
23804
  if (firstP < 0) firstP = k;
23753
23805
  lastP = k;
23754
23806
  }
23755
- 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
+ }
23756
23822
  for (let k = firstP; k <= lastP; k++) if (!toks[k].plain) return null;
23757
23823
  for (let k = 0; k < firstP; k++) if (toks[k].visible) return null;
23758
23824
  for (let k = lastP + 1; k < toks.length; k++) if (toks[k].visible) return null;
@@ -23785,7 +23851,6 @@ function rebuildCharShape(csData, coreStartUnit) {
23785
23851
  }
23786
23852
  function stageParaPatch(scan, para, newPlain, skip) {
23787
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");
23788
- if (para.textIdx === -1) return skip("\uBE48 \uBB38\uB2E8 \uD14D\uC2A4\uD2B8 \uCD94\uAC00\uB294 \uBBF8\uC9C0\uC6D0 (v1)");
23789
23854
  if (para.textIdx === -2) return skip("\uBCF5\uC218 PARA_TEXT \uB808\uCF54\uB4DC \uBB38\uB2E8 \u2014 \uBBF8\uC9C0\uC6D0 (v1)");
23790
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)");
23791
23856
  if (para.charShapeIdx < 0 || para.lineSegIdx < 0) return skip("\uBB38\uB2E8 \uB808\uCF54\uB4DC \uAD6C\uC131 \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
@@ -23793,11 +23858,27 @@ function stageParaPatch(scan, para, newPlain, skip) {
23793
23858
  if (/[\u0000-\u001f]/.test(newPlain)) return skip("\uC0C8 \uD14D\uC2A4\uD2B8\uC5D0 \uC81C\uC5B4\uBB38\uC790 \uD3EC\uD568 \u2014 \uBBF8\uC9C0\uC6D0");
23794
23859
  const records = scan.records;
23795
23860
  const headerRec = records[para.headerIdx];
23796
- const textRec = records[para.textIdx];
23797
23861
  const charShapeRec = records[para.charShapeIdx];
23798
23862
  if (charShapeRec.data.length < 8) {
23799
23863
  return skip("CHAR_SHAPE \uB808\uCF54\uB4DC \uBE44\uC815\uD615 \u2014 \uBBF8\uC9C0\uC6D0");
23800
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];
23801
23882
  const seg = splitParaText(textRec.data);
23802
23883
  if (!seg) {
23803
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");
@@ -24403,7 +24484,7 @@ async function parseHwp(buffer, options) {
24403
24484
  async function parsePdf(buffer, options) {
24404
24485
  let parsePdfDocument;
24405
24486
  try {
24406
- const mod = await import("./parser-ZHJFQR44.js");
24487
+ const mod = await import("./parser-DCK42RMA.js");
24407
24488
  parsePdfDocument = mod.parsePdfDocument;
24408
24489
  } catch {
24409
24490
  return {