@clazic/kordoc 2.5.2 → 2.6.1
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/README.md +29 -1
- package/dist/{chunk-5CILZHRW.js → chunk-4X5JCZFZ.js} +2 -2
- package/dist/{chunk-25ZYYLVP.js → chunk-BZPZXI66.js} +441 -6
- package/dist/chunk-BZPZXI66.js.map +1 -0
- package/dist/cli.js +87 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +649 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -2
- package/dist/index.d.ts +127 -2
- package/dist/index.js +639 -150
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +43 -2
- package/dist/mcp.js.map +1 -1
- package/dist/{utils-H2BL5GNR.js → utils-56QT5C33.js} +2 -2
- package/dist/{watch-D6ODQLPJ.js → watch-HRNMJWSE.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-25ZYYLVP.js.map +0 -1
- /package/dist/{chunk-5CILZHRW.js.map → chunk-4X5JCZFZ.js.map} +0 -0
- /package/dist/{utils-H2BL5GNR.js.map → utils-56QT5C33.js.map} +0 -0
- /package/dist/{watch-D6ODQLPJ.js.map → watch-HRNMJWSE.js.map} +0 -0
package/README.md
CHANGED
|
@@ -126,6 +126,25 @@ if (result.success) {
|
|
|
126
126
|
}
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### PDF 변환 (HWP/HWPX → PDF)
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { convertToPdf } from "@clazic/kordoc"
|
|
133
|
+
import { readFileSync, writeFileSync } from "fs"
|
|
134
|
+
|
|
135
|
+
const buffer = readFileSync("사업계획서.hwp")
|
|
136
|
+
const result = await convertToPdf(buffer)
|
|
137
|
+
|
|
138
|
+
if (result.success) {
|
|
139
|
+
writeFileSync("사업계획서.pdf", result.pdf)
|
|
140
|
+
console.log(`변환 완료: ${result.sourceFormat} → PDF`)
|
|
141
|
+
} else {
|
|
142
|
+
console.error(`변환 실패: ${result.error}`)
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**요구사항**: 시스템에 [LibreOffice](https://www.libreoffice.org/)가 설치되어 있어야 합니다.
|
|
147
|
+
|
|
129
148
|
### 문서 비교 (신구대조표)
|
|
130
149
|
|
|
131
150
|
```typescript
|
|
@@ -272,6 +291,11 @@ kordoc convert 보고서.md -o 최종보고서.hwpx # 출력 경로 지정
|
|
|
272
291
|
kordoc convert 보고서.md --image-dir ./이미지 # 이미지 폴더 지정
|
|
273
292
|
kordoc convert 보고서.md --template 기본.hwpx # HWPX 템플릿 적용
|
|
274
293
|
|
|
294
|
+
# HWP/HWPX → PDF 변환
|
|
295
|
+
kordoc convert-pdf 사업계획서.hwp # → 사업계획서.pdf
|
|
296
|
+
kordoc convert-pdf 보고서.hwpx -o 결과.pdf # 출력 경로 지정
|
|
297
|
+
kordoc convert-pdf 문서.hwp --pages 1-3 # 페이지 범위
|
|
298
|
+
|
|
275
299
|
# 폴더 감시 모드
|
|
276
300
|
kordoc watch ./수신함 -d ./변환결과 # 폴더 감시 모드
|
|
277
301
|
kordoc watch ./문서 --webhook https://api/hook # 웹훅 알림
|
|
@@ -381,12 +405,13 @@ HWP 5.x를 제외한 모든 포맷 지원. 설치 없이 바로 연결 가능합
|
|
|
381
405
|
}
|
|
382
406
|
```
|
|
383
407
|
|
|
384
|
-
**
|
|
408
|
+
**9개 도구:**
|
|
385
409
|
|
|
386
410
|
| 도구 | 설명 |
|
|
387
411
|
|------|------|
|
|
388
412
|
| `parse_document` | HWP/HWPX/PDF/XLSX/DOCX → 마크다운 (메타데이터·이미지 포함, `image_dir` 지원) |
|
|
389
413
|
| `convert_document` | Markdown → HWPX 또는 XLSX 변환 (이미지 폴더·템플릿 지원) |
|
|
414
|
+
| `convert_to_pdf` | HWP/HWPX → PDF 변환 (페이지 범위·타임아웃 지원) |
|
|
390
415
|
| `detect_format` | 매직 바이트로 포맷 감지 |
|
|
391
416
|
| `parse_metadata` | 메타데이터만 빠르게 추출 |
|
|
392
417
|
| `parse_pages` | 특정 페이지 범위만 파싱 |
|
|
@@ -406,6 +431,7 @@ HWP 5.x를 제외한 모든 포맷 지원. 설치 없이 바로 연결 가능합
|
|
|
406
431
|
| `parsePdf(buffer, options?)` | PDF 전용 |
|
|
407
432
|
| `parseXlsx(buffer, options?)` | XLSX 전용 |
|
|
408
433
|
| `parseDocx(buffer, options?)` | DOCX 전용 |
|
|
434
|
+
| `convertToPdf(input, options?)` | HWP/HWPX → PDF 변환 |
|
|
409
435
|
| `detectFormat(buffer)` | `"hwpx" \| "hwp" \| "pdf" \| "xlsx" \| "docx" \| "unknown"` |
|
|
410
436
|
|
|
411
437
|
### 고급 함수
|
|
@@ -429,6 +455,8 @@ import type {
|
|
|
429
455
|
FormField, FormResult,
|
|
430
456
|
OcrProvider, WatchOptions,
|
|
431
457
|
MarkdownToXlsxOptions,
|
|
458
|
+
// PDF 변환 타입
|
|
459
|
+
ConvertToPdfOptions, ConvertToPdfResult,
|
|
432
460
|
} from "@clazic/kordoc"
|
|
433
461
|
```
|
|
434
462
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/utils.ts
|
|
4
|
-
var VERSION = true ? "2.
|
|
4
|
+
var VERSION = true ? "2.6.0" : "0.0.0-dev";
|
|
5
5
|
function toArrayBuffer(buf) {
|
|
6
6
|
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
|
|
7
7
|
return buf.buffer;
|
|
@@ -105,4 +105,4 @@ export {
|
|
|
105
105
|
classifyError,
|
|
106
106
|
normalizeKordocError
|
|
107
107
|
};
|
|
108
|
-
//# sourceMappingURL=chunk-
|
|
108
|
+
//# sourceMappingURL=chunk-4X5JCZFZ.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
precheckZipSize,
|
|
8
8
|
sanitizeHref,
|
|
9
9
|
toArrayBuffer
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4X5JCZFZ.js";
|
|
11
11
|
import {
|
|
12
12
|
parsePageRange
|
|
13
13
|
} from "./chunk-MOL7MDBG.js";
|
|
@@ -6686,7 +6686,7 @@ function mergeKoreanLines(text) {
|
|
|
6686
6686
|
}
|
|
6687
6687
|
|
|
6688
6688
|
// src/index.ts
|
|
6689
|
-
import { readFile } from "fs/promises";
|
|
6689
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
6690
6690
|
|
|
6691
6691
|
// src/xlsx/parser.ts
|
|
6692
6692
|
import JSZip3 from "jszip";
|
|
@@ -9821,10 +9821,444 @@ async function markdownToXlsx(markdown, options) {
|
|
|
9821
9821
|
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
9822
9822
|
}
|
|
9823
9823
|
|
|
9824
|
-
// src/
|
|
9825
|
-
import {
|
|
9824
|
+
// src/convert/index.ts
|
|
9825
|
+
import { readFile } from "fs/promises";
|
|
9826
|
+
|
|
9827
|
+
// src/convert/libreoffice.ts
|
|
9826
9828
|
import libre from "libreoffice-convert";
|
|
9829
|
+
|
|
9830
|
+
// src/convert/error.ts
|
|
9831
|
+
var ConvertError = class extends Error {
|
|
9832
|
+
constructor(code, message) {
|
|
9833
|
+
super(message);
|
|
9834
|
+
this.code = code;
|
|
9835
|
+
this.name = "ConvertError";
|
|
9836
|
+
}
|
|
9837
|
+
};
|
|
9838
|
+
|
|
9839
|
+
// src/convert/installer.ts
|
|
9840
|
+
import { homedir } from "os";
|
|
9841
|
+
import { join as join2, delimiter } from "path";
|
|
9842
|
+
import { mkdir, access, symlink, rm } from "fs/promises";
|
|
9843
|
+
import { createWriteStream } from "fs";
|
|
9844
|
+
import { spawn } from "child_process";
|
|
9845
|
+
var CACHE_DIR = join2(homedir(), ".cache", "kordoc", "libreoffice");
|
|
9846
|
+
var VERSION_FILE = join2(CACHE_DIR, "version");
|
|
9847
|
+
var PACKAGES = {
|
|
9848
|
+
darwin: {
|
|
9849
|
+
url: "https://download.documentfoundation.org/libreoffice/stable/24.8.4/mac/x86_64/LibreOffice_24.8.4_MacOS_x86-64.dmg",
|
|
9850
|
+
binPath: "LibreOffice.app/Contents/MacOS/soffice",
|
|
9851
|
+
sizeMb: 300
|
|
9852
|
+
},
|
|
9853
|
+
linux: {
|
|
9854
|
+
url: "https://download.documentfoundation.org/libreoffice/stable/24.8.4/deb/x86_64/LibreOffice_24.8.4_Linux_x86-64_deb.tar.gz",
|
|
9855
|
+
binPath: "opt/libreoffice24.8/program/soffice",
|
|
9856
|
+
sizeMb: 200
|
|
9857
|
+
},
|
|
9858
|
+
win32: {
|
|
9859
|
+
url: "https://download.documentfoundation.org/libreoffice/stable/24.8.4/win/x86_64/LibreOffice_24.8.4_Win_x86-64.msi",
|
|
9860
|
+
binPath: "LibreOffice/program/soffice.exe",
|
|
9861
|
+
sizeMb: 350
|
|
9862
|
+
}
|
|
9863
|
+
};
|
|
9864
|
+
async function findInPath() {
|
|
9865
|
+
try {
|
|
9866
|
+
const { runCommand } = await import("./utils-56QT5C33.js");
|
|
9867
|
+
await runCommand("soffice", ["--version"]);
|
|
9868
|
+
return "soffice";
|
|
9869
|
+
} catch {
|
|
9870
|
+
return null;
|
|
9871
|
+
}
|
|
9872
|
+
}
|
|
9873
|
+
async function findInCache() {
|
|
9874
|
+
const cachedBin = join2(CACHE_DIR, "bin", "soffice");
|
|
9875
|
+
try {
|
|
9876
|
+
await access(cachedBin);
|
|
9877
|
+
return cachedBin;
|
|
9878
|
+
} catch {
|
|
9879
|
+
return null;
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
async function downloadWithProgress(url, dest, totalBytes, onProgress) {
|
|
9883
|
+
const response = await fetch(url);
|
|
9884
|
+
if (!response.body) throw new Error("\uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328: response body \uC5C6\uC74C");
|
|
9885
|
+
const file = createWriteStream(dest);
|
|
9886
|
+
const reader = response.body.getReader();
|
|
9887
|
+
let downloaded = 0;
|
|
9888
|
+
try {
|
|
9889
|
+
while (true) {
|
|
9890
|
+
const { done, value } = await reader.read();
|
|
9891
|
+
if (done) break;
|
|
9892
|
+
file.write(value);
|
|
9893
|
+
downloaded += value.length;
|
|
9894
|
+
onProgress?.(downloaded, totalBytes);
|
|
9895
|
+
}
|
|
9896
|
+
} finally {
|
|
9897
|
+
file.end();
|
|
9898
|
+
reader.releaseLock();
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
async function installForPlatform(pkg, onProgress) {
|
|
9902
|
+
const platform = process.platform;
|
|
9903
|
+
await mkdir(CACHE_DIR, { recursive: true });
|
|
9904
|
+
const downloadPath = join2(CACHE_DIR, `download-${Date.now()}`);
|
|
9905
|
+
await downloadWithProgress(pkg.url, downloadPath, pkg.sizeMb * 1024 * 1024, onProgress);
|
|
9906
|
+
try {
|
|
9907
|
+
if (platform === "darwin") {
|
|
9908
|
+
return await installMacOS(pkg, downloadPath);
|
|
9909
|
+
} else if (platform === "linux") {
|
|
9910
|
+
return await installLinux(pkg, downloadPath);
|
|
9911
|
+
} else if (platform === "win32") {
|
|
9912
|
+
return await installWindows(pkg, downloadPath);
|
|
9913
|
+
}
|
|
9914
|
+
} catch (err) {
|
|
9915
|
+
await rm(downloadPath, { force: true });
|
|
9916
|
+
throw err;
|
|
9917
|
+
}
|
|
9918
|
+
throw new ConvertError("UNSUPPORTED_PLATFORM", `${platform}\uC740 \uC790\uB3D9 \uC124\uCE58\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`);
|
|
9919
|
+
}
|
|
9920
|
+
async function installMacOS(pkg, downloadPath) {
|
|
9921
|
+
const mountPoint = `/Volumes/LibreOffice_${Date.now()}`;
|
|
9922
|
+
await new Promise((resolve2, reject) => {
|
|
9923
|
+
const child = spawn("hdiutil", ["attach", "-nobrowse", "-mountpoint", mountPoint, downloadPath]);
|
|
9924
|
+
child.on("close", (code) => code === 0 ? resolve2() : reject(new Error("dmg \uB9C8\uC6B4\uD2B8 \uC2E4\uD328")));
|
|
9925
|
+
});
|
|
9926
|
+
try {
|
|
9927
|
+
const appSource = join2(mountPoint, "LibreOffice.app");
|
|
9928
|
+
const appDest = join2(CACHE_DIR, "LibreOffice.app");
|
|
9929
|
+
await new Promise((resolve2, reject) => {
|
|
9930
|
+
const child = spawn("cp", ["-R", appSource, appDest]);
|
|
9931
|
+
child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(".app \uBCF5\uC0AC \uC2E4\uD328")));
|
|
9932
|
+
});
|
|
9933
|
+
} finally {
|
|
9934
|
+
await new Promise((resolve2) => {
|
|
9935
|
+
const child = spawn("hdiutil", ["detach", mountPoint]);
|
|
9936
|
+
child.on("close", () => resolve2());
|
|
9937
|
+
});
|
|
9938
|
+
}
|
|
9939
|
+
await rm(downloadPath, { force: true });
|
|
9940
|
+
return await createSymlink(join2(CACHE_DIR, pkg.binPath));
|
|
9941
|
+
}
|
|
9942
|
+
async function installLinux(pkg, downloadPath) {
|
|
9943
|
+
const extractDir = join2(CACHE_DIR, `extract-${Date.now()}`);
|
|
9944
|
+
await mkdir(extractDir, { recursive: true });
|
|
9945
|
+
await new Promise((resolve2, reject) => {
|
|
9946
|
+
const child = spawn("tar", ["xzf", downloadPath, "-C", extractDir]);
|
|
9947
|
+
child.on("close", (code) => code === 0 ? resolve2() : reject(new Error("\uC555\uCD95 \uD574\uC81C \uC2E4\uD328")));
|
|
9948
|
+
});
|
|
9949
|
+
const debsDir = join2(extractDir, "DEBS");
|
|
9950
|
+
try {
|
|
9951
|
+
await access(debsDir);
|
|
9952
|
+
const entries = await (await import("fs/promises")).readdir(debsDir);
|
|
9953
|
+
for (const entry of entries) {
|
|
9954
|
+
if (entry.endsWith(".deb")) {
|
|
9955
|
+
await new Promise((resolve2, reject) => {
|
|
9956
|
+
const child = spawn("dpkg-deb", ["-x", join2(debsDir, entry), CACHE_DIR]);
|
|
9957
|
+
child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(`${entry} \uCD94\uCD9C \uC2E4\uD328`)));
|
|
9958
|
+
});
|
|
9959
|
+
}
|
|
9960
|
+
}
|
|
9961
|
+
} catch {
|
|
9962
|
+
}
|
|
9963
|
+
await rm(downloadPath, { force: true });
|
|
9964
|
+
await rm(extractDir, { recursive: true, force: true });
|
|
9965
|
+
return await createSymlink(join2(CACHE_DIR, pkg.binPath));
|
|
9966
|
+
}
|
|
9967
|
+
async function installWindows(pkg, downloadPath) {
|
|
9968
|
+
await new Promise((resolve2, reject) => {
|
|
9969
|
+
const child = spawn("msiexec", ["/a", downloadPath, "/qn", `TARGETDIR=${CACHE_DIR}`]);
|
|
9970
|
+
child.on("close", (code) => code === 0 ? resolve2() : reject(new Error("MSI \uC124\uCE58 \uC2E4\uD328")));
|
|
9971
|
+
});
|
|
9972
|
+
await rm(downloadPath, { force: true });
|
|
9973
|
+
return join2(CACHE_DIR, pkg.binPath);
|
|
9974
|
+
}
|
|
9975
|
+
async function createSymlink(actualBin) {
|
|
9976
|
+
const binDir = join2(CACHE_DIR, "bin");
|
|
9977
|
+
await mkdir(binDir, { recursive: true });
|
|
9978
|
+
const linkBin = join2(binDir, "soffice");
|
|
9979
|
+
try {
|
|
9980
|
+
await symlink(actualBin, linkBin);
|
|
9981
|
+
} catch {
|
|
9982
|
+
}
|
|
9983
|
+
process.env.PATH = `${binDir}${delimiter}${process.env.PATH}`;
|
|
9984
|
+
return linkBin;
|
|
9985
|
+
}
|
|
9986
|
+
async function installLibreOffice(onProgress) {
|
|
9987
|
+
const platform = process.platform;
|
|
9988
|
+
const pkg = PACKAGES[platform];
|
|
9989
|
+
if (!pkg) {
|
|
9990
|
+
throw new ConvertError(
|
|
9991
|
+
"UNSUPPORTED_PLATFORM",
|
|
9992
|
+
`${platform}\uC740 \uC790\uB3D9 \uC124\uCE58\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC218\uB3D9\uC73C\uB85C LibreOffice\uB97C \uC124\uCE58\uD574 \uC8FC\uC138\uC694.`
|
|
9993
|
+
);
|
|
9994
|
+
}
|
|
9995
|
+
return await installForPlatform(pkg, onProgress);
|
|
9996
|
+
}
|
|
9997
|
+
async function resolveSoffice(emitter, autoInstall = true) {
|
|
9998
|
+
emitter.validate("soffice_check", "LibreOffice \uAC00\uC6A9\uC131 \uD655\uC778 \uC911...");
|
|
9999
|
+
const inPath = await findInPath();
|
|
10000
|
+
if (inPath) {
|
|
10001
|
+
emitter.validate("soffice_found", "\uC2DC\uC2A4\uD15C PATH\uC5D0\uC11C LibreOffice \uBC1C\uACAC", { sofficePath: inPath });
|
|
10002
|
+
return inPath;
|
|
10003
|
+
}
|
|
10004
|
+
const inCache = await findInCache();
|
|
10005
|
+
if (inCache) {
|
|
10006
|
+
emitter.validate("soffice_found", "\uCE90\uC2DC\uB41C LibreOffice \uBC1C\uACAC", { sofficePath: inCache });
|
|
10007
|
+
return inCache;
|
|
10008
|
+
}
|
|
10009
|
+
if (!autoInstall) {
|
|
10010
|
+
emitter.error(
|
|
10011
|
+
"validate",
|
|
10012
|
+
"SOFFICE_NOT_FOUND",
|
|
10013
|
+
"LibreOffice\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
10014
|
+
"\uC218\uB3D9\uC73C\uB85C \uC124\uCE58\uD558\uAC70\uB098 autoInstallLibreOffice: true \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694."
|
|
10015
|
+
);
|
|
10016
|
+
throw new ConvertError("SOFFICE_NOT_FOUND", "LibreOffice\uAC00 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
10017
|
+
}
|
|
10018
|
+
emitter.install("install_start", "LibreOffice \uC790\uB3D9 \uC124\uCE58\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
10019
|
+
try {
|
|
10020
|
+
const installed = await installLibreOffice((downloaded, total) => {
|
|
10021
|
+
const percent = Math.round(downloaded / total * 100);
|
|
10022
|
+
emitter.install("download_progress", `\uB2E4\uC6B4\uB85C\uB4DC \uC911... ${percent}%`, {
|
|
10023
|
+
percent,
|
|
10024
|
+
downloadedBytes: downloaded,
|
|
10025
|
+
totalBytes: total
|
|
10026
|
+
});
|
|
10027
|
+
});
|
|
10028
|
+
emitter.install("install_complete", "\uC124\uCE58 \uC644\uB8CC", { installedPath: installed });
|
|
10029
|
+
return installed;
|
|
10030
|
+
} catch (err) {
|
|
10031
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
10032
|
+
emitter.install("install_failed", "\uC124\uCE58 \uC2E4\uD328", { error: errorMsg });
|
|
10033
|
+
throw err;
|
|
10034
|
+
}
|
|
10035
|
+
}
|
|
10036
|
+
|
|
10037
|
+
// src/convert/libreoffice.ts
|
|
9827
10038
|
var libreConvert = libre.convert;
|
|
10039
|
+
async function convertBuffer(buffer, targetExt, timeoutMs = 6e4) {
|
|
10040
|
+
return new Promise((resolve2, reject) => {
|
|
10041
|
+
const timer = setTimeout(() => {
|
|
10042
|
+
reject(
|
|
10043
|
+
new ConvertError("TIMEOUT", `\uBCC0\uD658 \uD0C0\uC784\uC544\uC6C3 (${timeoutMs}ms \uCD08\uACFC)`)
|
|
10044
|
+
);
|
|
10045
|
+
}, timeoutMs);
|
|
10046
|
+
libreConvert(buffer, targetExt, void 0, (err, done) => {
|
|
10047
|
+
clearTimeout(timer);
|
|
10048
|
+
if (err || !done) {
|
|
10049
|
+
reject(
|
|
10050
|
+
new ConvertError(
|
|
10051
|
+
"CONVERT_FAILED",
|
|
10052
|
+
err?.message ?? "LibreOffice \uBCC0\uD658 \uC2E4\uD328"
|
|
10053
|
+
)
|
|
10054
|
+
);
|
|
10055
|
+
return;
|
|
10056
|
+
}
|
|
10057
|
+
resolve2(done);
|
|
10058
|
+
});
|
|
10059
|
+
});
|
|
10060
|
+
}
|
|
10061
|
+
|
|
10062
|
+
// src/convert/events.ts
|
|
10063
|
+
var ConvertEventEmitter = class {
|
|
10064
|
+
listener = null;
|
|
10065
|
+
/** 이벤트 리스너 등록 */
|
|
10066
|
+
setListener(listener) {
|
|
10067
|
+
this.listener = listener;
|
|
10068
|
+
}
|
|
10069
|
+
/** 이벤트 발송 */
|
|
10070
|
+
emit(event) {
|
|
10071
|
+
try {
|
|
10072
|
+
this.listener?.(event);
|
|
10073
|
+
} catch {
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
/** 타입 안전한 헬퍼: detect 이벤트 */
|
|
10077
|
+
detect(stage, message, meta) {
|
|
10078
|
+
this.emit({ type: "detect", stage, message, ...meta });
|
|
10079
|
+
}
|
|
10080
|
+
/** 타입 안전한 헬퍼: validate 이벤트 */
|
|
10081
|
+
validate(stage, message, meta) {
|
|
10082
|
+
this.emit({ type: "validate", stage, message, ...meta });
|
|
10083
|
+
}
|
|
10084
|
+
/** 타입 안전한 헬퍼: install 이벤트 */
|
|
10085
|
+
install(stage, message, meta) {
|
|
10086
|
+
this.emit({ type: "install", stage, message, ...meta });
|
|
10087
|
+
}
|
|
10088
|
+
/** 타입 안전한 헬퍼: convert 진행 이벤트 */
|
|
10089
|
+
progress(percent, message) {
|
|
10090
|
+
this.emit({ type: "convert", stage: "convert_progress", message, percent });
|
|
10091
|
+
}
|
|
10092
|
+
/** 타입 안전한 헬퍼: convert 시작 */
|
|
10093
|
+
convertStart(message) {
|
|
10094
|
+
this.emit({ type: "convert", stage: "convert_start", message, percent: 0 });
|
|
10095
|
+
}
|
|
10096
|
+
/** 타입 안전한 헬퍼: convert 완료 */
|
|
10097
|
+
convertDone(message) {
|
|
10098
|
+
this.emit({ type: "convert", stage: "convert_done", message, percent: 100 });
|
|
10099
|
+
}
|
|
10100
|
+
/** 타입 안전한 헬퍼: 완료 이벤트 */
|
|
10101
|
+
complete(result) {
|
|
10102
|
+
this.emit({ type: "complete", stage: "success", message: "\uBCC0\uD658 \uC644\uB8CC", result });
|
|
10103
|
+
}
|
|
10104
|
+
/** 타입 안전한 헬퍼: 에러 이벤트 */
|
|
10105
|
+
error(stage, code, message, suggestion) {
|
|
10106
|
+
this.emit({ type: "error", stage, code, message, recoverable: true, suggestion });
|
|
10107
|
+
}
|
|
10108
|
+
};
|
|
10109
|
+
|
|
10110
|
+
// src/convert/index.ts
|
|
10111
|
+
var isConverting = false;
|
|
10112
|
+
var queue = [];
|
|
10113
|
+
async function acquireConvertLock() {
|
|
10114
|
+
if (!isConverting) {
|
|
10115
|
+
isConverting = true;
|
|
10116
|
+
return () => {
|
|
10117
|
+
isConverting = false;
|
|
10118
|
+
const next = queue.shift();
|
|
10119
|
+
next?.();
|
|
10120
|
+
};
|
|
10121
|
+
}
|
|
10122
|
+
return new Promise((resolve2) => {
|
|
10123
|
+
queue.push(() => {
|
|
10124
|
+
isConverting = true;
|
|
10125
|
+
resolve2(() => {
|
|
10126
|
+
isConverting = false;
|
|
10127
|
+
const next = queue.shift();
|
|
10128
|
+
next?.();
|
|
10129
|
+
});
|
|
10130
|
+
});
|
|
10131
|
+
});
|
|
10132
|
+
}
|
|
10133
|
+
async function convertToPdf(input, options) {
|
|
10134
|
+
const emitter = new ConvertEventEmitter();
|
|
10135
|
+
if (options?.onEvent) {
|
|
10136
|
+
emitter.setListener(options.onEvent);
|
|
10137
|
+
}
|
|
10138
|
+
if (options?.onProgress) {
|
|
10139
|
+
const legacyProgress = options.onProgress;
|
|
10140
|
+
emitter.setListener((event) => {
|
|
10141
|
+
if (event.type === "convert" && event.stage === "convert_progress") {
|
|
10142
|
+
legacyProgress(event.percent, event.message);
|
|
10143
|
+
}
|
|
10144
|
+
});
|
|
10145
|
+
}
|
|
10146
|
+
try {
|
|
10147
|
+
emitter.detect("reading", "\uC785\uB825 \uD30C\uC77C \uC77D\uB294 \uC911...");
|
|
10148
|
+
let buffer;
|
|
10149
|
+
try {
|
|
10150
|
+
if (typeof input === "string") {
|
|
10151
|
+
buffer = await readFile(input);
|
|
10152
|
+
} else if (Buffer.isBuffer(input)) {
|
|
10153
|
+
buffer = input;
|
|
10154
|
+
} else {
|
|
10155
|
+
buffer = Buffer.from(input);
|
|
10156
|
+
}
|
|
10157
|
+
} catch (err) {
|
|
10158
|
+
emitter.error(
|
|
10159
|
+
"detect",
|
|
10160
|
+
"PARSE_ERROR",
|
|
10161
|
+
`\uC785\uB825 \uC77D\uAE30 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`
|
|
10162
|
+
);
|
|
10163
|
+
return {
|
|
10164
|
+
success: false,
|
|
10165
|
+
code: "PARSE_ERROR",
|
|
10166
|
+
error: `\uC785\uB825 \uC77D\uAE30 \uC2E4\uD328: ${err instanceof Error ? err.message : String(err)}`,
|
|
10167
|
+
stage: "detect"
|
|
10168
|
+
};
|
|
10169
|
+
}
|
|
10170
|
+
const MAX_FILE_SIZE = 500 * 1024 * 1024;
|
|
10171
|
+
if (buffer.length > MAX_FILE_SIZE) {
|
|
10172
|
+
emitter.error(
|
|
10173
|
+
"detect",
|
|
10174
|
+
"FILE_TOO_LARGE",
|
|
10175
|
+
`\uD30C\uC77C \uD06C\uAE30 \uCD08\uACFC: ${(buffer.length / 1024 / 1024).toFixed(1)}MB (\uCD5C\uB300 500MB)`
|
|
10176
|
+
);
|
|
10177
|
+
return {
|
|
10178
|
+
success: false,
|
|
10179
|
+
code: "FILE_TOO_LARGE",
|
|
10180
|
+
error: `\uD30C\uC77C \uD06C\uAE30 \uCD08\uACFC: ${(buffer.length / 1024 / 1024).toFixed(1)}MB (\uCD5C\uB300 500MB)`,
|
|
10181
|
+
stage: "detect"
|
|
10182
|
+
};
|
|
10183
|
+
}
|
|
10184
|
+
const format = detectFormat(toArrayBuffer(buffer));
|
|
10185
|
+
emitter.detect("format_detected", `\uD3EC\uB9F7 \uAC10\uC9C0 \uC644\uB8CC: ${format}`, { format });
|
|
10186
|
+
if (format !== "hwp" && format !== "hwpx") {
|
|
10187
|
+
emitter.error("detect", "UNSUPPORTED_FORMAT", `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD3EC\uB9F7\uC785\uB2C8\uB2E4: ${format}`);
|
|
10188
|
+
return {
|
|
10189
|
+
success: false,
|
|
10190
|
+
code: "UNSUPPORTED_FORMAT",
|
|
10191
|
+
error: `\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD3EC\uB9F7\uC785\uB2C8\uB2E4: ${format}`,
|
|
10192
|
+
stage: "detect"
|
|
10193
|
+
};
|
|
10194
|
+
}
|
|
10195
|
+
emitter.validate("soffice_check", "LibreOffice \uAC00\uC6A9\uC131 \uD655\uC778 \uC911...");
|
|
10196
|
+
let sofficePath;
|
|
10197
|
+
try {
|
|
10198
|
+
sofficePath = await resolveSoffice(emitter, options?.autoInstallLibreOffice ?? true);
|
|
10199
|
+
} catch (err) {
|
|
10200
|
+
if (err instanceof ConvertError) {
|
|
10201
|
+
return {
|
|
10202
|
+
success: false,
|
|
10203
|
+
code: err.code,
|
|
10204
|
+
error: err.message,
|
|
10205
|
+
stage: "validate"
|
|
10206
|
+
};
|
|
10207
|
+
}
|
|
10208
|
+
throw err;
|
|
10209
|
+
}
|
|
10210
|
+
const releaseLock = await acquireConvertLock();
|
|
10211
|
+
try {
|
|
10212
|
+
emitter.convertStart("\uBCC0\uD658 \uC2DC\uC791...");
|
|
10213
|
+
emitter.progress(10, "\uBCC0\uD658 \uC911...");
|
|
10214
|
+
const pdf = await convertBuffer(buffer, ".pdf", options?.timeoutMs);
|
|
10215
|
+
emitter.progress(100, "\uBCC0\uD658 \uC644\uB8CC");
|
|
10216
|
+
emitter.convertDone("\uBCC0\uD658 \uC644\uB8CC");
|
|
10217
|
+
const result = {
|
|
10218
|
+
success: true,
|
|
10219
|
+
pdf: new Uint8Array(pdf),
|
|
10220
|
+
sourceFormat: format
|
|
10221
|
+
};
|
|
10222
|
+
emitter.complete({
|
|
10223
|
+
sourceFormat: format,
|
|
10224
|
+
pdfSize: pdf.length
|
|
10225
|
+
});
|
|
10226
|
+
return result;
|
|
10227
|
+
} catch (err) {
|
|
10228
|
+
if (err instanceof ConvertError) {
|
|
10229
|
+
emitter.error("convert", err.code, err.message);
|
|
10230
|
+
return {
|
|
10231
|
+
success: false,
|
|
10232
|
+
code: err.code,
|
|
10233
|
+
error: err.message,
|
|
10234
|
+
stage: "convert"
|
|
10235
|
+
};
|
|
10236
|
+
}
|
|
10237
|
+
const errorMsg = err instanceof Error ? err.message : "\uBCC0\uD658 \uC2E4\uD328";
|
|
10238
|
+
emitter.error("convert", classifyError(err), errorMsg);
|
|
10239
|
+
return {
|
|
10240
|
+
success: false,
|
|
10241
|
+
code: classifyError(err),
|
|
10242
|
+
error: errorMsg,
|
|
10243
|
+
stage: "convert"
|
|
10244
|
+
};
|
|
10245
|
+
} finally {
|
|
10246
|
+
releaseLock();
|
|
10247
|
+
}
|
|
10248
|
+
} catch (unexpectedErr) {
|
|
10249
|
+
const errorMsg = unexpectedErr instanceof Error ? unexpectedErr.message : "\uC608\uC0C1\uCE58 \uBABB\uD55C \uC624\uB958";
|
|
10250
|
+
emitter.error("convert", "PARSE_ERROR", errorMsg);
|
|
10251
|
+
return {
|
|
10252
|
+
success: false,
|
|
10253
|
+
code: "PARSE_ERROR",
|
|
10254
|
+
error: errorMsg,
|
|
10255
|
+
stage: "convert"
|
|
10256
|
+
};
|
|
10257
|
+
}
|
|
10258
|
+
}
|
|
10259
|
+
|
|
10260
|
+
// src/pipeline/unified-ocr.ts
|
|
10261
|
+
import { performance } from "perf_hooks";
|
|
9828
10262
|
var OCR_PROMPT = [
|
|
9829
10263
|
"\uC774 PDF \uD398\uC774\uC9C0 \uC774\uBBF8\uC9C0\uC5D0\uC11C \uD14D\uC2A4\uD2B8\uC640 \uD45C\uB97C \uCD94\uCD9C\uD558\uC5EC Markdown\uC73C\uB85C \uBCC0\uD658\uD558\uACE0, OCR \uC624\uC778\uC2DD \uC624\uB958\uB97C \uC989\uC2DC \uAD50\uC815\uD558\uC5EC \uCD5C\uC885 \uACB0\uACFC\uBB3C\uC744 \uCD9C\uB825\uD558\uC138\uC694.",
|
|
9830
10264
|
"",
|
|
@@ -9864,7 +10298,7 @@ async function parse2(input, options) {
|
|
|
9864
10298
|
let buffer;
|
|
9865
10299
|
if (typeof input === "string") {
|
|
9866
10300
|
try {
|
|
9867
|
-
const buf = await
|
|
10301
|
+
const buf = await readFile2(input);
|
|
9868
10302
|
buffer = toArrayBuffer(buf);
|
|
9869
10303
|
} catch (err) {
|
|
9870
10304
|
const msg = err instanceof Error && "code" in err && err.code === "ENOENT" ? `\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}` : `\uD30C\uC77C \uC77D\uAE30 \uC2E4\uD328: ${input}`;
|
|
@@ -10199,6 +10633,7 @@ export {
|
|
|
10199
10633
|
extractFormFields,
|
|
10200
10634
|
markdownToHwpx,
|
|
10201
10635
|
markdownToXlsx,
|
|
10636
|
+
convertToPdf,
|
|
10202
10637
|
parse2 as parse
|
|
10203
10638
|
};
|
|
10204
10639
|
/*! Bundled license information:
|
|
@@ -10206,4 +10641,4 @@ export {
|
|
|
10206
10641
|
cfb/cfb.js:
|
|
10207
10642
|
(*! crc32.js (C) 2014-present SheetJS -- http://sheetjs.com *)
|
|
10208
10643
|
*/
|
|
10209
|
-
//# sourceMappingURL=chunk-
|
|
10644
|
+
//# sourceMappingURL=chunk-BZPZXI66.js.map
|