kordoc 4.1.0 → 4.2.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 (109) hide show
  1. package/README.md +25 -5
  2. package/dist/{-AS4IABL2.js → -SXQIZHYA.js} +10 -9
  3. package/dist/{parser-PEOYBXBO.js → chunk-26AJJCFF.js} +1339 -2023
  4. package/dist/chunk-26AJJCFF.js.map +1 -0
  5. package/dist/{parser-XX4QTDFQ.js → chunk-4JPPLIIV.js} +1337 -2019
  6. package/dist/chunk-4JPPLIIV.js.map +1 -0
  7. package/dist/chunk-4VNLZ2GZ.js +13 -0
  8. package/dist/chunk-4VNLZ2GZ.js.map +1 -0
  9. package/dist/{chunk-37HJHCPA.js → chunk-6SHO3BNV.js} +3 -3
  10. package/dist/{chunk-UOO7LSDG.js → chunk-7FCNOKOR.js} +1 -9
  11. package/dist/chunk-7FCNOKOR.js.map +1 -0
  12. package/dist/{chunk-SSTK6IKK.js → chunk-CMN3SYZ3.js} +14 -5
  13. package/dist/chunk-CMN3SYZ3.js.map +1 -0
  14. package/dist/{chunk-F2ZU3IZG.js → chunk-ELOV2GIJ.js} +2 -2
  15. package/dist/chunk-F2HYH62H.cjs +130 -0
  16. package/dist/chunk-F2HYH62H.cjs.map +1 -0
  17. package/dist/{chunk-O5VS7RIR.js → chunk-FSXLSHCW.js} +3 -3
  18. package/dist/{chunk-O5VS7RIR.js.map → chunk-FSXLSHCW.js.map} +1 -1
  19. package/dist/chunk-GEPINOL6.js +83 -0
  20. package/dist/chunk-GEPINOL6.js.map +1 -0
  21. package/dist/chunk-H7OWEYH2.cjs +243 -0
  22. package/dist/chunk-H7OWEYH2.cjs.map +1 -0
  23. package/dist/{chunk-L2XQQTOY.js → chunk-IGGRLWT5.js} +126 -46
  24. package/dist/chunk-IGGRLWT5.js.map +1 -0
  25. package/dist/chunk-J2RZ444H.js +243 -0
  26. package/dist/chunk-J2RZ444H.js.map +1 -0
  27. package/dist/chunk-JP5YH3Z4.js +130 -0
  28. package/dist/chunk-JP5YH3Z4.js.map +1 -0
  29. package/dist/{parser-N4A7UGDK.cjs → chunk-LHROM2BO.cjs} +1300 -1985
  30. package/dist/chunk-LHROM2BO.cjs.map +1 -0
  31. package/dist/chunk-NNDVFNMJ.js +246 -0
  32. package/dist/chunk-NNDVFNMJ.js.map +1 -0
  33. package/dist/{chunk-WO52HVQJ.js → chunk-PLIY23LY.js} +2 -2
  34. package/dist/{chunk-GQVSHGG4.js → chunk-V6U6YS5B.js} +2 -2
  35. package/dist/{chunk-4KI23VHA.js → chunk-WF2E5P2G.js} +16 -130
  36. package/dist/chunk-WF2E5P2G.js.map +1 -0
  37. package/dist/{chunk-AX2R5Q2N.js → chunk-WQLHPOMJ.js} +3 -3
  38. package/dist/{chunk-37VKMUST.cjs → chunk-XTCT6KQ5.cjs} +34 -148
  39. package/dist/chunk-XTCT6KQ5.cjs.map +1 -0
  40. package/dist/chunks-ZQEFAZNC.js +10 -0
  41. package/dist/cli.js +76 -19
  42. package/dist/cli.js.map +1 -1
  43. package/dist/{formula-RXVSQPXI.js → formula-KSNAEBZ2.js} +23 -232
  44. package/dist/formula-KSNAEBZ2.js.map +1 -0
  45. package/dist/{formula-5NKVS2LR.cjs → formula-SB3NHDUM.cjs} +30 -240
  46. package/dist/formula-SB3NHDUM.cjs.map +1 -0
  47. package/dist/{formula-JCNF43NE.js → formula-SXC6LRRC.js} +23 -233
  48. package/dist/formula-SXC6LRRC.js.map +1 -0
  49. package/dist/index.cjs +527 -447
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +7 -3
  52. package/dist/index.d.ts +7 -3
  53. package/dist/index.js +129 -49
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp.js +21 -19
  56. package/dist/mcp.js.map +1 -1
  57. package/dist/models-KA4GLQK5.js +25 -0
  58. package/dist/parser-ND2YGQZR.js +800 -0
  59. package/dist/parser-ND2YGQZR.js.map +1 -0
  60. package/dist/parser-RBZHA6SY.js +804 -0
  61. package/dist/parser-RBZHA6SY.js.map +1 -0
  62. package/dist/parser-WDZOMDG4.cjs +803 -0
  63. package/dist/parser-WDZOMDG4.cjs.map +1 -0
  64. package/dist/pdf-ocr-2Z7VZIFI.cjs +439 -0
  65. package/dist/pdf-ocr-2Z7VZIFI.cjs.map +1 -0
  66. package/dist/pdf-ocr-FJFXFADQ.js +438 -0
  67. package/dist/pdf-ocr-FJFXFADQ.js.map +1 -0
  68. package/dist/pdf-ocr-VYE4DHD4.js +386 -0
  69. package/dist/pdf-ocr-VYE4DHD4.js.map +1 -0
  70. package/dist/{profile-io-PLDQFPJA.js → profile-io-TWHDIVPL.js} +3 -3
  71. package/dist/{rasterize-WWNQLKYW.js → rasterize-W2NEZUSW.js} +2 -2
  72. package/dist/render-4MG5QUDG.js +10 -0
  73. package/dist/seal-D2QNXJVO.js +10 -0
  74. package/dist/seal-D2QNXJVO.js.map +1 -0
  75. package/dist/{watch-SFHXZALC.js → watch-VWXJZT2R.js} +11 -10
  76. package/dist/watch-VWXJZT2R.js.map +1 -0
  77. package/package.json +2 -1
  78. package/dist/chunk-37VKMUST.cjs.map +0 -1
  79. package/dist/chunk-4KI23VHA.js.map +0 -1
  80. package/dist/chunk-L2XQQTOY.js.map +0 -1
  81. package/dist/chunk-SSTK6IKK.js.map +0 -1
  82. package/dist/chunk-UOO7LSDG.js.map +0 -1
  83. package/dist/chunks-WVMGF7JL.js +0 -10
  84. package/dist/formula-5NKVS2LR.cjs.map +0 -1
  85. package/dist/formula-JCNF43NE.js.map +0 -1
  86. package/dist/formula-RXVSQPXI.js.map +0 -1
  87. package/dist/parser-N4A7UGDK.cjs.map +0 -1
  88. package/dist/parser-PEOYBXBO.js.map +0 -1
  89. package/dist/parser-XX4QTDFQ.js.map +0 -1
  90. package/dist/provider-4FAYHJ6N.cjs +0 -39
  91. package/dist/provider-4FAYHJ6N.cjs.map +0 -1
  92. package/dist/provider-C6IOGIS5.js +0 -37
  93. package/dist/provider-C6IOGIS5.js.map +0 -1
  94. package/dist/provider-UIBW2MIH.js +0 -39
  95. package/dist/provider-UIBW2MIH.js.map +0 -1
  96. package/dist/render-T7S6H6AN.js +0 -10
  97. package/dist/seal-7PTL6MXH.js +0 -10
  98. package/dist/watch-SFHXZALC.js.map +0 -1
  99. /package/dist/{-AS4IABL2.js.map → -SXQIZHYA.js.map} +0 -0
  100. /package/dist/{chunk-37HJHCPA.js.map → chunk-6SHO3BNV.js.map} +0 -0
  101. /package/dist/{chunk-F2ZU3IZG.js.map → chunk-ELOV2GIJ.js.map} +0 -0
  102. /package/dist/{chunk-WO52HVQJ.js.map → chunk-PLIY23LY.js.map} +0 -0
  103. /package/dist/{chunk-GQVSHGG4.js.map → chunk-V6U6YS5B.js.map} +0 -0
  104. /package/dist/{chunk-AX2R5Q2N.js.map → chunk-WQLHPOMJ.js.map} +0 -0
  105. /package/dist/{chunks-WVMGF7JL.js.map → chunks-ZQEFAZNC.js.map} +0 -0
  106. /package/dist/{profile-io-PLDQFPJA.js.map → models-KA4GLQK5.js.map} +0 -0
  107. /package/dist/{render-T7S6H6AN.js.map → profile-io-TWHDIVPL.js.map} +0 -0
  108. /package/dist/{rasterize-WWNQLKYW.js.map → rasterize-W2NEZUSW.js.map} +0 -0
  109. /package/dist/{seal-7PTL6MXH.js.map → render-4MG5QUDG.js.map} +0 -0
@@ -0,0 +1,804 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ collapseEvenSpacing,
4
+ computeMedianFontSizeFromFreq,
5
+ detectHeadings,
6
+ detectKoreanListBlocks,
7
+ detectMarkerHeadings,
8
+ detectTableCaptions,
9
+ extractImageRegions,
10
+ extractPageBlocksWithLines,
11
+ filterHiddenText,
12
+ mergeCrossPageTables,
13
+ normalizeItems,
14
+ removeHeaderFooterBlocks
15
+ } from "./chunk-26AJJCFF.js";
16
+ import {
17
+ encodePng
18
+ } from "./chunk-7FCNOKOR.js";
19
+ import "./chunk-4VNLZ2GZ.js";
20
+ import {
21
+ parsePageRange
22
+ } from "./chunk-MOL7MDBG.js";
23
+ import {
24
+ blocksToMarkdown
25
+ } from "./chunk-CMN3SYZ3.js";
26
+ import {
27
+ KordocError
28
+ } from "./chunk-FSXLSHCW.js";
29
+
30
+ // src/pdf/image-extract.ts
31
+ import { OPS, ImageKind } from "pdfjs-dist/legacy/build/pdf.mjs";
32
+ var IMAGE_RESOLVE_TIMEOUT_MS = 5e3;
33
+ function resolveImgData(page, objId) {
34
+ const store = objId.startsWith("g_") ? page.commonObjs : page.objs;
35
+ return new Promise((resolve) => {
36
+ let done = false;
37
+ const timer = setTimeout(() => {
38
+ if (!done) {
39
+ done = true;
40
+ resolve(null);
41
+ }
42
+ }, IMAGE_RESOLVE_TIMEOUT_MS);
43
+ try {
44
+ store.get(objId, (data) => {
45
+ if (!done) {
46
+ done = true;
47
+ clearTimeout(timer);
48
+ resolve(data ?? null);
49
+ }
50
+ });
51
+ } catch {
52
+ if (!done) {
53
+ done = true;
54
+ clearTimeout(timer);
55
+ resolve(null);
56
+ }
57
+ }
58
+ });
59
+ }
60
+ var MIN_DIM = 8;
61
+ var MAX_PIXELS = 36e6;
62
+ var MAX_IMAGES_PER_DOC = 200;
63
+ var MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024;
64
+ function createPdfImageState() {
65
+ return { imageIndex: 0, totalBytes: 0, seen: /* @__PURE__ */ new Map(), capWarned: false };
66
+ }
67
+ function fnv1a(data) {
68
+ let h = 2166136261;
69
+ for (let i = 0; i < data.length; i++) {
70
+ h ^= data[i];
71
+ h = Math.imul(h, 16777619);
72
+ }
73
+ return h >>> 0;
74
+ }
75
+ function toRgba(img) {
76
+ const { width: w, height: h, kind, data } = img;
77
+ if (!data || !w || !h) return null;
78
+ const rgba = new Uint8Array(w * h * 4);
79
+ if (kind === ImageKind.RGBA_32BPP) {
80
+ if (data.length < w * h * 4) return null;
81
+ rgba.set(data.subarray(0, w * h * 4));
82
+ return rgba;
83
+ }
84
+ if (kind === ImageKind.RGB_24BPP) {
85
+ if (data.length < w * h * 3) return null;
86
+ for (let i = 0, s = 0, d = 0; i < w * h; i++, s += 3, d += 4) {
87
+ rgba[d] = data[s];
88
+ rgba[d + 1] = data[s + 1];
89
+ rgba[d + 2] = data[s + 2];
90
+ rgba[d + 3] = 255;
91
+ }
92
+ return rgba;
93
+ }
94
+ if (kind === ImageKind.GRAYSCALE_1BPP) {
95
+ const stride = w + 7 >> 3;
96
+ if (data.length < stride * h) return null;
97
+ for (let y = 0; y < h; y++) {
98
+ for (let x = 0; x < w; x++) {
99
+ const bit = data[y * stride + (x >> 3)] >> 7 - (x & 7) & 1;
100
+ const v = bit ? 255 : 0;
101
+ const d = (y * w + x) * 4;
102
+ rgba[d] = v;
103
+ rgba[d + 1] = v;
104
+ rgba[d + 2] = v;
105
+ rgba[d + 3] = 255;
106
+ }
107
+ }
108
+ return rgba;
109
+ }
110
+ return null;
111
+ }
112
+ async function extractPageImages(page, fnArray, argsArray, pageNumber, state, warnings) {
113
+ const blocks = [];
114
+ const images = [];
115
+ const pageSeenIds = /* @__PURE__ */ new Set();
116
+ for (let i = 0; i < fnArray.length; i++) {
117
+ const op = fnArray[i];
118
+ let imgData = null;
119
+ let dedupeId = null;
120
+ if (op === OPS.paintImageXObject || op === OPS.paintImageXObjectRepeat) {
121
+ const objId = argsArray[i]?.[0];
122
+ if (typeof objId !== "string" || pageSeenIds.has(objId)) continue;
123
+ pageSeenIds.add(objId);
124
+ dedupeId = objId;
125
+ } else if (op !== OPS.paintInlineImageXObject) {
126
+ continue;
127
+ }
128
+ if (state.imageIndex >= MAX_IMAGES_PER_DOC || state.totalBytes >= MAX_TOTAL_IMAGE_BYTES) {
129
+ if (!state.capWarned) {
130
+ state.capWarned = true;
131
+ warnings.push({ page: pageNumber, message: `\uC774\uBBF8\uC9C0 \uCD94\uCD9C \uC0C1\uD55C \uB3C4\uB2EC (${MAX_IMAGES_PER_DOC}\uAC1C/128MB) \u2014 \uC774\uD6C4 \uC774\uBBF8\uC9C0 \uC0DD\uB7B5`, code: "SKIPPED_IMAGE" });
132
+ }
133
+ continue;
134
+ }
135
+ if (dedupeId) {
136
+ imgData = await resolveImgData(page, dedupeId);
137
+ } else {
138
+ imgData = argsArray[i]?.[0];
139
+ }
140
+ if (!imgData?.data || !imgData.width || !imgData.height) continue;
141
+ const { width: w, height: h } = imgData;
142
+ if (w < MIN_DIM || h < MIN_DIM) continue;
143
+ if (w * h > MAX_PIXELS) {
144
+ warnings.push({ page: pageNumber, message: `\uC774\uBBF8\uC9C0 \uD06C\uAE30 \uCD08\uACFC\uB85C \uCD94\uCD9C \uC0DD\uB7B5 (${w}\xD7${h})`, code: "SKIPPED_IMAGE" });
145
+ continue;
146
+ }
147
+ const hash = `${w}x${h}k${imgData.kind ?? "?"}h${fnv1a(imgData.data)}${dedupeId ? "" : "inline"}`;
148
+ if (state.seen.has(hash)) continue;
149
+ const rgba = toRgba(imgData);
150
+ if (!rgba) continue;
151
+ const png = encodePng(w, h, rgba);
152
+ state.imageIndex++;
153
+ state.totalBytes += png.length;
154
+ const filename = `image_${String(state.imageIndex).padStart(3, "0")}.png`;
155
+ state.seen.set(hash, filename);
156
+ images.push({ filename, data: png, mimeType: "image/png" });
157
+ blocks.push({ type: "image", text: filename, pageNumber });
158
+ }
159
+ return { blocks, images };
160
+ }
161
+ function injectPageImageBlocks(blocks, pageImages) {
162
+ if (pageImages.size === 0) return;
163
+ const lastIndexForPage = /* @__PURE__ */ new Map();
164
+ for (let i = 0; i < blocks.length; i++) {
165
+ const p = blocks[i].pageNumber;
166
+ if (p !== void 0) lastIndexForPage.set(p, i);
167
+ }
168
+ const pageAtIndex = /* @__PURE__ */ new Map();
169
+ for (const [p, last] of lastIndexForPage) pageAtIndex.set(last, p);
170
+ const result = [];
171
+ const injected = /* @__PURE__ */ new Set();
172
+ for (let i = 0; i < blocks.length; i++) {
173
+ result.push(blocks[i]);
174
+ const p = pageAtIndex.get(i);
175
+ if (p !== void 0 && pageImages.has(p)) {
176
+ result.push(...pageImages.get(p));
177
+ injected.add(p);
178
+ }
179
+ }
180
+ for (const p of [...pageImages.keys()].sort((a, b) => a - b)) {
181
+ if (injected.has(p)) continue;
182
+ let at = result.length;
183
+ for (let i = 0; i < result.length; i++) {
184
+ const bp = result[i].pageNumber;
185
+ if (bp !== void 0 && bp > p) {
186
+ at = i;
187
+ break;
188
+ }
189
+ }
190
+ result.splice(at, 0, ...pageImages.get(p));
191
+ }
192
+ blocks.length = 0;
193
+ blocks.push(...result);
194
+ }
195
+
196
+ // src/pdf/quality.ts
197
+ var RARE_JONG = /* @__PURE__ */ new Set([3, 5, 6, 9, 10, 11, 12, 13, 14, 15, 18, 24, 25, 26]);
198
+ var MOJIBAKE_MIN_HANGUL = 30;
199
+ var MOJIBAKE_MAX_NOBATCHIM = 0.15;
200
+ var MOJIBAKE_MIN_RAREBATCHIM = 0.25;
201
+ function computePageQuality(page, text) {
202
+ let total = 0;
203
+ let hangul = 0;
204
+ let hangulNoBatchim = 0;
205
+ let hangulRareBatchim = 0;
206
+ let control = 0;
207
+ let replacement = 0;
208
+ let pua = 0;
209
+ for (let i = 0; i < text.length; i++) {
210
+ const code = text.charCodeAt(i);
211
+ if (code === 32 || code === 9 || code === 10 || code === 13) continue;
212
+ total++;
213
+ if (code < 32 || code === 127 || code >= 128 && code <= 159) {
214
+ control++;
215
+ continue;
216
+ }
217
+ if (code === 65533) {
218
+ replacement++;
219
+ continue;
220
+ }
221
+ if (code >= 44032 && code <= 55203) {
222
+ hangul++;
223
+ const jong = (code - 44032) % 28;
224
+ if (jong === 0) hangulNoBatchim++;
225
+ else if (RARE_JONG.has(jong)) hangulRareBatchim++;
226
+ continue;
227
+ }
228
+ if (code >= 57344 && code <= 63743 || code >= 56192 && code <= 56319) {
229
+ pua++;
230
+ continue;
231
+ }
232
+ }
233
+ const denom = total || 1;
234
+ const puaRatio = pua / denom;
235
+ const controlCharRatio = control / denom;
236
+ const replacementCharRatio = replacement / denom;
237
+ const hangulNoBatchimRatio = hangul > 0 ? hangulNoBatchim / hangul : 0;
238
+ const hangulRareBatchimRatio = hangul > 0 ? hangulRareBatchim / hangul : 0;
239
+ const garbledHangul = hangul >= MOJIBAKE_MIN_HANGUL && hangulNoBatchimRatio < MOJIBAKE_MAX_NOBATCHIM && hangulRareBatchimRatio >= MOJIBAKE_MIN_RAREBATCHIM;
240
+ let needsOcr = false;
241
+ let ocrReason;
242
+ if (total < LOW_TEXT_THRESHOLD) {
243
+ needsOcr = true;
244
+ ocrReason = "low_text";
245
+ } else if (puaRatio >= HIGH_PUA_THRESHOLD) {
246
+ needsOcr = true;
247
+ ocrReason = "high_pua";
248
+ } else if (controlCharRatio >= HIGH_CONTROL_THRESHOLD) {
249
+ needsOcr = true;
250
+ ocrReason = "high_control";
251
+ } else if (replacementCharRatio >= HIGH_REPLACEMENT_THRESHOLD) {
252
+ needsOcr = true;
253
+ ocrReason = "high_replacement";
254
+ } else if (garbledHangul) {
255
+ needsOcr = true;
256
+ ocrReason = "garbled_hangul";
257
+ }
258
+ return {
259
+ page,
260
+ textChars: total,
261
+ hangulRatio: hangul / denom,
262
+ controlCharRatio,
263
+ replacementCharRatio,
264
+ puaRatio,
265
+ hangulNoBatchimRatio,
266
+ hangulRareBatchimRatio,
267
+ needsOcr,
268
+ ocrReason
269
+ };
270
+ }
271
+ var LOW_TEXT_THRESHOLD = 20;
272
+ var HIGH_PUA_THRESHOLD = 0.2;
273
+ var HIGH_CONTROL_THRESHOLD = 0.05;
274
+ var HIGH_REPLACEMENT_THRESHOLD = 0.05;
275
+ var DOC_NEEDS_OCR_PAGE_RATIO = 0.3;
276
+ function stripControlChars(text) {
277
+ return text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F]/g, "");
278
+ }
279
+ function summarizeDocumentQuality(pages) {
280
+ if (pages.length === 0) {
281
+ return {
282
+ totalPages: 0,
283
+ totalTextChars: 0,
284
+ avgHangulRatio: 0,
285
+ avgControlCharRatio: 0,
286
+ avgReplacementCharRatio: 0,
287
+ avgPuaRatio: 0,
288
+ lowTextPageCount: 0,
289
+ highPuaPageCount: 0,
290
+ needsOcr: false,
291
+ ocrCandidatePages: []
292
+ };
293
+ }
294
+ let textChars = 0;
295
+ let hangul = 0;
296
+ let control = 0;
297
+ let replacement = 0;
298
+ let pua = 0;
299
+ let lowText = 0;
300
+ let highPua = 0;
301
+ const ocrCandidatePages = [];
302
+ for (const p of pages) {
303
+ textChars += p.textChars;
304
+ hangul += p.hangulRatio;
305
+ control += p.controlCharRatio;
306
+ replacement += p.replacementCharRatio;
307
+ pua += p.puaRatio;
308
+ if (p.textChars < LOW_TEXT_THRESHOLD) lowText++;
309
+ if (p.puaRatio >= HIGH_PUA_THRESHOLD) highPua++;
310
+ if (p.needsOcr && !p.ocrApplied) ocrCandidatePages.push(p.page);
311
+ }
312
+ const n = pages.length;
313
+ return {
314
+ totalPages: n,
315
+ totalTextChars: textChars,
316
+ avgHangulRatio: hangul / n,
317
+ avgControlCharRatio: control / n,
318
+ avgReplacementCharRatio: replacement / n,
319
+ avgPuaRatio: pua / n,
320
+ lowTextPageCount: lowText,
321
+ highPuaPageCount: highPua,
322
+ needsOcr: ocrCandidatePages.length / n >= DOC_NEEDS_OCR_PAGE_RATIO,
323
+ ocrCandidatePages
324
+ };
325
+ }
326
+
327
+ // src/pdf/text-clean.ts
328
+ function sanitizeBlockControlChars(blocks) {
329
+ for (const b of blocks) {
330
+ if (b.text) b.text = stripControlChars(b.text);
331
+ if (b.table) {
332
+ for (const row of b.table.cells) {
333
+ for (const cell of row) {
334
+ if (cell.text) cell.text = stripControlChars(cell.text);
335
+ }
336
+ }
337
+ }
338
+ if (b.children) sanitizeBlockControlChars(b.children);
339
+ }
340
+ }
341
+ function cleanPdfText(text) {
342
+ return mergeKoreanLines(
343
+ stripControlChars(text).replace(/^\d{1,4}\n/, "").replace(/^[\s]*[-–—]\s*[-–—]?\d+[-–—]?[\s]*[-–—]?[\s]*$/gm, "").replace(/^\s*\d+\s*\/\s*\d+\s*$/gm, "").replace(/\n\d{1,4}\n/g, "\n").replace(/\n\d{1,4}$/, "").replace(/^#{1,6}\s*\d{1,4}\s*$/gm, "")
344
+ ).replace(/^(?!\| ---).*$/gm, (line) => {
345
+ if (/^\s*\${1,2}.+\${1,2}\s*$/.test(line)) return line;
346
+ return collapseEvenSpacing(line);
347
+ }).replace(/([□■◆○●▶ㅇ])\s+([가-힣])\s+([가-힣])/g, "$1 $2$3").replace(/\\~\\~/g, "~~").replace(/~~~~/g, "").replace(/\n{3,}/g, "\n\n").trim();
348
+ }
349
+ function startsWithMarker(line) {
350
+ const t = line.trimStart();
351
+ return /^[가-힣ㄱ-ㅎ][.)]/.test(t) || /^\d+[.)]/.test(t) || /^\([가-힣ㄱ-ㅎ\d]+\)/.test(t) || /^[○●※▶▷◆◇■□★☆\-·]\s/.test(t) || /^제\d+[조항호장절]/.test(t);
352
+ }
353
+ function isStandaloneHeader(line) {
354
+ return /^제\d+[조항호장절](\([^)]*\))?(\s+\S+){0,7}$/.test(line.trim());
355
+ }
356
+ function mergeKoreanLines(text) {
357
+ if (!text) return "";
358
+ const lines = text.split("\n");
359
+ if (lines.length <= 1) return text;
360
+ const result = [lines[0]];
361
+ for (let i = 1; i < lines.length; i++) {
362
+ const prev = result[result.length - 1];
363
+ const curr = lines[i];
364
+ const currTrimmed = curr.trim();
365
+ if (/^#{1,6}\s/.test(prev) || /^#{1,6}\s/.test(curr) || /^\|/.test(currTrimmed) || /^---/.test(currTrimmed)) {
366
+ result.push(curr);
367
+ continue;
368
+ }
369
+ if (/,$/.test(prev.trim()) && currTrimmed.length > 0) {
370
+ result[result.length - 1] = prev + "\n" + curr;
371
+ continue;
372
+ }
373
+ if (/^\(※/.test(currTrimmed)) {
374
+ result[result.length - 1] = prev + " " + currTrimmed;
375
+ continue;
376
+ }
377
+ if (/[가-힣·,\-]$/.test(prev) && /^[가-힣(]/.test(curr) && !startsWithMarker(curr) && !isStandaloneHeader(prev) && !startsWithMarker(prev)) {
378
+ result[result.length - 1] = prev + " " + curr;
379
+ } else {
380
+ result.push(curr);
381
+ }
382
+ }
383
+ return result.join("\n");
384
+ }
385
+
386
+ // src/pdf/formula-ocr.ts
387
+ async function applyFormulaOcr(buffer, blocks, pageFilter, effectivePageCount, warnings, _onProgress) {
388
+ const formulaMod = await import("./formula-SXC6LRRC.js");
389
+ const { FormulaPipeline, ensureFormulaModels } = formulaMod;
390
+ await ensureFormulaModels((p) => {
391
+ if (p.phase === "download" && p.total) {
392
+ const pct = Math.floor(p.downloaded / p.total * 100);
393
+ process.stderr.write(`\r[kordoc-formula] ${p.spec.name} ${pct}% (${formatMb(p.downloaded)}/${formatMb(p.total)})`);
394
+ if (p.downloaded >= p.total) process.stderr.write("\n");
395
+ } else if (p.phase === "verify") {
396
+ process.stderr.write(`[kordoc-formula] ${p.spec.name} SHA-256 \uAC80\uC99D \uC911...
397
+ `);
398
+ } else if (p.phase === "done") {
399
+ process.stderr.write(`[kordoc-formula] ${p.spec.name} \uC900\uBE44 \uC644\uB8CC
400
+ `);
401
+ } else if (p.phase === "skip") {
402
+ }
403
+ });
404
+ const pipeline = await FormulaPipeline.create();
405
+ try {
406
+ const effectiveFilter = pageFilter ?? new Set(Array.from({ length: effectivePageCount }, (_, i) => i + 1));
407
+ const pagesResult = await pipeline.runOnBuffer(buffer, effectiveFilter);
408
+ if (pagesResult.length === 0) return;
409
+ let insertedCount = 0;
410
+ let removedDupCount = 0;
411
+ for (const page of pagesResult) {
412
+ const pageNumber = page.pageNumber;
413
+ const pdfHeight = page.pdfHeight;
414
+ const scaleX = page.renderedWidth > 0 ? page.pdfWidth / page.renderedWidth : 0.5;
415
+ const scaleY = page.renderedHeight > 0 ? page.pdfHeight / page.renderedHeight : 0.5;
416
+ const candidates = [];
417
+ for (const r of page.regions) {
418
+ if (!r.latex || !r.latex.trim()) continue;
419
+ const wrapped = r.kind === "display" ? `$$${r.latex}$$` : `$${r.latex}$`;
420
+ const x1 = r.bbox.x1 * scaleX;
421
+ const x2 = r.bbox.x2 * scaleX;
422
+ const yTop = pdfHeight - r.bbox.y1 * scaleY;
423
+ const yBottom = pdfHeight - r.bbox.y2 * scaleY;
424
+ const centerY = (yTop + yBottom) / 2;
425
+ const width = x2 - x1;
426
+ const height = yTop - yBottom;
427
+ candidates.push({
428
+ block: {
429
+ type: "paragraph",
430
+ text: wrapped,
431
+ pageNumber,
432
+ bbox: { page: pageNumber, x: x1, y: yBottom, width, height }
433
+ },
434
+ pdfBbox: { x1, x2, yTop, yBottom },
435
+ centerY
436
+ });
437
+ }
438
+ if (candidates.length === 0) continue;
439
+ const OVERLAP_THRESHOLD = 0.6;
440
+ const indicesToRemove = /* @__PURE__ */ new Set();
441
+ for (let i = 0; i < blocks.length; i++) {
442
+ const b = blocks[i];
443
+ if (b.pageNumber !== pageNumber) continue;
444
+ if (b.type === "table") continue;
445
+ if (!b.bbox || b.bbox.width <= 0 || b.bbox.height <= 0) continue;
446
+ const blockArea = b.bbox.width * b.bbox.height;
447
+ if (blockArea <= 0) continue;
448
+ for (const c of candidates) {
449
+ const ox1 = Math.max(b.bbox.x, c.pdfBbox.x1);
450
+ const ox2 = Math.min(b.bbox.x + b.bbox.width, c.pdfBbox.x2);
451
+ const oy1 = Math.max(b.bbox.y, c.pdfBbox.yBottom);
452
+ const oy2 = Math.min(b.bbox.y + b.bbox.height, c.pdfBbox.yTop);
453
+ const interArea = Math.max(0, ox2 - ox1) * Math.max(0, oy2 - oy1);
454
+ if (interArea / blockArea >= OVERLAP_THRESHOLD) {
455
+ indicesToRemove.add(i);
456
+ break;
457
+ }
458
+ }
459
+ }
460
+ if (indicesToRemove.size > 0) {
461
+ const sorted = [...indicesToRemove].sort((a, b) => b - a);
462
+ for (const idx of sorted) blocks.splice(idx, 1);
463
+ removedDupCount += indicesToRemove.size;
464
+ }
465
+ candidates.sort((a, b) => b.centerY - a.centerY);
466
+ for (const c of candidates) {
467
+ let insertIdx = -1;
468
+ let pageFirstIdx = -1;
469
+ let pageLastIdx = -1;
470
+ for (let i = 0; i < blocks.length; i++) {
471
+ const b = blocks[i];
472
+ if (b.pageNumber !== pageNumber) continue;
473
+ if (pageFirstIdx === -1) pageFirstIdx = i;
474
+ pageLastIdx = i;
475
+ if (!b.bbox) continue;
476
+ const blockCenter = b.bbox.y + b.bbox.height / 2;
477
+ if (blockCenter < c.centerY) {
478
+ insertIdx = i;
479
+ break;
480
+ }
481
+ }
482
+ if (insertIdx !== -1) {
483
+ blocks.splice(insertIdx, 0, c.block);
484
+ } else if (pageLastIdx !== -1) {
485
+ blocks.splice(pageLastIdx + 1, 0, c.block);
486
+ } else {
487
+ blocks.push(c.block);
488
+ }
489
+ insertedCount++;
490
+ }
491
+ }
492
+ if (insertedCount > 0 || removedDupCount > 0) {
493
+ process.stderr.write(
494
+ `[kordoc-formula] ${insertedCount}\uAC1C \uC218\uC2DD \uC0BD\uC785, ${removedDupCount}\uAC1C \uC911\uBCF5 block \uC81C\uAC70 (${pagesResult.length}\uAC1C \uD398\uC774\uC9C0)
495
+ `
496
+ );
497
+ }
498
+ } finally {
499
+ await pipeline.destroy().catch(() => {
500
+ });
501
+ }
502
+ }
503
+ function formatMb(bytes) {
504
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
505
+ }
506
+
507
+ // src/pdf/polyfill.ts
508
+ import * as pdfjsWorker from "pdfjs-dist/legacy/build/pdf.worker.mjs";
509
+ var g = globalThis;
510
+ if (typeof g.DOMMatrix === "undefined") {
511
+ g.DOMMatrix = class DOMMatrix {
512
+ m = [1, 0, 0, 1, 0, 0];
513
+ constructor(init) {
514
+ if (init) this.m = init;
515
+ }
516
+ };
517
+ }
518
+ if (typeof g.Path2D === "undefined") {
519
+ g.Path2D = class Path2D {
520
+ };
521
+ }
522
+ g.pdfjsWorker = pdfjsWorker;
523
+
524
+ // src/pdf/parser.ts
525
+ import { getDocument, GlobalWorkerOptions } from "pdfjs-dist/legacy/build/pdf.mjs";
526
+ import { createRequire } from "module";
527
+ import { dirname, join } from "path";
528
+ GlobalWorkerOptions.workerSrc = "";
529
+ var MAX_PAGES = 5e3;
530
+ var MAX_TOTAL_TEXT = 100 * 1024 * 1024;
531
+ var PDF_LOAD_TIMEOUT_MS = 3e4;
532
+ var pdfjsAssets = {};
533
+ try {
534
+ const _require = createRequire(import.meta.url);
535
+ const pkgDir = dirname(_require.resolve("pdfjs-dist/package.json"));
536
+ pdfjsAssets.cMapUrl = join(pkgDir, "cmaps") + "/";
537
+ pdfjsAssets.cMapPacked = true;
538
+ pdfjsAssets.standardFontDataUrl = join(pkgDir, "standard_fonts") + "/";
539
+ } catch {
540
+ }
541
+ async function loadPdfWithTimeout(buffer) {
542
+ const loadingTask = getDocument({
543
+ data: new Uint8Array(buffer),
544
+ useSystemFonts: true,
545
+ disableFontFace: true,
546
+ isEvalSupported: false,
547
+ ...pdfjsAssets
548
+ });
549
+ let timer;
550
+ try {
551
+ return await Promise.race([
552
+ loadingTask.promise,
553
+ new Promise((_, reject) => {
554
+ timer = setTimeout(() => {
555
+ loadingTask.destroy();
556
+ reject(new KordocError("PDF \uB85C\uB529 \uD0C0\uC784\uC544\uC6C3 (30\uCD08 \uCD08\uACFC)"));
557
+ }, PDF_LOAD_TIMEOUT_MS);
558
+ })
559
+ ]);
560
+ } finally {
561
+ if (timer !== void 0) clearTimeout(timer);
562
+ }
563
+ }
564
+ async function parsePdfDocument(buffer, options) {
565
+ const formulaBuffer = options?.formulaOcr ? buffer.slice(0) : null;
566
+ const ocrBuffer = options?.ocr ? buffer.slice(0) : null;
567
+ const doc = await loadPdfWithTimeout(buffer);
568
+ try {
569
+ const pageCount = doc.numPages;
570
+ if (pageCount === 0) throw new KordocError("PDF\uC5D0 \uD398\uC774\uC9C0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
571
+ const metadata = { pageCount };
572
+ await extractPdfMetadata(doc, metadata);
573
+ const blocks = [];
574
+ const warnings = [];
575
+ const pageQuality = [];
576
+ let totalChars = 0;
577
+ let totalTextBytes = 0;
578
+ const effectivePageCount = Math.min(pageCount, MAX_PAGES);
579
+ const pageFilter = options?.pages ? parsePageRange(options.pages, effectivePageCount) : null;
580
+ const totalTarget = pageFilter ? pageFilter.size : effectivePageCount;
581
+ const fontSizeFreq = /* @__PURE__ */ new Map();
582
+ const pageHeights = /* @__PURE__ */ new Map();
583
+ const pagesWithLargeImage = /* @__PURE__ */ new Set();
584
+ const skippedImagePages = /* @__PURE__ */ new Map();
585
+ const imageState = createPdfImageState();
586
+ const extractedImages = [];
587
+ const pageImageBlocks = /* @__PURE__ */ new Map();
588
+ let parsedPages = 0;
589
+ for (let i = 1; i <= effectivePageCount; i++) {
590
+ if (pageFilter && !pageFilter.has(i)) continue;
591
+ try {
592
+ const page = await doc.getPage(i);
593
+ const tc = await page.getTextContent();
594
+ const viewport = page.getViewport({ scale: 1 });
595
+ pageHeights.set(i, viewport.height);
596
+ const rawItems = tc.items;
597
+ const items = normalizeItems(rawItems);
598
+ const { visible, hiddenCount } = filterHiddenText(items, viewport.width, viewport.height);
599
+ if (hiddenCount > 0) {
600
+ warnings.push({ page: i, message: `${hiddenCount}\uAC1C \uC228\uACA8\uC9C4 \uD14D\uC2A4\uD2B8 \uC694\uC18C \uD544\uD130\uB9C1\uB428`, code: "HIDDEN_TEXT_FILTERED" });
601
+ }
602
+ for (const item of visible) {
603
+ if (item.fontSize > 0) fontSizeFreq.set(item.fontSize, (fontSizeFreq.get(item.fontSize) || 0) + 1);
604
+ }
605
+ const opList = await page.getOperatorList();
606
+ const pageArea = viewport.width * viewport.height;
607
+ if (pageArea > 0) {
608
+ const imageRegions = extractImageRegions(opList.fnArray, opList.argsArray);
609
+ let uncovered = 0;
610
+ for (const r of imageRegions) {
611
+ const area = (r.x2 - r.x1) * (r.y2 - r.y1);
612
+ if (area < pageArea * 0.05) continue;
613
+ pagesWithLargeImage.add(i);
614
+ const hasText = visible.some((it) => {
615
+ const cx = it.x + it.w / 2;
616
+ const cy = it.y + (it.h || it.fontSize) / 2;
617
+ return cx >= r.x1 && cx <= r.x2 && cy >= r.y1 && cy <= r.y2;
618
+ });
619
+ if (!hasText) uncovered++;
620
+ }
621
+ if (uncovered > 0) skippedImagePages.set(i, uncovered);
622
+ }
623
+ const pageBlocks = extractPageBlocksWithLines(visible, i, opList, viewport.width, viewport.height);
624
+ for (const b of pageBlocks) blocks.push(b);
625
+ try {
626
+ const { blocks: imgBlocks, images: pageImages } = await extractPageImages(page, opList.fnArray, opList.argsArray, i, imageState, warnings);
627
+ if (imgBlocks.length > 0) pageImageBlocks.set(i, imgBlocks);
628
+ extractedImages.push(...pageImages);
629
+ } catch {
630
+ }
631
+ let pageText = "";
632
+ for (const b of pageBlocks) {
633
+ let t = b.text || "";
634
+ if (b.type === "table" && b.table) {
635
+ const cellText = b.table.cells.map((row) => row.map((c) => c.text).join(" ")).join("\n");
636
+ t = t ? t + "\n" + cellText : cellText;
637
+ }
638
+ totalChars += t.replace(/\s/g, "").length;
639
+ totalTextBytes += t.length * 2;
640
+ pageText += pageText ? "\n" + t : t;
641
+ }
642
+ pageQuality.push(computePageQuality(i, pageText));
643
+ if (totalTextBytes > MAX_TOTAL_TEXT) throw new KordocError("\uD14D\uC2A4\uD2B8 \uCD94\uCD9C \uD06C\uAE30 \uCD08\uACFC");
644
+ parsedPages++;
645
+ options?.onProgress?.(parsedPages, totalTarget);
646
+ } catch (pageErr) {
647
+ if (pageErr instanceof KordocError) throw pageErr;
648
+ warnings.push({ page: i, message: `\uD398\uC774\uC9C0 ${i} \uD30C\uC2F1 \uC2E4\uD328: ${pageErr instanceof Error ? pageErr.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"}`, code: "PARTIAL_PARSE" });
649
+ }
650
+ }
651
+ const parsedPageCount = parsedPages || (pageFilter ? pageFilter.size : effectivePageCount);
652
+ const isImageBased = totalChars / Math.max(parsedPageCount, 1) < 10;
653
+ const ocrDone = /* @__PURE__ */ new Set();
654
+ if (options?.ocr && ocrBuffer) {
655
+ const inScope = (p) => !pageFilter || pageFilter.has(p);
656
+ const targets = /* @__PURE__ */ new Set();
657
+ if (options.ocr === "force" || isImageBased) {
658
+ for (let i = 1; i <= effectivePageCount; i++) if (inScope(i)) targets.add(i);
659
+ } else {
660
+ for (const pq of pageQuality) {
661
+ if (!pq.needsOcr) continue;
662
+ if (pq.ocrReason === "low_text" && !pagesWithLargeImage.has(pq.page)) continue;
663
+ targets.add(pq.page);
664
+ }
665
+ }
666
+ if (targets.size > 0) {
667
+ try {
668
+ const { runPdfOcr } = await import("./pdf-ocr-VYE4DHD4.js");
669
+ const mode = typeof options.ocr === "function" ? options.ocr : "builtin";
670
+ const ocrPageBlocks = await runPdfOcr(ocrBuffer, targets, mode, warnings, options.onProgress);
671
+ if (ocrPageBlocks.size > 0) {
672
+ for (const p of ocrPageBlocks.keys()) ocrDone.add(p);
673
+ const merged = blocks.filter((b) => !(b.pageNumber && ocrDone.has(b.pageNumber)));
674
+ for (const obs of ocrPageBlocks.values()) merged.push(...obs);
675
+ merged.sort((a, b) => (a.pageNumber ?? 0) - (b.pageNumber ?? 0));
676
+ blocks.length = 0;
677
+ blocks.push(...merged);
678
+ for (const pq of pageQuality) if (ocrDone.has(pq.page)) pq.ocrApplied = true;
679
+ warnings.push({
680
+ message: `${ocrDone.size}\uAC1C \uD398\uC774\uC9C0\uC5D0 OCR \uC801\uC6A9 (${mode === "builtin" ? "\uB0B4\uC7A5 PP-OCRv5" : "\uC0AC\uC6A9\uC790 \uD504\uB85C\uBC14\uC774\uB354"})`,
681
+ code: "OCR_APPLIED"
682
+ });
683
+ }
684
+ } catch (e) {
685
+ warnings.push({
686
+ message: `OCR \uC2E4\uD589 \uBD88\uAC00: ${e instanceof Error ? e.message : String(e)}`,
687
+ code: "OCR_FAILED"
688
+ });
689
+ }
690
+ }
691
+ }
692
+ if (isImageBased && ocrDone.size === 0) {
693
+ warnings.push({
694
+ message: `\uC774\uBBF8\uC9C0 \uAE30\uBC18 PDF (${pageCount}\uD398\uC774\uC9C0, \uD14D\uC2A4\uD2B8 ${totalChars}\uC790) \u2014 \uD14D\uC2A4\uD2B8 \uB808\uC774\uC5B4\uAC00 \uC5C6\uC5B4 OCR\uC774 \uD544\uC694\uD569\uB2C8\uB2E4`,
695
+ code: "NEEDS_OCR"
696
+ });
697
+ }
698
+ if (!isImageBased) {
699
+ const OCR_REASON_MESSAGES = {
700
+ low_text: "\uD14D\uC2A4\uD2B8\uAC00 \uAC70\uC758 \uC5C6\uB294 \uD398\uC774\uC9C0 (\uC2A4\uCE94/\uC774\uBBF8\uC9C0 \uCD94\uC815)",
701
+ high_pua: "\uAE00\uAF34 \uB9E4\uD551 \uC2E4\uD328 (PUA \uBE44\uC728 \uB192\uC74C) \u2014 \uCD94\uCD9C \uD14D\uC2A4\uD2B8 \uC2E0\uB8B0 \uBD88\uAC00",
702
+ high_control: "\uC81C\uC5B4\uBB38\uC790 \uBE44\uC728 \uB192\uC74C \u2014 \uCD94\uCD9C \uD14D\uC2A4\uD2B8 \uC2E0\uB8B0 \uBD88\uAC00",
703
+ high_replacement: "\uB300\uCCB4\uBB38\uC790(U+FFFD) \uBE44\uC728 \uB192\uC74C \u2014 \uCD94\uCD9C \uD14D\uC2A4\uD2B8 \uC2E0\uB8B0 \uBD88\uAC00",
704
+ garbled_hangul: "\uAE00\uAF34 \uB9E4\uD551 \uC2E4\uD328 (\uD55C\uAE00 \uC790\uC18C \uBD84\uD3EC \uC774\uC0C1) \u2014 \uCD94\uCD9C \uD14D\uC2A4\uD2B8\uAC00 \uAE68\uC84C\uC744 \uC218 \uC788\uC74C"
705
+ };
706
+ for (const pq of pageQuality) {
707
+ if (!pq.needsOcr || !pq.ocrReason || pq.ocrApplied) continue;
708
+ if (pq.ocrReason === "low_text" && !pagesWithLargeImage.has(pq.page)) continue;
709
+ warnings.push({ page: pq.page, message: `${OCR_REASON_MESSAGES[pq.ocrReason]} \u2014 OCR \uAC80\uD1A0 \uD544\uC694`, code: "NEEDS_OCR" });
710
+ }
711
+ }
712
+ if (!isImageBased) {
713
+ for (const [page, count] of [...skippedImagePages.entries()].sort((a, b) => a[0] - b[0])) {
714
+ warnings.push({ page, message: `${count}\uAC1C \uC774\uBBF8\uC9C0 \uC601\uC5ED\uC5D0 \uCD94\uCD9C \uAC00\uB2A5\uD55C \uD14D\uC2A4\uD2B8 \uC5C6\uC74C (\uADF8\uB9BC/\uCC28\uD2B8/\uB3C4\uC7A5 \uB0B4\uC6A9 \uB204\uB77D \uAC00\uB2A5)`, code: "SKIPPED_IMAGE" });
715
+ }
716
+ }
717
+ if (options?.removeHeaderFooter !== false && parsedPageCount >= 3) {
718
+ const removed = removeHeaderFooterBlocks(blocks, pageHeights, warnings);
719
+ for (let ri = removed.length - 1; ri >= 0; ri--) {
720
+ blocks.splice(removed[ri], 1);
721
+ }
722
+ }
723
+ mergeCrossPageTables(blocks);
724
+ injectPageImageBlocks(blocks, pageImageBlocks);
725
+ if (options?.formulaOcr && formulaBuffer) {
726
+ try {
727
+ await applyFormulaOcr(formulaBuffer, blocks, pageFilter, effectivePageCount, warnings, options.onProgress);
728
+ } catch (e) {
729
+ warnings.push({
730
+ message: `\uC218\uC2DD OCR \uC2E4\uD328: ${e instanceof Error ? e.message : String(e)}`,
731
+ code: "PARTIAL_PARSE"
732
+ });
733
+ }
734
+ }
735
+ const medianFontSize = computeMedianFontSizeFromFreq(fontSizeFreq);
736
+ if (medianFontSize > 0) {
737
+ detectHeadings(blocks, medianFontSize);
738
+ }
739
+ detectMarkerHeadings(blocks);
740
+ detectTableCaptions(blocks);
741
+ detectKoreanListBlocks(blocks);
742
+ const outline = blocks.filter((b) => b.type === "heading" && b.level && b.text).map((b) => ({ level: b.level, text: b.text, pageNumber: b.pageNumber }));
743
+ sanitizeBlockControlChars(blocks);
744
+ let markdown = cleanPdfText(blocksToMarkdown(blocks));
745
+ return {
746
+ markdown,
747
+ blocks,
748
+ metadata,
749
+ outline: outline.length > 0 ? outline : void 0,
750
+ warnings: warnings.length > 0 ? warnings : void 0,
751
+ isImageBased: isImageBased || void 0,
752
+ pageQuality,
753
+ qualitySummary: summarizeDocumentQuality(pageQuality),
754
+ images: extractedImages.length > 0 ? extractedImages : void 0
755
+ };
756
+ } finally {
757
+ await doc.destroy().catch(() => {
758
+ });
759
+ }
760
+ }
761
+ async function extractPdfMetadata(doc, metadata) {
762
+ try {
763
+ const result = await doc.getMetadata();
764
+ if (!result?.info) return;
765
+ const info = result.info;
766
+ if (typeof info.Title === "string" && info.Title.trim()) metadata.title = info.Title.trim();
767
+ if (typeof info.Author === "string" && info.Author.trim()) metadata.author = info.Author.trim();
768
+ if (typeof info.Creator === "string" && info.Creator.trim()) metadata.creator = info.Creator.trim();
769
+ if (typeof info.Subject === "string" && info.Subject.trim()) metadata.description = info.Subject.trim();
770
+ if (typeof info.Keywords === "string" && info.Keywords.trim()) {
771
+ metadata.keywords = info.Keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean);
772
+ }
773
+ if (typeof info.CreationDate === "string") metadata.createdAt = parsePdfDate(info.CreationDate);
774
+ if (typeof info.ModDate === "string") metadata.modifiedAt = parsePdfDate(info.ModDate);
775
+ } catch {
776
+ }
777
+ }
778
+ function parsePdfDate(dateStr) {
779
+ const m = dateStr.match(/D:(\d{4})(\d{2})?(\d{2})?(\d{2})?(\d{2})?(\d{2})?/);
780
+ if (!m) return void 0;
781
+ const [, year, month = "01", day = "01", hour = "00", min = "00", sec = "00"] = m;
782
+ return `${year}-${month}-${day}T${hour}:${min}:${sec}`;
783
+ }
784
+ async function extractPdfMetadataOnly(buffer) {
785
+ const doc = await loadPdfWithTimeout(buffer);
786
+ try {
787
+ const metadata = { pageCount: doc.numPages };
788
+ await extractPdfMetadata(doc, metadata);
789
+ return metadata;
790
+ } finally {
791
+ await doc.destroy().catch(() => {
792
+ });
793
+ }
794
+ }
795
+ export {
796
+ cleanPdfText,
797
+ detectKoreanListBlocks,
798
+ detectTableCaptions,
799
+ extractPdfMetadataOnly,
800
+ mergeCrossPageTables,
801
+ parsePdfDocument,
802
+ removeHeaderFooterBlocks
803
+ };
804
+ //# sourceMappingURL=parser-RBZHA6SY.js.map