kordoc 4.12.2 → 4.13.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/README.md +32 -1
- package/dist/{-EZH6NKTE.js → -UZDRUWTX.js} +43 -22
- package/dist/{chunk-FBLMVHDK.js → chunk-27QN27I3.js} +2 -2
- package/dist/{chunk-MUYU4JI5.js → chunk-2B45S7FM.js} +2 -2
- package/dist/{chunk-LD2K4K5V.js → chunk-2RREQZ44.js} +1662 -652
- package/dist/chunk-2RREQZ44.js.map +1 -0
- package/dist/{chunk-OEYECZGV.js → chunk-3ABHPKYA.js} +10 -6
- package/dist/chunk-3ABHPKYA.js.map +1 -0
- package/dist/{chunk-QM2RSJ2S.js → chunk-3MCFJIN2.js} +5 -5
- package/dist/{chunk-JCMBQC5N.js → chunk-5VHLKNCE.js} +2 -2
- package/dist/{chunk-FVBR7SC3.js → chunk-6JXMIX3G.js} +2 -2
- package/dist/chunk-6JXMIX3G.js.map +1 -0
- package/dist/{chunk-5RPYBC2Q.js → chunk-6NLXGGNH.js} +37 -6
- package/dist/chunk-6NLXGGNH.js.map +1 -0
- package/dist/{chunk-2B7OIEJJ.js → chunk-AGBFCFQY.js} +10 -6
- package/dist/chunk-AGBFCFQY.js.map +1 -0
- package/dist/{rasterize-NRWP2C6L.js → chunk-BQLZOQES.js} +22 -6
- package/dist/chunk-BQLZOQES.js.map +1 -0
- package/dist/{chunk-ONNAHSPV.cjs → chunk-CKMK42FV.cjs} +18 -14
- package/dist/chunk-CKMK42FV.cjs.map +1 -0
- package/dist/{chunk-T7GRHAIW.cjs → chunk-COUGOFT3.cjs} +1 -1
- package/dist/chunk-COUGOFT3.cjs.map +1 -0
- package/dist/{chunk-2LO525TL.js → chunk-F3FEIYI7.js} +2 -2
- package/dist/{chunk-Q6ETEJFK.js → chunk-GWFX5ZD7.js} +11 -7
- package/dist/chunk-GWFX5ZD7.js.map +1 -0
- package/dist/{chunk-2HBYWA5X.js → chunk-H5JWPWQP.js} +2 -2
- package/dist/{chunk-N3DLQ2YH.js → chunk-HG5LXXCC.js} +1 -1
- package/dist/chunk-HG5LXXCC.js.map +1 -0
- package/dist/{chunk-KX7IYCCZ.js → chunk-IEJR23OA.js} +3 -3
- package/dist/{chunk-7XUXSDH5.js → chunk-ILDTN72S.js} +10 -6
- package/dist/chunk-ILDTN72S.js.map +1 -0
- package/dist/{chunk-QBCMFNHS.js → chunk-L3CEPNQS.js} +2 -2
- package/dist/{chunk-XXDZ2KTW.js → chunk-LP5RTYFM.js} +2 -2
- package/dist/{chunk-2LKYJ4G3.cjs → chunk-MCD45IHO.cjs} +6 -6
- package/dist/{chunk-2LKYJ4G3.cjs.map → chunk-MCD45IHO.cjs.map} +1 -1
- package/dist/{chunk-V5BUPZ6I.cjs → chunk-OJV7HNGJ.cjs} +13 -9
- package/dist/chunk-OJV7HNGJ.cjs.map +1 -0
- package/dist/{chunk-DBGEUDBY.js → chunk-OSLJ3A6D.js} +2 -2
- package/dist/{chunk-2LPBIW2B.cjs → chunk-S2VKZKUX.cjs} +3 -3
- package/dist/{chunk-2LPBIW2B.cjs.map → chunk-S2VKZKUX.cjs.map} +1 -1
- package/dist/{chunk-IOJX4Q63.js → chunk-T7AFLXGA.js} +786 -19
- package/dist/chunk-T7AFLXGA.js.map +1 -0
- package/dist/{chunk-UL2MDA5C.js → chunk-TA2LEFOF.js} +3 -3
- package/dist/{chunk-62IU4NV3.cjs → chunk-TAYCQE6K.cjs} +2 -2
- package/dist/{chunk-62IU4NV3.cjs.map → chunk-TAYCQE6K.cjs.map} +1 -1
- package/dist/{chunk-ONQHGZTS.js → chunk-XVIJLHCW.js} +1 -1
- package/dist/chunk-XVIJLHCW.js.map +1 -0
- package/dist/chunks-G3GK536Y.js +10 -0
- package/dist/cli.js +187 -38
- package/dist/cli.js.map +1 -1
- package/dist/{formula-6RQX4NMJ.js → formula-3T7HQA4O.js} +4 -4
- package/dist/{formula-NW33S4TA.js → formula-FWVAOSIK.js} +3 -3
- package/dist/{formula-NBEO6CFC.cjs → formula-YALEMZMF.cjs} +9 -9
- package/dist/{formula-NBEO6CFC.cjs.map → formula-YALEMZMF.cjs.map} +1 -1
- package/dist/{image-ocr-SO56VETS.js → image-ocr-2M7K3CE5.js} +8 -8
- package/dist/{image-ocr-V4KY373B.cjs → image-ocr-4EUZIF6M.cjs} +12 -12
- package/dist/{image-ocr-V4KY373B.cjs.map → image-ocr-4EUZIF6M.cjs.map} +1 -1
- package/dist/{image-ocr-CAVU2TNZ.js → image-ocr-X3I2HUK2.js} +6 -6
- package/dist/index.cjs +2349 -576
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -12
- package/dist/index.d.ts +305 -12
- package/dist/index.js +1980 -207
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +109 -35
- package/dist/mcp.js.map +1 -1
- package/dist/{model-bundle-EIJF34DR.js → model-bundle-XXYZ6ELQ.js} +5 -5
- package/dist/{models-6V4CPQXC.js → models-APDSXJW7.js} +5 -5
- package/dist/{parser-OMT25WH2.cjs → parser-4KE2FR5F.cjs} +60 -39
- package/dist/parser-4KE2FR5F.cjs.map +1 -0
- package/dist/{parser-OBHNXEQM.js → parser-T5R4YVZS.js} +32 -11
- package/dist/parser-T5R4YVZS.js.map +1 -0
- package/dist/{parser-GIREMEPD.js → parser-XKSOS6GY.js} +32 -11
- package/dist/parser-XKSOS6GY.js.map +1 -0
- package/dist/pdf-ocr-JZKCEKWX.cjs +14 -0
- package/dist/{pdf-ocr-TFYVK4FI.cjs.map → pdf-ocr-JZKCEKWX.cjs.map} +1 -1
- package/dist/pdf-ocr-X62B7YDV.js +13 -0
- package/dist/pdf-ocr-YDJQEGDS.js +16 -0
- package/dist/{profile-io-PZVMM66V.js → profile-io-Y24GOHWM.js} +3 -3
- package/dist/rasterize-QCOBWHAG.js +13 -0
- package/dist/render-DHFQQCSV.js +38 -0
- package/dist/seal-UTHFQPLO.js +10 -0
- package/dist/seal-UTHFQPLO.js.map +1 -0
- package/dist/{setup-V55AJP6H.js → setup-UWX3KH3G.js} +3 -3
- package/dist/visual-DF2KSPEC.js +19 -0
- package/dist/visual-DF2KSPEC.js.map +1 -0
- package/dist/{watch-7CQSMDDP.js → watch-4ZUTMC3Q.js} +15 -13
- package/dist/{watch-7CQSMDDP.js.map → watch-4ZUTMC3Q.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-2B7OIEJJ.js.map +0 -1
- package/dist/chunk-5RPYBC2Q.js.map +0 -1
- package/dist/chunk-7XUXSDH5.js.map +0 -1
- package/dist/chunk-FVBR7SC3.js.map +0 -1
- package/dist/chunk-IOJX4Q63.js.map +0 -1
- package/dist/chunk-LD2K4K5V.js.map +0 -1
- package/dist/chunk-N3DLQ2YH.js.map +0 -1
- package/dist/chunk-OEYECZGV.js.map +0 -1
- package/dist/chunk-ONNAHSPV.cjs.map +0 -1
- package/dist/chunk-ONQHGZTS.js.map +0 -1
- package/dist/chunk-Q6ETEJFK.js.map +0 -1
- package/dist/chunk-T7GRHAIW.cjs.map +0 -1
- package/dist/chunk-V5BUPZ6I.cjs.map +0 -1
- package/dist/chunks-TT7ZE5QQ.js +0 -10
- package/dist/parser-GIREMEPD.js.map +0 -1
- package/dist/parser-OBHNXEQM.js.map +0 -1
- package/dist/parser-OMT25WH2.cjs.map +0 -1
- package/dist/pdf-ocr-IRXQ5GBM.js +0 -13
- package/dist/pdf-ocr-SL4GCOGS.js +0 -16
- package/dist/pdf-ocr-TFYVK4FI.cjs +0 -14
- package/dist/rasterize-NRWP2C6L.js.map +0 -1
- package/dist/render-V5ZPTZQ7.js +0 -10
- package/dist/seal-LVG2DGAB.js +0 -10
- /package/dist/{-EZH6NKTE.js.map → -UZDRUWTX.js.map} +0 -0
- /package/dist/{chunk-FBLMVHDK.js.map → chunk-27QN27I3.js.map} +0 -0
- /package/dist/{chunk-MUYU4JI5.js.map → chunk-2B45S7FM.js.map} +0 -0
- /package/dist/{chunk-QM2RSJ2S.js.map → chunk-3MCFJIN2.js.map} +0 -0
- /package/dist/{chunk-JCMBQC5N.js.map → chunk-5VHLKNCE.js.map} +0 -0
- /package/dist/{chunk-2LO525TL.js.map → chunk-F3FEIYI7.js.map} +0 -0
- /package/dist/{chunk-2HBYWA5X.js.map → chunk-H5JWPWQP.js.map} +0 -0
- /package/dist/{chunk-KX7IYCCZ.js.map → chunk-IEJR23OA.js.map} +0 -0
- /package/dist/{chunk-QBCMFNHS.js.map → chunk-L3CEPNQS.js.map} +0 -0
- /package/dist/{chunk-XXDZ2KTW.js.map → chunk-LP5RTYFM.js.map} +0 -0
- /package/dist/{chunk-DBGEUDBY.js.map → chunk-OSLJ3A6D.js.map} +0 -0
- /package/dist/{chunk-UL2MDA5C.js.map → chunk-TA2LEFOF.js.map} +0 -0
- /package/dist/{chunks-TT7ZE5QQ.js.map → chunks-G3GK536Y.js.map} +0 -0
- /package/dist/{formula-6RQX4NMJ.js.map → formula-3T7HQA4O.js.map} +0 -0
- /package/dist/{formula-NW33S4TA.js.map → formula-FWVAOSIK.js.map} +0 -0
- /package/dist/{image-ocr-SO56VETS.js.map → image-ocr-2M7K3CE5.js.map} +0 -0
- /package/dist/{image-ocr-CAVU2TNZ.js.map → image-ocr-X3I2HUK2.js.map} +0 -0
- /package/dist/{model-bundle-EIJF34DR.js.map → model-bundle-XXYZ6ELQ.js.map} +0 -0
- /package/dist/{models-6V4CPQXC.js.map → models-APDSXJW7.js.map} +0 -0
- /package/dist/{pdf-ocr-IRXQ5GBM.js.map → pdf-ocr-X62B7YDV.js.map} +0 -0
- /package/dist/{pdf-ocr-SL4GCOGS.js.map → pdf-ocr-YDJQEGDS.js.map} +0 -0
- /package/dist/{profile-io-PZVMM66V.js.map → profile-io-Y24GOHWM.js.map} +0 -0
- /package/dist/{render-V5ZPTZQ7.js.map → rasterize-QCOBWHAG.js.map} +0 -0
- /package/dist/{seal-LVG2DGAB.js.map → render-DHFQQCSV.js.map} +0 -0
- /package/dist/{setup-V55AJP6H.js.map → setup-UWX3KH3G.js.map} +0 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
parsePageRange
|
|
4
|
+
} from "./chunk-MOL7MDBG.js";
|
|
2
5
|
import {
|
|
3
6
|
DEFAULT_CHAR,
|
|
4
7
|
DEFAULT_PARA_GEOM,
|
|
@@ -7,11 +10,19 @@ import {
|
|
|
7
10
|
createXmlParser,
|
|
8
11
|
findChildByLocalName,
|
|
9
12
|
parseRenderStyles
|
|
10
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6JXMIX3G.js";
|
|
14
|
+
import {
|
|
15
|
+
blocksToMarkdown
|
|
16
|
+
} from "./chunk-GWFX5ZD7.js";
|
|
17
|
+
import {
|
|
18
|
+
loadSharp,
|
|
19
|
+
rasterizePageSvg
|
|
20
|
+
} from "./chunk-BQLZOQES.js";
|
|
11
21
|
import {
|
|
12
22
|
KordocError,
|
|
13
|
-
precheckZipSize
|
|
14
|
-
|
|
23
|
+
precheckZipSize,
|
|
24
|
+
toArrayBuffer
|
|
25
|
+
} from "./chunk-27QN27I3.js";
|
|
15
26
|
|
|
16
27
|
// src/render/svg-render.ts
|
|
17
28
|
import JSZip from "jszip";
|
|
@@ -251,13 +262,29 @@ var SPACE_EM_FONT = 300;
|
|
|
251
262
|
function faceClassOf(face) {
|
|
252
263
|
return face && /^(굴림체|돋움체|바탕체|궁서체)$/.test(face.trim()) ? "fixedPitch" : "hcr";
|
|
253
264
|
}
|
|
265
|
+
function faceClassForGen(face) {
|
|
266
|
+
if (!face) return "hcr";
|
|
267
|
+
const f = face.trim();
|
|
268
|
+
if (/^(굴림체|돋움체|바탕체|궁서체)$/.test(f)) return "fixedPitch";
|
|
269
|
+
if (/^(한컴돋움|맑은 고딕|HY견고딕|HY헤드라인M|HY중고딕|한양중고딕|나눔고딕|나눔스퀘어|돋움|굴림)$/.test(f)) return "gothic";
|
|
270
|
+
return "hcr";
|
|
271
|
+
}
|
|
272
|
+
function gothicWidthEm1000(cp) {
|
|
273
|
+
if (cp >= 128) return 1e3;
|
|
274
|
+
if (cp >= 65 && cp <= 90) return 720;
|
|
275
|
+
if (cp >= 97 && cp <= 122) return 560;
|
|
276
|
+
if (cp >= 48 && cp <= 57) return 580;
|
|
277
|
+
if (cp === 40 || cp === 41 || cp === 91 || cp === 93) return 350;
|
|
278
|
+
if (cp === 46 || cp === 44 || cp === 58 || cp === 59 || cp === 39) return 280;
|
|
279
|
+
return 500;
|
|
280
|
+
}
|
|
254
281
|
function fixedPitchWidthEm1000(cp) {
|
|
255
282
|
return cp < 128 ? 500 : 1e3;
|
|
256
283
|
}
|
|
257
284
|
function measureTextWidth(text, height, ratioPct, opts) {
|
|
258
285
|
const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
|
|
259
286
|
const spacing = opts?.spacingPct ?? 0;
|
|
260
|
-
const widthEm = opts?.faceClass === "fixedPitch" ? fixedPitchWidthEm1000 : charWidthEm1000;
|
|
287
|
+
const widthEm = opts?.faceClass === "fixedPitch" ? fixedPitchWidthEm1000 : opts?.faceClass === "gothic" ? gothicWidthEm1000 : charWidthEm1000;
|
|
261
288
|
let em = 0;
|
|
262
289
|
for (const ch of text) {
|
|
263
290
|
const cp = ch.codePointAt(0);
|
|
@@ -272,7 +299,7 @@ function simulateWrap(text, firstWidth, contWidth, height, ratioPct, mode = "kee
|
|
|
272
299
|
const EPS = 0.5;
|
|
273
300
|
const spaceEm = opts?.spaceEm ?? SPACE_EM_FIXED;
|
|
274
301
|
const spacing = opts?.spacingPct ?? 0;
|
|
275
|
-
const widthEm = opts?.faceClass === "fixedPitch" ? fixedPitchWidthEm1000 : charWidthEm1000;
|
|
302
|
+
const widthEm = opts?.faceClass === "fixedPitch" ? fixedPitchWidthEm1000 : opts?.faceClass === "gothic" ? gothicWidthEm1000 : charWidthEm1000;
|
|
276
303
|
const k = height * ratioPct / 100 / 1e3;
|
|
277
304
|
const cwCp = (cp) => (cp === 32 ? spaceEm : widthEm(cp)) * (1 + spacing / 100) * k;
|
|
278
305
|
const charW2 = (ch) => cwCp(ch.codePointAt(0));
|
|
@@ -534,6 +561,36 @@ function reflowSection(root, styles, geom, mode = "keep") {
|
|
|
534
561
|
return counter.n;
|
|
535
562
|
}
|
|
536
563
|
|
|
564
|
+
// src/render/scene.ts
|
|
565
|
+
function regionId(type, seq) {
|
|
566
|
+
return `${type}-${String(seq).padStart(6, "0")}`;
|
|
567
|
+
}
|
|
568
|
+
function unionBBox(a, b) {
|
|
569
|
+
const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
|
|
570
|
+
return { page: a.page, x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
|
|
571
|
+
}
|
|
572
|
+
var RegionCollector = class {
|
|
573
|
+
regions = [];
|
|
574
|
+
counters = /* @__PURE__ */ new Map();
|
|
575
|
+
add(type, bbox, extra = {}) {
|
|
576
|
+
const seq = (this.counters.get(type) ?? 0) + 1;
|
|
577
|
+
this.counters.set(type, seq);
|
|
578
|
+
const id = regionId(type, seq);
|
|
579
|
+
const region = { id, type, page: bbox.page, regions: [bbox] };
|
|
580
|
+
if (extra.sourceId) region.sourceId = extra.sourceId;
|
|
581
|
+
if (extra.parentId) region.parentId = extra.parentId;
|
|
582
|
+
this.regions.push(region);
|
|
583
|
+
return id;
|
|
584
|
+
}
|
|
585
|
+
addFragment(id, bbox) {
|
|
586
|
+
const r = this.regions.find((x) => x.id === id);
|
|
587
|
+
if (!r) return;
|
|
588
|
+
const same = r.regions.find((f) => f.page === bbox.page);
|
|
589
|
+
if (same) Object.assign(same, unionBBox(same, bbox));
|
|
590
|
+
else r.regions.push(bbox);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
537
594
|
// src/render/svg-render.ts
|
|
538
595
|
function ln2(el) {
|
|
539
596
|
return (el.tagName || "").replace(/^[^:]+:/, "");
|
|
@@ -564,6 +621,20 @@ function escapeXml(s) {
|
|
|
564
621
|
}
|
|
565
622
|
var OBJ_TAGS = /* @__PURE__ */ new Set(["tbl", "pic", "container", "equation", "rect", "ellipse", "polygon", "curv", "line", "arc", "ole", "textart"]);
|
|
566
623
|
var pt = (u) => String(Math.round(u) / 100);
|
|
624
|
+
var ptNum = (u) => Math.round(u) / 100;
|
|
625
|
+
function pageNo(ctx) {
|
|
626
|
+
return ctx.pageBase + ctx.page + 1;
|
|
627
|
+
}
|
|
628
|
+
function bboxOf(ctx, x, y, w, h) {
|
|
629
|
+
const clippedH = Math.max(0, Math.min(h, ctx.pageH - y));
|
|
630
|
+
return { page: pageNo(ctx), x: ptNum(x), y: ptNum(y), width: ptNum(Math.max(0, w)), height: ptNum(clippedH) };
|
|
631
|
+
}
|
|
632
|
+
function regionOpenTag(id, type, page) {
|
|
633
|
+
return `<g data-kordoc-id="${id}" data-kordoc-type="${type}" data-kordoc-page="${page}">`;
|
|
634
|
+
}
|
|
635
|
+
function parentId(ctx) {
|
|
636
|
+
return ctx.parentStack.length ? ctx.parentStack[ctx.parentStack.length - 1] : void 0;
|
|
637
|
+
}
|
|
567
638
|
function emit(ctx, s) {
|
|
568
639
|
ctx.pages[ctx.page].push(s);
|
|
569
640
|
}
|
|
@@ -704,10 +775,23 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
|
|
|
704
775
|
}
|
|
705
776
|
const plans = planLines(m, ctx.styles);
|
|
706
777
|
const baseV = m.segs[0].vertpos;
|
|
778
|
+
const hasText = m.chars.some((c) => c.ch !== "" && c.ch.trim() !== "");
|
|
779
|
+
let paraRegionId = null;
|
|
780
|
+
let gOpenOn = -1;
|
|
707
781
|
for (let li = 0; li < plans.length; li++) {
|
|
708
782
|
const plan = plans[li];
|
|
709
783
|
if (segPages && segPages[li] !== void 0) ctx.page = segPages[li];
|
|
710
784
|
const { seg } = plan;
|
|
785
|
+
if (hasText) {
|
|
786
|
+
const lineBox = bboxOf(ctx, ox + seg.horzpos, oy + seg.vertpos, seg.horzsize, seg.textheight);
|
|
787
|
+
if (paraRegionId === null) paraRegionId = ctx.regions.add("paragraph", lineBox, { parentId: parentId(ctx) });
|
|
788
|
+
else ctx.regions.addFragment(paraRegionId, lineBox);
|
|
789
|
+
if (gOpenOn !== ctx.page) {
|
|
790
|
+
if (gOpenOn >= 0) ctx.pages[gOpenOn].push("</g>");
|
|
791
|
+
emit(ctx, regionOpenTag(paraRegionId, "paragraph", pageNo(ctx)));
|
|
792
|
+
gOpenOn = ctx.page;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
711
795
|
let i = plan.start;
|
|
712
796
|
let cursor = ox + seg.horzpos + plan.xoff;
|
|
713
797
|
const y = oy + seg.vertpos + seg.baseline;
|
|
@@ -788,6 +872,8 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
|
|
|
788
872
|
i = j;
|
|
789
873
|
}
|
|
790
874
|
}
|
|
875
|
+
if (gOpenOn >= 0) ctx.pages[gOpenOn].push("</g>");
|
|
876
|
+
if (paraRegionId) ctx.parentStack.push(paraRegionId);
|
|
791
877
|
for (const o of m.objs) {
|
|
792
878
|
if (o.inline) {
|
|
793
879
|
let planIdx = 0;
|
|
@@ -806,6 +892,7 @@ function drawPara(p, ox, oy, areaW, ctx, depth, segPages) {
|
|
|
806
892
|
drawObject(o, x, y, baseV, areaW, ctx, depth);
|
|
807
893
|
}
|
|
808
894
|
}
|
|
895
|
+
if (paraRegionId) ctx.parentStack.pop();
|
|
809
896
|
}
|
|
810
897
|
function anchorObject(o, ox, oy, baseV, areaW, ctx) {
|
|
811
898
|
const { PW, PH, ML, MT, BODY_W, BODY_H } = ctx.geom;
|
|
@@ -888,6 +975,10 @@ function drawShape(o, x, y, ctx, depth) {
|
|
|
888
975
|
const face = winBrush?.getAttribute("faceColor");
|
|
889
976
|
const fill = face && face.toLowerCase() !== "none" ? face : "none";
|
|
890
977
|
const fillAttr = ` fill="${fill === "none" ? "none" : escapeXml(fill)}"`;
|
|
978
|
+
ctx.stats.shapes++;
|
|
979
|
+
const shapeId = ctx.regions.add("shape", bboxOf(ctx, x, y, w, h), { parentId: parentId(ctx) });
|
|
980
|
+
emit(ctx, regionOpenTag(shapeId, "shape", pageNo(ctx)));
|
|
981
|
+
ctx.parentStack.push(shapeId);
|
|
891
982
|
if (o.tag === "rect") {
|
|
892
983
|
emit(ctx, `<rect x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}"${fillAttr}${strokeAttr}/>`);
|
|
893
984
|
} else if (o.tag === "ellipse") {
|
|
@@ -909,6 +1000,8 @@ function drawShape(o, x, y, ctx, depth) {
|
|
|
909
1000
|
if (sub) {
|
|
910
1001
|
for (const p of elements2(sub)) if (ln2(p) === "p") drawPara(p, x, y, w, ctx, depth + 1);
|
|
911
1002
|
}
|
|
1003
|
+
ctx.parentStack.pop();
|
|
1004
|
+
emit(ctx, "</g>");
|
|
912
1005
|
}
|
|
913
1006
|
function cellContentExtent(cell, memo) {
|
|
914
1007
|
if (!cell.sub) return 0;
|
|
@@ -1009,6 +1102,11 @@ function drawTable(tbl, tx, ty, ctx, depth) {
|
|
|
1009
1102
|
);
|
|
1010
1103
|
const rowY = [0];
|
|
1011
1104
|
for (let r = 0; r < nRows; r++) rowY.push(rowY[r] + rowH[r]);
|
|
1105
|
+
const tblH = rowY[nRows];
|
|
1106
|
+
if (ty + tblH > ctx.pageH + 100) warnOnce(ctx, "table-split", "\uD398\uC774\uC9C0\uC5D0 \uAC78\uCE5C \uD45C \u2014 \uBD84\uD560 \uC9C0\uC624\uBA54\uD2B8\uB9AC\uAC00 \uC5C6\uC5B4 \uC2DC\uC791 \uD398\uC774\uC9C0 \uC601\uC5ED\uB9CC region \uC73C\uB85C \uAE30\uB85D");
|
|
1107
|
+
const tblId = ctx.regions.add("table", bboxOf(ctx, tx, ty, colX[nCols], tblH), { sourceId: tbl.getAttribute("id") ?? void 0, parentId: parentId(ctx) });
|
|
1108
|
+
emit(ctx, regionOpenTag(tblId, "table", pageNo(ctx)));
|
|
1109
|
+
ctx.parentStack.push(tblId);
|
|
1012
1110
|
const geom = cells.map((c) => ({
|
|
1013
1111
|
c,
|
|
1014
1112
|
x: tx + colX[c.ca],
|
|
@@ -1042,6 +1140,8 @@ function drawTable(tbl, tx, ty, ctx, depth) {
|
|
|
1042
1140
|
if (bf.left) emit(ctx, edgeLine(g.x, g.y, g.x, g.y + g.h, bf.left));
|
|
1043
1141
|
if (bf.right) emit(ctx, edgeLine(g.x + g.w, g.y, g.x + g.w, g.y + g.h, bf.right));
|
|
1044
1142
|
}
|
|
1143
|
+
ctx.parentStack.pop();
|
|
1144
|
+
emit(ctx, "</g>");
|
|
1045
1145
|
}
|
|
1046
1146
|
function imageSymbol(loaded, ctx) {
|
|
1047
1147
|
if (!loaded.symId) {
|
|
@@ -1058,9 +1158,12 @@ function drawPic(pic, x, y, ctx) {
|
|
|
1058
1158
|
const img = findFirst(pic, "img");
|
|
1059
1159
|
const ref = img?.getAttribute("binaryItemIDRef");
|
|
1060
1160
|
const loaded = ref != null ? ctx.images.get(ref) : void 0;
|
|
1161
|
+
const imgId = ctx.regions.add("image", bboxOf(ctx, x, y, w, h), { sourceId: ref ?? void 0, parentId: parentId(ctx) });
|
|
1162
|
+
emit(ctx, regionOpenTag(imgId, "image", pageNo(ctx)));
|
|
1061
1163
|
if (!loaded) {
|
|
1062
1164
|
emit(ctx, `<rect x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}" fill="#eee" stroke="#c00" stroke-width="0.5"/>`);
|
|
1063
1165
|
warnOnce(ctx, `img:${ref}`, `\uC774\uBBF8\uC9C0 \uBC14\uC774\uB108\uB9AC \uB204\uB77D: ${ref ?? "(ref \uC5C6\uC74C)"}`);
|
|
1166
|
+
emit(ctx, "</g>");
|
|
1064
1167
|
return;
|
|
1065
1168
|
}
|
|
1066
1169
|
ctx.stats.images++;
|
|
@@ -1082,6 +1185,7 @@ function drawPic(pic, x, y, ctx) {
|
|
|
1082
1185
|
} else {
|
|
1083
1186
|
emit(ctx, `<use href="#${symId}" x="${pt(x)}" y="${pt(y)}" width="${pt(w)}" height="${pt(h)}"/>`);
|
|
1084
1187
|
}
|
|
1188
|
+
emit(ctx, "</g>");
|
|
1085
1189
|
}
|
|
1086
1190
|
function sniffMime(name, bytes) {
|
|
1087
1191
|
const lower = name.toLowerCase();
|
|
@@ -1140,20 +1244,22 @@ function renderSectionToPages(root, geom, ctxBase, doReflow, reflowMode) {
|
|
|
1140
1244
|
nPages = Math.max(nPages, cur + 1);
|
|
1141
1245
|
}
|
|
1142
1246
|
}
|
|
1247
|
+
const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2e3) : PH;
|
|
1143
1248
|
const ctx = {
|
|
1144
1249
|
...ctxBase,
|
|
1145
1250
|
pages: Array.from({ length: nPages }, () => []),
|
|
1146
1251
|
page: 0,
|
|
1147
|
-
geom
|
|
1252
|
+
geom,
|
|
1253
|
+
pageH
|
|
1148
1254
|
};
|
|
1149
1255
|
for (const p of elements2(root)) {
|
|
1150
1256
|
if (ln2(p) !== "p") continue;
|
|
1151
1257
|
drawPara(p, ML, MT, BODY_W, ctx, 0, paraSegPages.get(p));
|
|
1152
1258
|
}
|
|
1153
|
-
const pageH = nPages === 1 ? Math.max(PH, MT + maxTopV + 2e3) : PH;
|
|
1154
1259
|
return { pages: ctx.pages, pageH };
|
|
1155
1260
|
}
|
|
1156
|
-
|
|
1261
|
+
var SVG_FONT_FAMILY = `'HCR Batang','\uD568\uCD08\uB86C\uBC14\uD0D5','Hancom Batang',AppleMyungjo,'Noto Serif CJK KR',serif`;
|
|
1262
|
+
async function renderHwpxInternal(input, options) {
|
|
1157
1263
|
const maxImg = options?.maxImageBytes ?? 40 * 1024 * 1024;
|
|
1158
1264
|
const ab = input instanceof Uint8Array ? input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) : input;
|
|
1159
1265
|
precheckZipSize(ab, MAX_DECOMPRESS_SIZE, MAX_ZIP_ENTRIES);
|
|
@@ -1220,8 +1326,11 @@ async function renderHwpxToSvg(input, options) {
|
|
|
1220
1326
|
highlights: (options?.highlights ?? []).map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0),
|
|
1221
1327
|
warnings,
|
|
1222
1328
|
warned: /* @__PURE__ */ new Set(),
|
|
1223
|
-
stats: { texts: 0, images: 0, tables: 0 },
|
|
1224
|
-
extentMemo: { cell: /* @__PURE__ */ new WeakMap(), table: /* @__PURE__ */ new WeakMap() }
|
|
1329
|
+
stats: { texts: 0, images: 0, tables: 0, shapes: 0 },
|
|
1330
|
+
extentMemo: { cell: /* @__PURE__ */ new WeakMap(), table: /* @__PURE__ */ new WeakMap() },
|
|
1331
|
+
regions: new RegionCollector(),
|
|
1332
|
+
parentStack: [],
|
|
1333
|
+
pageBase: 0
|
|
1225
1334
|
};
|
|
1226
1335
|
const rendered = [];
|
|
1227
1336
|
let noCacheSkipped = false;
|
|
@@ -1242,6 +1351,7 @@ async function renderHwpxToSvg(input, options) {
|
|
|
1242
1351
|
const geom = readSectionGeom(root);
|
|
1243
1352
|
const { pages, pageH } = renderSectionToPages(root, geom, ctxBase, !!options?.reflow, options?.reflowMode ?? "keep");
|
|
1244
1353
|
rendered.push({ pages, PW: geom.PW, pageH, clipId: `pgclip${si}` });
|
|
1354
|
+
ctxBase.pageBase += pages.length;
|
|
1245
1355
|
}
|
|
1246
1356
|
if (rendered.length === 0) {
|
|
1247
1357
|
if (noCacheSkipped) {
|
|
@@ -1249,19 +1359,23 @@ async function renderHwpxToSvg(input, options) {
|
|
|
1249
1359
|
}
|
|
1250
1360
|
throw new KordocError("\uB80C\uB354\uD560 \uAD6C\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 HWPX\uAC00 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
|
|
1251
1361
|
}
|
|
1362
|
+
return { sections: rendered, defs: ctxBase.defs, warnings, stats: ctxBase.stats, regions: ctxBase.regions.regions };
|
|
1363
|
+
}
|
|
1364
|
+
async function renderHwpxToSvg(input, options) {
|
|
1365
|
+
const r = await renderHwpxInternal(input, options);
|
|
1252
1366
|
const GAP = 2400;
|
|
1253
1367
|
const clipDefs = [];
|
|
1254
1368
|
const groups = [];
|
|
1255
1369
|
let y = 0;
|
|
1256
1370
|
let maxPW = 0;
|
|
1257
|
-
let
|
|
1258
|
-
for (const rs of
|
|
1371
|
+
let pageNo2 = 0;
|
|
1372
|
+
for (const rs of r.sections) {
|
|
1259
1373
|
maxPW = Math.max(maxPW, rs.PW);
|
|
1260
1374
|
clipDefs.push(`<clipPath id="${rs.clipId}"><rect x="0" y="0" width="${pt(rs.PW)}" height="${pt(rs.pageH)}"/></clipPath>`);
|
|
1261
1375
|
for (const buf of rs.pages) {
|
|
1262
|
-
|
|
1376
|
+
pageNo2++;
|
|
1263
1377
|
groups.push(
|
|
1264
|
-
`<g data-page="${
|
|
1378
|
+
`<g data-page="${pageNo2}" transform="translate(0 ${pt(y)})"><rect width="${pt(rs.PW)}" height="${pt(rs.pageH)}" fill="white" stroke="#c9c7c4" stroke-width="0.75"/><g clip-path="url(#${rs.clipId})">
|
|
1265
1379
|
${buf.join("\n")}
|
|
1266
1380
|
</g></g>`
|
|
1267
1381
|
);
|
|
@@ -1269,21 +1383,674 @@ ${buf.join("\n")}
|
|
|
1269
1383
|
}
|
|
1270
1384
|
}
|
|
1271
1385
|
const totalH = Math.max(0, y - GAP);
|
|
1272
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pt(maxPW)} ${pt(totalH)}" width="${pt(maxPW)}pt" height="${pt(totalH)}pt" font-family="
|
|
1273
|
-
<defs>${clipDefs.join("")}${
|
|
1386
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pt(maxPW)} ${pt(totalH)}" width="${pt(maxPW)}pt" height="${pt(totalH)}pt" font-family="${SVG_FONT_FAMILY}" xml:space="preserve">
|
|
1387
|
+
<defs>${clipDefs.join("")}${r.defs.join("")}</defs>
|
|
1274
1388
|
${groups.join("\n")}
|
|
1275
1389
|
</svg>`;
|
|
1276
|
-
return { svg, width: Math.round(maxPW) / 100, height: Math.round(totalH) / 100, pageCount:
|
|
1390
|
+
return { svg, width: Math.round(maxPW) / 100, height: Math.round(totalH) / 100, pageCount: pageNo2, warnings: r.warnings, stats: r.stats, regions: r.regions };
|
|
1391
|
+
}
|
|
1392
|
+
async function renderHwpxPages(input, options, select) {
|
|
1393
|
+
const r = await renderHwpxInternal(input, options);
|
|
1394
|
+
const pages = [];
|
|
1395
|
+
const pageSvgs = /* @__PURE__ */ new Map();
|
|
1396
|
+
const pageCount = r.sections.reduce((n, rs) => n + rs.pages.length, 0);
|
|
1397
|
+
const chosen = typeof select === "function" ? select(pageCount) : select;
|
|
1398
|
+
let pageNo2 = 0;
|
|
1399
|
+
for (const rs of r.sections) {
|
|
1400
|
+
for (const buf of rs.pages) {
|
|
1401
|
+
pageNo2++;
|
|
1402
|
+
pages.push({ page: pageNo2, width: ptNum(rs.PW), height: ptNum(rs.pageH) });
|
|
1403
|
+
if (chosen && !chosen.has(pageNo2)) continue;
|
|
1404
|
+
const body = buf.join("\n");
|
|
1405
|
+
const used = /* @__PURE__ */ new Set();
|
|
1406
|
+
for (const m of body.matchAll(/href="#(bin\d+)"/g)) used.add(m[1]);
|
|
1407
|
+
const defs = r.defs.filter((d) => {
|
|
1408
|
+
const m = /<symbol id="(bin\d+)"/.exec(d);
|
|
1409
|
+
return m ? used.has(m[1]) : true;
|
|
1410
|
+
});
|
|
1411
|
+
const clipId = `pgclip${pageNo2}`;
|
|
1412
|
+
pageSvgs.set(
|
|
1413
|
+
pageNo2,
|
|
1414
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${pt(rs.PW)} ${pt(rs.pageH)}" width="${pt(rs.PW)}pt" height="${pt(rs.pageH)}pt" font-family="${SVG_FONT_FAMILY}" xml:space="preserve" data-page="${pageNo2}">
|
|
1415
|
+
<defs><clipPath id="${clipId}"><rect x="0" y="0" width="${pt(rs.PW)}" height="${pt(rs.pageH)}"/></clipPath>${defs.join("")}</defs>
|
|
1416
|
+
<rect width="${pt(rs.PW)}" height="${pt(rs.pageH)}" fill="white"/>
|
|
1417
|
+
<g clip-path="url(#${clipId})">
|
|
1418
|
+
${body}
|
|
1419
|
+
</g>
|
|
1420
|
+
</svg>`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
const scene = { format: "hwpx", pages, regions: r.regions, warnings: r.warnings, stats: { texts: r.stats.texts, tables: r.stats.tables, images: r.stats.images, shapes: r.stats.shapes } };
|
|
1425
|
+
return { scene, pageSvgs };
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/render/document.ts
|
|
1429
|
+
import { readFile } from "fs/promises";
|
|
1430
|
+
|
|
1431
|
+
// src/detect.ts
|
|
1432
|
+
import JSZip2 from "jszip";
|
|
1433
|
+
|
|
1434
|
+
// src/hwp5/cfb-lenient.ts
|
|
1435
|
+
var CFB_MAGIC = Buffer.from([208, 207, 17, 224, 161, 177, 26, 225]);
|
|
1436
|
+
var END_OF_CHAIN = 4294967294;
|
|
1437
|
+
var FREE_SECT = 4294967295;
|
|
1438
|
+
var MAX_CHAIN_LENGTH = 1e6;
|
|
1439
|
+
var MAX_DIR_ENTRIES = 1e5;
|
|
1440
|
+
var MAX_STREAM_SIZE = 100 * 1024 * 1024;
|
|
1441
|
+
function parseLenientCfb(data) {
|
|
1442
|
+
if (data.length < 512) throw new Error("CFB \uD30C\uC77C\uC774 \uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4 (\uCD5C\uC18C 512\uBC14\uC774\uD2B8)");
|
|
1443
|
+
if (!data.subarray(0, 8).equals(CFB_MAGIC)) throw new Error("CFB \uB9E4\uC9C1 \uBC14\uC774\uD2B8 \uBD88\uC77C\uCE58");
|
|
1444
|
+
const sectorSizeShift = data.readUInt16LE(30);
|
|
1445
|
+
if (sectorSizeShift < 7 || sectorSizeShift > 16) throw new Error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uC139\uD130 \uD06C\uAE30 \uC2DC\uD504\uD2B8: " + sectorSizeShift);
|
|
1446
|
+
const sectorSize = 1 << sectorSizeShift;
|
|
1447
|
+
const miniSectorSizeShift = data.readUInt16LE(32);
|
|
1448
|
+
if (miniSectorSizeShift > 16) throw new Error("\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uBBF8\uB2C8 \uC139\uD130 \uD06C\uAE30 \uC2DC\uD504\uD2B8: " + miniSectorSizeShift);
|
|
1449
|
+
const miniSectorSize = 1 << miniSectorSizeShift;
|
|
1450
|
+
const headerFatSectorCount = data.readUInt32LE(44);
|
|
1451
|
+
if (headerFatSectorCount > 1e4) throw new Error("FAT \uC139\uD130 \uC218\uAC00 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4: " + headerFatSectorCount);
|
|
1452
|
+
const maxRealSectors = Math.ceil(Math.max(0, data.length - 512) / sectorSize);
|
|
1453
|
+
const maxFatSectors = Math.max(1, Math.ceil(maxRealSectors / (sectorSize / 4)));
|
|
1454
|
+
const fatSectorCount = Math.min(headerFatSectorCount, maxFatSectors);
|
|
1455
|
+
const firstDirSector = data.readUInt32LE(48);
|
|
1456
|
+
const miniStreamCutoff = data.readUInt32LE(56);
|
|
1457
|
+
const firstMiniFatSector = data.readUInt32LE(60);
|
|
1458
|
+
const miniFatSectorCount = data.readUInt32LE(64);
|
|
1459
|
+
const firstDifatSector = data.readUInt32LE(68);
|
|
1460
|
+
const difatSectorCount = data.readUInt32LE(72);
|
|
1461
|
+
function sectorOffset(id) {
|
|
1462
|
+
return 512 + id * sectorSize;
|
|
1463
|
+
}
|
|
1464
|
+
function readSectorData(id) {
|
|
1465
|
+
const off = sectorOffset(id);
|
|
1466
|
+
if (off + sectorSize > data.length) return Buffer.alloc(0);
|
|
1467
|
+
return data.subarray(off, off + sectorSize);
|
|
1468
|
+
}
|
|
1469
|
+
const fatSectors = [];
|
|
1470
|
+
for (let i = 0; i < 109 && fatSectors.length < fatSectorCount; i++) {
|
|
1471
|
+
const sid = data.readUInt32LE(76 + i * 4);
|
|
1472
|
+
if (sid === FREE_SECT || sid === END_OF_CHAIN) break;
|
|
1473
|
+
fatSectors.push(sid);
|
|
1474
|
+
}
|
|
1475
|
+
let difatSector = firstDifatSector;
|
|
1476
|
+
const visitedDifat = /* @__PURE__ */ new Set();
|
|
1477
|
+
for (let d = 0; d < difatSectorCount && difatSector !== END_OF_CHAIN && difatSector !== FREE_SECT; d++) {
|
|
1478
|
+
if (visitedDifat.has(difatSector)) break;
|
|
1479
|
+
visitedDifat.add(difatSector);
|
|
1480
|
+
const buf = readSectorData(difatSector);
|
|
1481
|
+
if (buf.length < sectorSize) break;
|
|
1482
|
+
const entriesPerSector = sectorSize / 4 - 1;
|
|
1483
|
+
for (let i = 0; i < entriesPerSector && fatSectors.length < fatSectorCount; i++) {
|
|
1484
|
+
const sid = buf.readUInt32LE(i * 4);
|
|
1485
|
+
if (sid === FREE_SECT || sid === END_OF_CHAIN) continue;
|
|
1486
|
+
fatSectors.push(sid);
|
|
1487
|
+
}
|
|
1488
|
+
difatSector = buf.readUInt32LE(entriesPerSector * 4);
|
|
1489
|
+
}
|
|
1490
|
+
const entriesPerFatSector = sectorSize / 4;
|
|
1491
|
+
const fatTable = new Uint32Array(fatSectors.length * entriesPerFatSector);
|
|
1492
|
+
for (let fi = 0; fi < fatSectors.length; fi++) {
|
|
1493
|
+
const buf = readSectorData(fatSectors[fi]);
|
|
1494
|
+
for (let i = 0; i < entriesPerFatSector; i++) {
|
|
1495
|
+
fatTable[fi * entriesPerFatSector + i] = i * 4 + 3 < buf.length ? buf.readUInt32LE(i * 4) : FREE_SECT;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
function readChain(startSector, maxBytes) {
|
|
1499
|
+
if (startSector === END_OF_CHAIN || startSector === FREE_SECT) return Buffer.alloc(0);
|
|
1500
|
+
if (maxBytes > MAX_STREAM_SIZE) throw new Error("\uC2A4\uD2B8\uB9BC\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4");
|
|
1501
|
+
const chunks = [];
|
|
1502
|
+
let current = startSector;
|
|
1503
|
+
let totalRead = 0;
|
|
1504
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1505
|
+
while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < maxBytes) {
|
|
1506
|
+
if (visited.has(current)) break;
|
|
1507
|
+
if (visited.size > MAX_CHAIN_LENGTH) break;
|
|
1508
|
+
visited.add(current);
|
|
1509
|
+
const buf = readSectorData(current);
|
|
1510
|
+
const remaining = maxBytes - totalRead;
|
|
1511
|
+
chunks.push(remaining < sectorSize ? buf.subarray(0, remaining) : buf);
|
|
1512
|
+
totalRead += Math.min(buf.length, remaining);
|
|
1513
|
+
current = current < fatTable.length ? fatTable[current] : END_OF_CHAIN;
|
|
1514
|
+
}
|
|
1515
|
+
return Buffer.concat(chunks);
|
|
1516
|
+
}
|
|
1517
|
+
let miniFatTable = null;
|
|
1518
|
+
function getMiniFatTable() {
|
|
1519
|
+
if (miniFatTable) return miniFatTable;
|
|
1520
|
+
if (miniFatSectorCount === 0 || firstMiniFatSector === END_OF_CHAIN) {
|
|
1521
|
+
miniFatTable = new Uint32Array(0);
|
|
1522
|
+
return miniFatTable;
|
|
1523
|
+
}
|
|
1524
|
+
const miniFatData = readChain(firstMiniFatSector, miniFatSectorCount * sectorSize);
|
|
1525
|
+
const entries = miniFatData.length / 4;
|
|
1526
|
+
miniFatTable = new Uint32Array(entries);
|
|
1527
|
+
for (let i = 0; i < entries; i++) {
|
|
1528
|
+
miniFatTable[i] = miniFatData.readUInt32LE(i * 4);
|
|
1529
|
+
}
|
|
1530
|
+
return miniFatTable;
|
|
1531
|
+
}
|
|
1532
|
+
const dirData = readChain(firstDirSector, MAX_DIR_ENTRIES * 128);
|
|
1533
|
+
const dirEntries = [];
|
|
1534
|
+
for (let offset = 0; offset + 128 <= dirData.length && dirEntries.length < MAX_DIR_ENTRIES; offset += 128) {
|
|
1535
|
+
const nameLen = dirData.readUInt16LE(offset + 64);
|
|
1536
|
+
if (nameLen <= 0 || nameLen > 64) {
|
|
1537
|
+
dirEntries.push({ name: "", type: 0, startSector: 0, size: 0 });
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const nameBytes = nameLen - 2;
|
|
1541
|
+
const name = nameBytes > 0 ? dirData.subarray(offset, offset + nameBytes).toString("utf16le") : "";
|
|
1542
|
+
const type = dirData[offset + 66];
|
|
1543
|
+
const startSector = dirData.readUInt32LE(offset + 116);
|
|
1544
|
+
const size = dirData.readUInt32LE(offset + 120);
|
|
1545
|
+
dirEntries.push({ name, type, startSector, size });
|
|
1546
|
+
}
|
|
1547
|
+
let miniStreamData = null;
|
|
1548
|
+
function getMiniStream() {
|
|
1549
|
+
if (miniStreamData) return miniStreamData;
|
|
1550
|
+
const root = dirEntries[0];
|
|
1551
|
+
if (!root || root.type !== 5) {
|
|
1552
|
+
miniStreamData = Buffer.alloc(0);
|
|
1553
|
+
return miniStreamData;
|
|
1554
|
+
}
|
|
1555
|
+
miniStreamData = readChain(root.startSector, root.size || MAX_STREAM_SIZE);
|
|
1556
|
+
return miniStreamData;
|
|
1557
|
+
}
|
|
1558
|
+
function readMiniStream(startSector, size) {
|
|
1559
|
+
const mft = getMiniFatTable();
|
|
1560
|
+
const ms = getMiniStream();
|
|
1561
|
+
if (mft.length === 0 || ms.length === 0) return Buffer.alloc(0);
|
|
1562
|
+
const chunks = [];
|
|
1563
|
+
let current = startSector;
|
|
1564
|
+
let totalRead = 0;
|
|
1565
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1566
|
+
while (current !== END_OF_CHAIN && current !== FREE_SECT && totalRead < size) {
|
|
1567
|
+
if (visited.has(current)) break;
|
|
1568
|
+
if (visited.size > MAX_CHAIN_LENGTH) break;
|
|
1569
|
+
visited.add(current);
|
|
1570
|
+
const off = current * miniSectorSize;
|
|
1571
|
+
const remaining = size - totalRead;
|
|
1572
|
+
const chunkSize = Math.min(miniSectorSize, remaining);
|
|
1573
|
+
if (off + chunkSize <= ms.length) {
|
|
1574
|
+
chunks.push(ms.subarray(off, off + chunkSize));
|
|
1575
|
+
}
|
|
1576
|
+
totalRead += chunkSize;
|
|
1577
|
+
current = current < mft.length ? mft[current] : END_OF_CHAIN;
|
|
1578
|
+
}
|
|
1579
|
+
return Buffer.concat(chunks);
|
|
1580
|
+
}
|
|
1581
|
+
function readStreamData(entry) {
|
|
1582
|
+
if (entry.size === 0) return Buffer.alloc(0);
|
|
1583
|
+
if (entry.size < miniStreamCutoff) {
|
|
1584
|
+
const miniResult = readMiniStream(entry.startSector, entry.size);
|
|
1585
|
+
if (miniResult.length > 0) return miniResult;
|
|
1586
|
+
}
|
|
1587
|
+
return readChain(entry.startSector, entry.size);
|
|
1588
|
+
}
|
|
1589
|
+
function findEntryByPath(path) {
|
|
1590
|
+
const parts = path.replace(/^\//, "").split("/");
|
|
1591
|
+
if (parts.length === 1) {
|
|
1592
|
+
return dirEntries.find((e) => e.name === parts[0] && e.type === 2) ?? null;
|
|
1593
|
+
}
|
|
1594
|
+
const storageName = parts[0];
|
|
1595
|
+
const streamName = parts.slice(1).join("/");
|
|
1596
|
+
for (const e of dirEntries) {
|
|
1597
|
+
if (e.type === 2 && e.name === streamName) {
|
|
1598
|
+
return e;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
const lastPart = parts[parts.length - 1];
|
|
1602
|
+
return dirEntries.find((e) => e.type === 2 && e.name === lastPart) ?? null;
|
|
1603
|
+
}
|
|
1604
|
+
return {
|
|
1605
|
+
findStream(path) {
|
|
1606
|
+
const normalized = path.replace(/^\//, "");
|
|
1607
|
+
const entry = findEntryByPath(normalized);
|
|
1608
|
+
if (!entry || entry.type !== 2) return null;
|
|
1609
|
+
const stream = readStreamData(entry);
|
|
1610
|
+
return stream.length > 0 ? stream : null;
|
|
1611
|
+
},
|
|
1612
|
+
entries() {
|
|
1613
|
+
return dirEntries.filter((e) => e.type === 2);
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// src/detect.ts
|
|
1619
|
+
function magicBytes(buffer) {
|
|
1620
|
+
return new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength));
|
|
1621
|
+
}
|
|
1622
|
+
function isZipFile(buffer) {
|
|
1623
|
+
const b = magicBytes(buffer);
|
|
1624
|
+
return b[0] === 80 && b[1] === 75 && b[2] === 3 && b[3] === 4;
|
|
1625
|
+
}
|
|
1626
|
+
function isHwpxFile(buffer) {
|
|
1627
|
+
return isZipFile(buffer);
|
|
1628
|
+
}
|
|
1629
|
+
function isOldHwpFile(buffer) {
|
|
1630
|
+
const b = magicBytes(buffer);
|
|
1631
|
+
return b[0] === 208 && b[1] === 207 && b[2] === 17 && b[3] === 224;
|
|
1632
|
+
}
|
|
1633
|
+
var HWP3_PREFIX = new TextEncoder().encode("HWP Document File V3.00");
|
|
1634
|
+
function isHwp3File(buffer) {
|
|
1635
|
+
if (buffer.byteLength < HWP3_PREFIX.length) return false;
|
|
1636
|
+
const head = new Uint8Array(buffer, 0, HWP3_PREFIX.length);
|
|
1637
|
+
for (let i = 0; i < HWP3_PREFIX.length; i++) {
|
|
1638
|
+
if (head[i] !== HWP3_PREFIX[i]) return false;
|
|
1639
|
+
}
|
|
1640
|
+
return true;
|
|
1641
|
+
}
|
|
1642
|
+
function isPdfFile(buffer) {
|
|
1643
|
+
const b = magicBytes(buffer);
|
|
1644
|
+
return b[0] === 37 && b[1] === 80 && b[2] === 68 && b[3] === 70;
|
|
1645
|
+
}
|
|
1646
|
+
function isImageFile(buffer) {
|
|
1647
|
+
if (buffer.byteLength < 12) return false;
|
|
1648
|
+
const b = new Uint8Array(buffer, 0, 12);
|
|
1649
|
+
if (b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return true;
|
|
1650
|
+
if (b[0] === 255 && b[1] === 216 && b[2] === 255) return true;
|
|
1651
|
+
if (b[0] === 82 && b[1] === 73 && b[2] === 70 && b[3] === 70 && b[8] === 87 && b[9] === 69 && b[10] === 66 && b[11] === 80) return true;
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
function isHwpmlFile(buffer) {
|
|
1655
|
+
const bytes = new Uint8Array(buffer, 0, Math.min(512, buffer.byteLength));
|
|
1656
|
+
const head = new TextDecoder("utf-8", { fatal: false }).decode(bytes).replace(/^\uFEFF/, "");
|
|
1657
|
+
return head.trimStart().startsWith("<?xml") && head.includes("<HWPML");
|
|
1658
|
+
}
|
|
1659
|
+
function detectFormat(buffer) {
|
|
1660
|
+
if (buffer.byteLength < 4) return "unknown";
|
|
1661
|
+
if (isHwp3File(buffer)) return "hwp3";
|
|
1662
|
+
if (isZipFile(buffer)) return "hwpx";
|
|
1663
|
+
if (isOldHwpFile(buffer)) return "hwp";
|
|
1664
|
+
if (isPdfFile(buffer)) return "pdf";
|
|
1665
|
+
if (isHwpmlFile(buffer)) return "hwpml";
|
|
1666
|
+
if (isImageFile(buffer)) return "image";
|
|
1667
|
+
return "unknown";
|
|
1668
|
+
}
|
|
1669
|
+
function detectOle2Format(buffer) {
|
|
1670
|
+
try {
|
|
1671
|
+
const cfb = parseLenientCfb(Buffer.from(buffer));
|
|
1672
|
+
const names = cfb.entries().map((e) => e.name);
|
|
1673
|
+
if (names.includes("Workbook") || names.includes("Book")) return "xls";
|
|
1674
|
+
if (names.includes("FileHeader")) return "hwp";
|
|
1675
|
+
if (names.some((n) => n === "DocInfo" || n.startsWith("Section"))) return "hwp";
|
|
1676
|
+
return "unknown";
|
|
1677
|
+
} catch {
|
|
1678
|
+
return "unknown";
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
async function detectZipFormat(buffer) {
|
|
1682
|
+
try {
|
|
1683
|
+
const zip = await JSZip2.loadAsync(buffer);
|
|
1684
|
+
if (zip.file("xl/workbook.xml")) return "xlsx";
|
|
1685
|
+
if (zip.file("word/document.xml")) return "docx";
|
|
1686
|
+
if (zip.file("Contents/content.hpf") || zip.file("mimetype")) return "hwpx";
|
|
1687
|
+
const hasSection = Object.keys(zip.files).some((f) => f.startsWith("Contents/"));
|
|
1688
|
+
if (hasSection) return "hwpx";
|
|
1689
|
+
return "unknown";
|
|
1690
|
+
} catch {
|
|
1691
|
+
return "unknown";
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// src/render/html.ts
|
|
1696
|
+
function renderSceneToHtml(scene, pageSvgs, options = {}) {
|
|
1697
|
+
const title = escapeXml(options.title ?? "kordoc");
|
|
1698
|
+
const first = scene.pages.find((p) => pageSvgs.has(p.page)) ?? scene.pages[0];
|
|
1699
|
+
const pageSize = first ? `${first.width}pt ${first.height}pt` : "auto";
|
|
1700
|
+
const pages = [...pageSvgs.keys()].sort((a, b) => a - b).map((n) => {
|
|
1701
|
+
const meta = scene.pages.find((p) => p.page === n);
|
|
1702
|
+
const size = meta ? ` style="width:${meta.width}pt;height:${meta.height}pt"` : "";
|
|
1703
|
+
return `<div class="kordoc-page" data-page="${n}"${size}>${pageSvgs.get(n)}</div>`;
|
|
1704
|
+
});
|
|
1705
|
+
return `<!doctype html>
|
|
1706
|
+
<html lang="ko">
|
|
1707
|
+
<head>
|
|
1708
|
+
<meta charset="utf-8">
|
|
1709
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1710
|
+
<title>${title}</title>
|
|
1711
|
+
<style>
|
|
1712
|
+
html, body { margin: 0; background: #e9e8e6; }
|
|
1713
|
+
.kordoc-document { display: flex; flex-direction: column; align-items: center; gap: 16px; padding: 16px 0; }
|
|
1714
|
+
.kordoc-page { position: relative; background: white; box-shadow: 0 1px 4px rgba(0,0,0,.25); }
|
|
1715
|
+
.kordoc-page > svg { display: block; width: 100%; height: 100%; }
|
|
1716
|
+
@page { size: ${pageSize}; margin: 0; }
|
|
1717
|
+
@media print {
|
|
1718
|
+
html, body { background: white; }
|
|
1719
|
+
.kordoc-document { gap: 0; padding: 0; }
|
|
1720
|
+
.kordoc-page { box-shadow: none; break-after: page; page-break-after: always; }
|
|
1721
|
+
.kordoc-page:last-child { break-after: auto; page-break-after: auto; }
|
|
1722
|
+
}
|
|
1723
|
+
</style>
|
|
1724
|
+
</head>
|
|
1725
|
+
<body>
|
|
1726
|
+
<div class="kordoc-document">
|
|
1727
|
+
${pages.join("\n")}
|
|
1728
|
+
</div>
|
|
1729
|
+
</body>
|
|
1730
|
+
</html>
|
|
1731
|
+
`;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/print/renderer.ts
|
|
1735
|
+
import { existsSync } from "fs";
|
|
1736
|
+
import MarkdownIt from "markdown-it";
|
|
1737
|
+
var PRESETS = {
|
|
1738
|
+
default: `
|
|
1739
|
+
@page { size: A4; margin: 20mm; }
|
|
1740
|
+
body { font-family: 'Pretendard', 'Malgun Gothic', '\uB9D1\uC740 \uACE0\uB515', sans-serif; font-size: 11pt; line-height: 1.6; color: #111; }
|
|
1741
|
+
h1 { font-size: 20pt; margin: 1em 0 0.5em; }
|
|
1742
|
+
h2 { font-size: 16pt; margin: 1em 0 0.4em; }
|
|
1743
|
+
h3 { font-size: 13pt; margin: 0.8em 0 0.3em; }
|
|
1744
|
+
p { margin: 0.4em 0; }
|
|
1745
|
+
table { border-collapse: collapse; margin: 0.6em 0; width: 100%; }
|
|
1746
|
+
th, td { border: 1px solid #555; padding: 4px 8px; text-align: left; vertical-align: top; }
|
|
1747
|
+
th { background: #f0f0f0; }
|
|
1748
|
+
code { background: #f5f5f5; padding: 1px 4px; border-radius: 2px; font-family: 'D2Coding', Consolas, monospace; }
|
|
1749
|
+
pre { background: #f5f5f5; padding: 8px; border-radius: 4px; overflow-x: auto; }
|
|
1750
|
+
blockquote { border-left: 3px solid #ccc; padding-left: 12px; color: #555; margin: 0.6em 0; }
|
|
1751
|
+
img { max-width: 100%; }
|
|
1752
|
+
`,
|
|
1753
|
+
"gov-formal": `
|
|
1754
|
+
@page { size: A4; margin: 25mm 20mm; }
|
|
1755
|
+
body { font-family: '\uD568\uCD08\uB86C\uBC14\uD0D5', 'HCR Batang', 'Batang', 'Malgun Gothic', serif; font-size: 11pt; line-height: 1.7; color: #000; }
|
|
1756
|
+
h1 { font-size: 18pt; text-align: center; margin: 0.5em 0 1em; letter-spacing: 0.05em; }
|
|
1757
|
+
h2 { font-size: 14pt; margin: 1em 0 0.4em; border-bottom: 1px solid #999; padding-bottom: 2px; }
|
|
1758
|
+
h3 { font-size: 12pt; margin: 0.8em 0 0.3em; }
|
|
1759
|
+
p { margin: 0.3em 0; text-indent: 1em; }
|
|
1760
|
+
table { border-collapse: collapse; margin: 0.8em 0; width: 100%; }
|
|
1761
|
+
th, td { border: 1px solid #000; padding: 5px 8px; vertical-align: top; }
|
|
1762
|
+
th { background: #e8e8e8; font-weight: normal; }
|
|
1763
|
+
blockquote { border-left: 2px solid #555; padding-left: 12px; margin: 0.6em 0; }
|
|
1764
|
+
`,
|
|
1765
|
+
compact: `
|
|
1766
|
+
@page { size: A4; margin: 10mm; }
|
|
1767
|
+
body { font-family: 'Pretendard', 'Malgun Gothic', sans-serif; font-size: 9pt; line-height: 1.4; color: #111; }
|
|
1768
|
+
h1 { font-size: 14pt; margin: 0.5em 0 0.3em; }
|
|
1769
|
+
h2 { font-size: 12pt; margin: 0.5em 0 0.3em; }
|
|
1770
|
+
h3 { font-size: 10pt; margin: 0.4em 0 0.2em; }
|
|
1771
|
+
p { margin: 0.2em 0; }
|
|
1772
|
+
table { border-collapse: collapse; margin: 0.3em 0; width: 100%; font-size: 8pt; }
|
|
1773
|
+
th, td { border: 1px solid #777; padding: 2px 4px; }
|
|
1774
|
+
th { background: #f0f0f0; }
|
|
1775
|
+
`
|
|
1776
|
+
};
|
|
1777
|
+
var md = new MarkdownIt({
|
|
1778
|
+
html: true,
|
|
1779
|
+
linkify: true,
|
|
1780
|
+
breaks: false
|
|
1781
|
+
});
|
|
1782
|
+
function renderHtml(markdown, options) {
|
|
1783
|
+
const preset = options?.preset ?? "default";
|
|
1784
|
+
const css = PRESETS[preset] + (options?.extraCss ?? "");
|
|
1785
|
+
const body = md.render(markdown);
|
|
1786
|
+
const watermark = options?.watermark ? `<div class="watermark">${escapeHtml(options.watermark)}</div>` : "";
|
|
1787
|
+
const watermarkCss = options?.watermark ? `
|
|
1788
|
+
.watermark {
|
|
1789
|
+
position: fixed;
|
|
1790
|
+
top: 50%; left: 50%;
|
|
1791
|
+
transform: translate(-50%, -50%) rotate(-30deg);
|
|
1792
|
+
font-size: 80pt;
|
|
1793
|
+
color: rgba(0,0,0,0.08);
|
|
1794
|
+
pointer-events: none;
|
|
1795
|
+
z-index: 9999;
|
|
1796
|
+
white-space: nowrap;
|
|
1797
|
+
}` : "";
|
|
1798
|
+
return `<!DOCTYPE html>
|
|
1799
|
+
<html lang="ko">
|
|
1800
|
+
<head>
|
|
1801
|
+
<meta charset="utf-8">
|
|
1802
|
+
<style>${css}${watermarkCss}</style>
|
|
1803
|
+
</head>
|
|
1804
|
+
<body>
|
|
1805
|
+
${watermark}
|
|
1806
|
+
${body}
|
|
1807
|
+
</body>
|
|
1808
|
+
</html>`;
|
|
1809
|
+
}
|
|
1810
|
+
function escapeHtml(s) {
|
|
1811
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1812
|
+
}
|
|
1813
|
+
async function htmlToPdf(html, options) {
|
|
1814
|
+
let puppeteer;
|
|
1815
|
+
try {
|
|
1816
|
+
puppeteer = await import("puppeteer-core");
|
|
1817
|
+
} catch {
|
|
1818
|
+
throw new KordocError(
|
|
1819
|
+
"PDF \uC0DD\uC131\uC5D0 puppeteer-core\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC124\uCE58: npm install puppeteer-core"
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
const executablePath = process.env.PUPPETEER_EXECUTABLE_PATH ?? findChromiumPath();
|
|
1823
|
+
if (!executablePath) {
|
|
1824
|
+
throw new KordocError(
|
|
1825
|
+
"Chromium \uC2E4\uD589 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. PUPPETEER_EXECUTABLE_PATH \uD658\uACBD\uBCC0\uC218\uB97C \uC124\uC815\uD558\uC138\uC694."
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
const browser = await puppeteer.default.launch({
|
|
1829
|
+
executablePath,
|
|
1830
|
+
headless: true,
|
|
1831
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
1832
|
+
});
|
|
1833
|
+
try {
|
|
1834
|
+
const page = await browser.newPage();
|
|
1835
|
+
await page.setContent(html, { waitUntil: "networkidle0" });
|
|
1836
|
+
const margin = options?.margin;
|
|
1837
|
+
const pdf = await page.pdf({
|
|
1838
|
+
format: options?.pageSize ?? "A4",
|
|
1839
|
+
landscape: options?.orientation === "landscape",
|
|
1840
|
+
printBackground: true,
|
|
1841
|
+
margin: margin ? {
|
|
1842
|
+
top: toCss(margin.top),
|
|
1843
|
+
right: toCss(margin.right),
|
|
1844
|
+
bottom: toCss(margin.bottom),
|
|
1845
|
+
left: toCss(margin.left)
|
|
1846
|
+
} : void 0,
|
|
1847
|
+
displayHeaderFooter: !!(options?.header || options?.footer),
|
|
1848
|
+
headerTemplate: options?.header ?? "<div></div>",
|
|
1849
|
+
footerTemplate: options?.footer ?? '<div style="font-size:8pt;width:100%;text-align:center;color:#777;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>'
|
|
1850
|
+
});
|
|
1851
|
+
return Buffer.from(pdf);
|
|
1852
|
+
} finally {
|
|
1853
|
+
await browser.close();
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
function toCss(v) {
|
|
1857
|
+
return typeof v === "number" ? `${v}mm` : v;
|
|
1858
|
+
}
|
|
1859
|
+
function findChromiumPath() {
|
|
1860
|
+
const win = [
|
|
1861
|
+
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
1862
|
+
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
1863
|
+
process.env.LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe",
|
|
1864
|
+
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
1865
|
+
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"
|
|
1866
|
+
];
|
|
1867
|
+
const mac = [
|
|
1868
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
1869
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
|
|
1870
|
+
];
|
|
1871
|
+
const linux = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium"];
|
|
1872
|
+
const candidates = process.platform === "win32" ? win : process.platform === "darwin" ? mac : linux;
|
|
1873
|
+
for (const p of candidates) {
|
|
1874
|
+
if (p && existsSync(p)) return p;
|
|
1875
|
+
}
|
|
1876
|
+
return null;
|
|
1877
|
+
}
|
|
1878
|
+
async function markdownToPdf(markdown, options) {
|
|
1879
|
+
const html = renderHtml(markdown, options);
|
|
1880
|
+
return htmlToPdf(html, options);
|
|
1881
|
+
}
|
|
1882
|
+
async function blocksToPdf(blocks, options) {
|
|
1883
|
+
const markdown = blocksToMarkdown(blocks);
|
|
1884
|
+
return markdownToPdf(markdown, options);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// src/render/pdf.ts
|
|
1888
|
+
async function renderHtmlToPdf(html, options = {}) {
|
|
1889
|
+
let puppeteer;
|
|
1890
|
+
try {
|
|
1891
|
+
puppeteer = await import("puppeteer-core");
|
|
1892
|
+
} catch {
|
|
1893
|
+
throw new KordocError("PDF \uB80C\uB354\uC5D0\uB294 puppeteer-core \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (npm install puppeteer-core). SVG/HTML/PNG \uB294 Chromium \uC5C6\uC774 \uB429\uB2C8\uB2E4");
|
|
1894
|
+
}
|
|
1895
|
+
const executablePath = options.browserExecutablePath ?? process.env.PUPPETEER_EXECUTABLE_PATH ?? findChromiumPath();
|
|
1896
|
+
if (!executablePath) {
|
|
1897
|
+
throw new KordocError("Chromium \uC2E4\uD589 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 browserExecutablePath \uC635\uC158 \uB610\uB294 PUPPETEER_EXECUTABLE_PATH \uD658\uACBD\uBCC0\uC218\uB97C \uC9C0\uC815\uD558\uC138\uC694");
|
|
1898
|
+
}
|
|
1899
|
+
const browser = await puppeteer.default.launch({ executablePath, headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"] });
|
|
1900
|
+
try {
|
|
1901
|
+
const page = await browser.newPage();
|
|
1902
|
+
await page.setContent(html, { waitUntil: "load" });
|
|
1903
|
+
const pdf = await page.pdf({ printBackground: true, preferCSSPageSize: true });
|
|
1904
|
+
return Buffer.from(pdf);
|
|
1905
|
+
} finally {
|
|
1906
|
+
await browser.close();
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// src/render/document.ts
|
|
1911
|
+
async function loadBuffer(input) {
|
|
1912
|
+
if (typeof input === "string") {
|
|
1913
|
+
try {
|
|
1914
|
+
return toArrayBuffer(await readFile(input));
|
|
1915
|
+
} catch (err) {
|
|
1916
|
+
const code = err instanceof Error && "code" in err ? err.code : void 0;
|
|
1917
|
+
throw new KordocError(code === "ENOENT" ? `\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input}` : `\uD30C\uC77C \uC77D\uAE30 \uC2E4\uD328: ${input}`);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
if (Buffer.isBuffer(input)) return toArrayBuffer(input);
|
|
1921
|
+
if (input instanceof Uint8Array) return input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength);
|
|
1922
|
+
return input;
|
|
1923
|
+
}
|
|
1924
|
+
async function sourceFormatOf(buffer) {
|
|
1925
|
+
const format = detectFormat(buffer);
|
|
1926
|
+
if (format === "hwpx") {
|
|
1927
|
+
const zip = await detectZipFormat(buffer);
|
|
1928
|
+
if (zip === "hwpx" || zip === "unknown") return "hwpx";
|
|
1929
|
+
throw new KordocError(`\uB80C\uB354 \uBBF8\uC9C0\uC6D0 \uD615\uC2DD(${zip}) \u2014 \uB808\uC774\uC544\uC6C3 \uB80C\uB354\uB294 HWPX \uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4`);
|
|
1930
|
+
}
|
|
1931
|
+
if (format === "hwp") throw new KordocError("HWP5 \uB808\uC774\uC544\uC6C3 \uB80C\uB354\uB294 \uC544\uC9C1 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uD55C\uCEF4\uC5D0\uC11C HWPX \uB85C \uC800\uC7A5\uD55C \uB4A4 \uB80C\uB354\uD558\uC138\uC694 (#75 \uD6C4\uC18D)");
|
|
1932
|
+
throw new KordocError(`\uB80C\uB354 \uBBF8\uC9C0\uC6D0 \uD615\uC2DD(${format}) \u2014 \uB808\uC774\uC544\uC6C3 \uB80C\uB354\uB294 HWPX \uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4`);
|
|
1933
|
+
}
|
|
1934
|
+
async function renderDocumentToScene(input, options = {}) {
|
|
1935
|
+
const buffer = await loadBuffer(input);
|
|
1936
|
+
const source = await sourceFormatOf(buffer);
|
|
1937
|
+
if (source !== "hwpx") throw new KordocError("HWP5 \uB808\uC774\uC544\uC6C3 \uB80C\uB354\uB294 \uC544\uC9C1 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
|
|
1938
|
+
const select = options.pages !== void 0 ? (pageCount) => {
|
|
1939
|
+
const set = parsePageRange(options.pages, pageCount);
|
|
1940
|
+
if (set.size === 0) throw new KordocError(`\uC120\uD0DD\uD55C \uD398\uC774\uC9C0\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBB38\uC11C\uB294 ${pageCount}\uCABD (pages: ${JSON.stringify(options.pages)})`);
|
|
1941
|
+
return set;
|
|
1942
|
+
} : void 0;
|
|
1943
|
+
const { scene, pageSvgs } = await renderHwpxPages(buffer, {
|
|
1944
|
+
reflow: options.reflow ?? true,
|
|
1945
|
+
reflowMode: options.reflowMode,
|
|
1946
|
+
highlights: options.highlights,
|
|
1947
|
+
maxImageBytes: options.maxImageBytes
|
|
1948
|
+
}, select);
|
|
1949
|
+
return { scene, pageSvgs };
|
|
1950
|
+
}
|
|
1951
|
+
async function renderDocument(input, options) {
|
|
1952
|
+
const { scene, pageSvgs } = await renderDocumentToScene(input, options);
|
|
1953
|
+
const pageNos = [...pageSvgs.keys()].sort((a, b) => a - b);
|
|
1954
|
+
const sizeOf = (n) => scene.pages.find((p) => p.page === n);
|
|
1955
|
+
const assets = [];
|
|
1956
|
+
switch (options.format) {
|
|
1957
|
+
case "svg":
|
|
1958
|
+
for (const n of pageNos) {
|
|
1959
|
+
const p = sizeOf(n);
|
|
1960
|
+
assets.push({ format: "svg", page: n, data: pageSvgs.get(n), width: p.width, height: p.height, mimeType: "image/svg+xml" });
|
|
1961
|
+
}
|
|
1962
|
+
break;
|
|
1963
|
+
case "html":
|
|
1964
|
+
assets.push({ format: "html", data: renderSceneToHtml(scene, pageSvgs, { title: options.title }), mimeType: "text/html" });
|
|
1965
|
+
break;
|
|
1966
|
+
case "png":
|
|
1967
|
+
case "jpeg":
|
|
1968
|
+
for (const n of pageNos) {
|
|
1969
|
+
const p = sizeOf(n);
|
|
1970
|
+
const r = await rasterizePageSvg(pageSvgs.get(n), p.width, p.height, { format: options.format, maxWidthPx: options.maxWidthPx, quality: options.quality });
|
|
1971
|
+
assets.push({ format: options.format, page: n, data: r.data, width: r.widthPx, height: r.heightPx, scale: r.scale, mimeType: r.mimeType });
|
|
1972
|
+
}
|
|
1973
|
+
break;
|
|
1974
|
+
case "pdf": {
|
|
1975
|
+
const html = renderSceneToHtml(scene, pageSvgs, { title: options.title });
|
|
1976
|
+
assets.push({ format: "pdf", data: await renderHtmlToPdf(html, { browserExecutablePath: options.browserExecutablePath }), mimeType: "application/pdf" });
|
|
1977
|
+
break;
|
|
1978
|
+
}
|
|
1979
|
+
default:
|
|
1980
|
+
throw new KordocError(`\uC54C \uC218 \uC5C6\uB294 \uB80C\uB354 \uD3EC\uB9F7: ${String(options.format)} (svg|html|png|jpeg|pdf)`);
|
|
1981
|
+
}
|
|
1982
|
+
return { scene, assets };
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// src/render/regions.ts
|
|
1986
|
+
function cropRect(bbox, scale, pageWidthPx, pageHeightPx, paddingPt = 0) {
|
|
1987
|
+
const W = Math.max(1, Math.floor(pageWidthPx)), H = Math.max(1, Math.floor(pageHeightPx));
|
|
1988
|
+
const clamp = (v, hi) => Math.min(Math.max(0, v), hi);
|
|
1989
|
+
let left = clamp(Math.floor((bbox.x - paddingPt) * scale), W - 1);
|
|
1990
|
+
let top = clamp(Math.floor((bbox.y - paddingPt) * scale), H - 1);
|
|
1991
|
+
const right = clamp(Math.ceil((bbox.x + bbox.width + paddingPt) * scale), W);
|
|
1992
|
+
const bottom = clamp(Math.ceil((bbox.y + bbox.height + paddingPt) * scale), H);
|
|
1993
|
+
const width = Math.max(1, right - left), height = Math.max(1, bottom - top);
|
|
1994
|
+
if (left + width > W) left = W - width;
|
|
1995
|
+
if (top + height > H) top = H - height;
|
|
1996
|
+
return { left, top, width, height };
|
|
1997
|
+
}
|
|
1998
|
+
async function extractRenderedRegions(input, options = {}) {
|
|
1999
|
+
const { scene, pageSvgs } = await renderDocumentToScene(input, { pages: options.pages, reflow: options.reflow, reflowMode: options.reflowMode });
|
|
2000
|
+
const types = options.types ? new Set(options.types) : null;
|
|
2001
|
+
const format = options.format ?? "png";
|
|
2002
|
+
const wanted = scene.regions.filter((r) => (!types || types.has(r.type)) && (!options.filter || options.filter(r)));
|
|
2003
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
2004
|
+
for (const r of wanted) for (const b of r.regions) if (pageSvgs.has(b.page)) (byPage.get(b.page) ?? byPage.set(b.page, []).get(b.page)).push({ region: r, bbox: b });
|
|
2005
|
+
if (byPage.size === 0) return [];
|
|
2006
|
+
const sharp = await loadSharp();
|
|
2007
|
+
const out = [];
|
|
2008
|
+
for (const [page, items] of [...byPage.entries()].sort((a, b) => a[0] - b[0])) {
|
|
2009
|
+
const meta = scene.pages.find((p) => p.page === page);
|
|
2010
|
+
if (!meta) throw new KordocError(`\uD398\uC774\uC9C0 ${page} \uBA54\uD0C0 \uC5C6\uC74C`);
|
|
2011
|
+
const raster = await rasterizePageSvg(pageSvgs.get(page), meta.width, meta.height, { format, maxWidthPx: options.maxWidthPx, quality: options.quality });
|
|
2012
|
+
for (const { region, bbox } of items) {
|
|
2013
|
+
const rect = cropRect(bbox, raster.scale, raster.widthPx, raster.heightPx, options.paddingPt ?? 0);
|
|
2014
|
+
const pipeline = sharp(raster.data).extract(rect);
|
|
2015
|
+
const data = format === "jpeg" ? await pipeline.jpeg({ quality: options.quality ?? 85 }).toBuffer() : await pipeline.png().toBuffer();
|
|
2016
|
+
out.push({ region, page, bbox, mimeType: raster.mimeType, data, widthPx: rect.width, heightPx: rect.height });
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
const order = new Map(scene.regions.map((r, i) => [r.id, i]));
|
|
2020
|
+
out.sort((a, b) => order.get(a.region.id) - order.get(b.region.id) || a.page - b.page);
|
|
2021
|
+
return out;
|
|
1277
2022
|
}
|
|
1278
2023
|
|
|
1279
2024
|
export {
|
|
2025
|
+
parseLenientCfb,
|
|
2026
|
+
isZipFile,
|
|
2027
|
+
isHwpxFile,
|
|
2028
|
+
isOldHwpFile,
|
|
2029
|
+
isPdfFile,
|
|
2030
|
+
detectFormat,
|
|
2031
|
+
detectOle2Format,
|
|
2032
|
+
detectZipFormat,
|
|
1280
2033
|
charWidthEm1000,
|
|
1281
2034
|
SPACE_EM_FIXED,
|
|
1282
2035
|
SPACE_EM_FONT,
|
|
2036
|
+
faceClassForGen,
|
|
1283
2037
|
measureTextWidth,
|
|
1284
2038
|
simulateWrap,
|
|
1285
2039
|
simulateWrapKeepWord,
|
|
1286
2040
|
fitRatioForFewerLines,
|
|
1287
|
-
|
|
2041
|
+
renderHtml,
|
|
2042
|
+
markdownToPdf,
|
|
2043
|
+
blocksToPdf,
|
|
2044
|
+
regionId,
|
|
2045
|
+
unionBBox,
|
|
2046
|
+
RegionCollector,
|
|
2047
|
+
renderHwpxToSvg,
|
|
2048
|
+
renderHwpxPages,
|
|
2049
|
+
renderSceneToHtml,
|
|
2050
|
+
renderHtmlToPdf,
|
|
2051
|
+
renderDocumentToScene,
|
|
2052
|
+
renderDocument,
|
|
2053
|
+
cropRect,
|
|
2054
|
+
extractRenderedRegions
|
|
1288
2055
|
};
|
|
1289
|
-
//# sourceMappingURL=chunk-
|
|
2056
|
+
//# sourceMappingURL=chunk-T7AFLXGA.js.map
|