hwpx-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2378 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/codecs/hwpx/zip.ts
8
+ import { zipSync, unzipSync } from "fflate";
9
+ function createZip(entries) {
10
+ const data = {};
11
+ for (const entry of entries) {
12
+ data[entry.path] = entry.store ? [entry.data, { level: 0 }] : entry.data;
13
+ }
14
+ return zipSync(data);
15
+ }
16
+ function extractZip(data) {
17
+ return unzipSync(data);
18
+ }
19
+ function encodeUtf8(str) {
20
+ return new TextEncoder().encode(str);
21
+ }
22
+ function decodeUtf8(data) {
23
+ return new TextDecoder().decode(data);
24
+ }
25
+
26
+ // src/codecs/hwpx/xml/index.ts
27
+ function escapeXml(str) {
28
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
29
+ }
30
+ function attrs(map) {
31
+ const parts = [];
32
+ for (const [key, val] of Object.entries(map)) {
33
+ if (val === void 0) continue;
34
+ parts.push(`${key}="${escapeXml(String(val))}"`);
35
+ }
36
+ return parts.length > 0 ? " " + parts.join(" ") : "";
37
+ }
38
+ function el(tag, attributes, children) {
39
+ const a = attrs(attributes);
40
+ if (children === void 0 || children === "") {
41
+ return `<${tag}${a}/>`;
42
+ }
43
+ return `<${tag}${a}>${children}</${tag}>`;
44
+ }
45
+ function xmlDecl() {
46
+ return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
47
+ }
48
+
49
+ // src/codecs/hwpx/ns.ts
50
+ var NS = {
51
+ // 한/글 문서 네임스페이스
52
+ hh: "http://www.hancom.co.kr/hwpml/2011/head",
53
+ hp: "http://www.hancom.co.kr/hwpml/2011/paragraph",
54
+ hs: "http://www.hancom.co.kr/hwpml/2011/section",
55
+ hc: "http://www.hancom.co.kr/hwpml/2011/core",
56
+ ha: "http://www.hancom.co.kr/hwpml/2011/app",
57
+ // OPF / ODF
58
+ opf: "http://www.idpf.org/2007/opf",
59
+ odf: "urn:oasis:names:tc:opendocument:xmlns:container",
60
+ // 미디어 타입
61
+ mimeHwpx: "application/hwp+zip"
62
+ };
63
+
64
+ // src/codecs/hwpx/xml/version.ts
65
+ function generateVersionXml(meta) {
66
+ return xmlDecl() + "\n" + el(
67
+ "hc:version",
68
+ {
69
+ "xmlns:hc": NS.hc,
70
+ "xmlns:ha": NS.ha,
71
+ tagtypes: "1"
72
+ },
73
+ "\n" + el("ha:app", {}, "hwpx-js") + "\n" + el("ha:appversion", {}, meta.hwpVersion) + "\n" + el(
74
+ "hc:version",
75
+ { tagtypes: "0" },
76
+ el("ha:major", {}, "1") + el("ha:minor", {}, "4")
77
+ ) + "\n"
78
+ );
79
+ }
80
+
81
+ // src/codecs/hwpx/xml/container.ts
82
+ function generateContainerXml() {
83
+ return xmlDecl() + "\n" + el(
84
+ "container",
85
+ { xmlns: NS.odf, version: "1.0" },
86
+ "\n" + el(
87
+ "rootfiles",
88
+ {},
89
+ "\n" + el("rootfile", {
90
+ "full-path": "Contents/content.hpf",
91
+ "media-type": NS.mimeHwpx
92
+ }) + "\n"
93
+ ) + "\n"
94
+ );
95
+ }
96
+
97
+ // src/codecs/hwpx/xml/manifest.ts
98
+ function generateManifestXml(entries) {
99
+ const items = entries.map(
100
+ (e) => "\n" + el("manifest:file-entry", {
101
+ "manifest:full-path": e.fullPath,
102
+ "manifest:media-type": e.mediaType
103
+ })
104
+ ).join("");
105
+ return xmlDecl() + "\n" + el(
106
+ "manifest:manifest",
107
+ { "xmlns:manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" },
108
+ items + "\n"
109
+ );
110
+ }
111
+ function buildManifestEntries(sectionCount, binData) {
112
+ const entries = [
113
+ { fullPath: "/", mediaType: "application/hwp+zip" },
114
+ { fullPath: "version.xml", mediaType: "application/xml" },
115
+ { fullPath: "Contents/content.hpf", mediaType: "application/xml" },
116
+ { fullPath: "Contents/header.xml", mediaType: "application/xml" },
117
+ { fullPath: "settings.xml", mediaType: "application/xml" }
118
+ ];
119
+ for (let i = 0; i < sectionCount; i++) {
120
+ entries.push({
121
+ fullPath: `Contents/section${i}.xml`,
122
+ mediaType: "application/xml"
123
+ });
124
+ }
125
+ for (const bin of binData) {
126
+ const mimeMap = {
127
+ jpg: "image/jpeg",
128
+ png: "image/png",
129
+ gif: "image/gif",
130
+ bmp: "image/bmp",
131
+ tiff: "image/tiff",
132
+ wmf: "image/x-wmf",
133
+ emf: "image/x-emf"
134
+ };
135
+ entries.push({
136
+ fullPath: `BinData/${bin.name}`,
137
+ mediaType: mimeMap[bin.format] || "application/octet-stream"
138
+ });
139
+ }
140
+ return entries;
141
+ }
142
+
143
+ // src/codecs/hwpx/xml/settings.ts
144
+ function generateSettingsXml() {
145
+ return xmlDecl() + "\n" + el(
146
+ "ha:HWPApplicationSetting",
147
+ { "xmlns:ha": NS.ha },
148
+ "\n" + el("ha:CaretPosition", { list: "0", para: "0", pos: "0" }) + "\n"
149
+ );
150
+ }
151
+
152
+ // src/codecs/hwpx/xml/content-hpf.ts
153
+ function generateContentHpfXml(doc) {
154
+ const sectionItems = doc.sections.map(
155
+ (_, i) => el("opf:item", {
156
+ id: `section${i}`,
157
+ href: `section${i}.xml`,
158
+ "media-type": "application/xml"
159
+ })
160
+ ).join("\n");
161
+ const sectionRefs = doc.sections.map((_, i) => el("opf:itemref", { idref: `section${i}` })).join("\n");
162
+ const binItems = doc.binData.map(
163
+ (b) => el("opf:item", {
164
+ id: `bindata${b.id}`,
165
+ href: `../BinData/${b.name}`,
166
+ "media-type": getMimeType(b.format)
167
+ })
168
+ ).join("\n");
169
+ return xmlDecl() + "\n" + el(
170
+ "opf:package",
171
+ {
172
+ "xmlns:opf": NS.opf,
173
+ version: "1.0",
174
+ "unique-identifier": "bookid"
175
+ },
176
+ "\n" + el(
177
+ "opf:metadata",
178
+ {},
179
+ "\n" + el("opf:title", {}, doc.meta.title || "") + "\n"
180
+ ) + "\n" + el(
181
+ "opf:manifest",
182
+ {},
183
+ "\n" + el("opf:item", { id: "header", href: "header.xml", "media-type": "application/xml" }) + "\n" + sectionItems + "\n" + binItems + (binItems ? "\n" : "")
184
+ ) + "\n" + el(
185
+ "opf:spine",
186
+ {},
187
+ "\n" + sectionRefs + "\n"
188
+ ) + "\n"
189
+ );
190
+ }
191
+ function getMimeType(format) {
192
+ const map = {
193
+ jpg: "image/jpeg",
194
+ png: "image/png",
195
+ gif: "image/gif",
196
+ bmp: "image/bmp",
197
+ tiff: "image/tiff",
198
+ wmf: "image/x-wmf",
199
+ emf: "image/x-emf"
200
+ };
201
+ return map[format] || "application/octet-stream";
202
+ }
203
+
204
+ // src/codecs/hwpx/xml/header.ts
205
+ function generateHeaderXml(head) {
206
+ return xmlDecl() + "\n" + el(
207
+ "hh:head",
208
+ {
209
+ "xmlns:hh": NS.hh,
210
+ "xmlns:hc": NS.hc,
211
+ version: "1.4",
212
+ secCnt: "1"
213
+ },
214
+ "\n" + generateFontFaces(head.fontFaces) + generateBorderFills(head.borderFills) + generateCharProperties(head.charProperties) + generateParaProperties(head.paraProperties) + generateStyles(head.styles) + generateNumberings(head.numberingProperties) + generateBullets(head.bulletProperties) + generateCompatibleDoc(head.compatibleDoc)
215
+ );
216
+ }
217
+ function generateFontFaces(faces) {
218
+ if (faces.length === 0) return "";
219
+ const byLang = /* @__PURE__ */ new Map();
220
+ for (const f of faces) {
221
+ const arr = byLang.get(f.lang) || [];
222
+ arr.push(f);
223
+ byLang.set(f.lang, arr);
224
+ }
225
+ let xml = "";
226
+ xml += el(
227
+ "hh:fontfaces",
228
+ {},
229
+ "\n" + Array.from(byLang.entries()).map(
230
+ ([lang, items]) => el(
231
+ "hh:fontface",
232
+ { lang },
233
+ "\n" + items.map(
234
+ (f, i) => el("hh:font", { id: String(i), face: f.fontName, type: f.fontType || "ttf" })
235
+ ).join("\n") + "\n"
236
+ )
237
+ ).join("\n") + "\n"
238
+ );
239
+ xml += "\n";
240
+ return xml;
241
+ }
242
+ function generateBorderFills(fills) {
243
+ if (fills.length === 0) return "";
244
+ const items = fills.map((bf) => {
245
+ const borderElements = ["left", "right", "top", "bottom"].map((side) => generateBorderLine(side, bf.borders[side])).join("\n");
246
+ let fillContent = "";
247
+ if (bf.fillBrush) {
248
+ if (bf.fillBrush.type === "SOLID" && bf.fillBrush.faceColor !== void 0) {
249
+ fillContent = el(
250
+ "hc:fillBrush",
251
+ {},
252
+ "\n" + el("hc:winBrush", {
253
+ faceColor: colorToString(bf.fillBrush.faceColor),
254
+ hatchColor: bf.fillBrush.patternColor !== void 0 ? colorToString(bf.fillBrush.patternColor) : void 0
255
+ }) + "\n"
256
+ );
257
+ }
258
+ }
259
+ return el(
260
+ "hh:borderFill",
261
+ {
262
+ id: String(bf.id),
263
+ threeD: bf.threeD ? "1" : "0",
264
+ shadow: bf.shadow ? "1" : "0",
265
+ slash: bf.slash || "NONE",
266
+ backSlash: bf.backSlash || "NONE"
267
+ },
268
+ "\n" + borderElements + "\n" + fillContent
269
+ );
270
+ });
271
+ return el("hh:borderFills", {}, "\n" + items.join("\n") + "\n") + "\n";
272
+ }
273
+ function generateBorderLine(side, line) {
274
+ return el(`hc:${side}Border`, {
275
+ type: line.type,
276
+ width: line.width,
277
+ color: colorToString(line.color)
278
+ });
279
+ }
280
+ function generateCharProperties(props) {
281
+ if (props.length === 0) return "";
282
+ const items = props.map((cp) => {
283
+ const fontRefs = el("hh:fontRef", {
284
+ hangul: String(cp.fontRef.hangul),
285
+ latin: String(cp.fontRef.latin),
286
+ hanja: String(cp.fontRef.hanja),
287
+ japanese: String(cp.fontRef.japanese),
288
+ other: String(cp.fontRef.other),
289
+ symbol: String(cp.fontRef.symbol),
290
+ user: String(cp.fontRef.user)
291
+ });
292
+ return el(
293
+ "hh:charPr",
294
+ {
295
+ id: String(cp.id),
296
+ height: String(cp.height),
297
+ textColor: colorToString(cp.textColor),
298
+ shadeColor: cp.shadeColor !== void 0 ? colorToString(cp.shadeColor) : void 0,
299
+ useFontSpace: cp.useFontSpace ? "1" : void 0,
300
+ useKerning: cp.useKerning ? "1" : void 0,
301
+ spacing: cp.spacing !== void 0 ? String(cp.spacing) : void 0,
302
+ relSz: cp.relSize !== void 0 ? String(cp.relSize) : void 0,
303
+ offset: cp.charOffset !== void 0 ? String(cp.charOffset) : void 0,
304
+ bold: cp.bold ? "1" : "0",
305
+ italic: cp.italic ? "1" : "0",
306
+ underline: cp.underline || "NONE",
307
+ strikeout: cp.strikeout || "NONE"
308
+ },
309
+ "\n" + fontRefs + "\n"
310
+ );
311
+ });
312
+ return el("hh:charProperties", {}, "\n" + items.join("\n") + "\n") + "\n";
313
+ }
314
+ function generateParaProperties(props) {
315
+ if (props.length === 0) return "";
316
+ const items = props.map((pp) => {
317
+ const marginEl = el("hh:parMargin", {
318
+ left: String(pp.paraMargin.left),
319
+ right: String(pp.paraMargin.right),
320
+ indent: String(pp.paraMargin.indent),
321
+ prev: String(pp.paraMargin.prevSpacing),
322
+ next: String(pp.paraMargin.nextSpacing)
323
+ });
324
+ const lineSpacingEl = el("hh:lineSpacing", {
325
+ type: pp.lineSpacing.type,
326
+ value: String(pp.lineSpacing.value)
327
+ });
328
+ return el(
329
+ "hh:paraPr",
330
+ {
331
+ id: String(pp.id),
332
+ align: pp.alignment || "JUSTIFY",
333
+ heading: pp.heading,
334
+ breakBefore: pp.breakBefore
335
+ },
336
+ "\n" + marginEl + "\n" + lineSpacingEl + "\n"
337
+ );
338
+ });
339
+ return el("hh:paraProperties", {}, "\n" + items.join("\n") + "\n") + "\n";
340
+ }
341
+ function generateStyles(styles) {
342
+ if (styles.length === 0) return "";
343
+ const items = styles.map(
344
+ (s) => el("hh:style", {
345
+ id: String(s.id),
346
+ type: s.type,
347
+ name: s.name,
348
+ engName: s.engName,
349
+ paraPrIDRef: s.paraPrIDRef !== void 0 ? String(s.paraPrIDRef) : void 0,
350
+ charPrIDRef: s.charPrIDRef !== void 0 ? String(s.charPrIDRef) : void 0,
351
+ nextStyleIDRef: s.nextStyleIDRef !== void 0 ? String(s.nextStyleIDRef) : void 0
352
+ })
353
+ );
354
+ return el("hh:styles", {}, "\n" + items.join("\n") + "\n") + "\n";
355
+ }
356
+ function generateNumberings(numberings) {
357
+ if (numberings.length === 0) return "";
358
+ const items = numberings.map((n) => {
359
+ const levels = n.levels.map(
360
+ (lvl, i) => el("hh:paraHead", {
361
+ level: String(i + 1),
362
+ numFormat: lvl.format,
363
+ start: lvl.start !== void 0 ? String(lvl.start) : "1",
364
+ prefix: lvl.prefix || "",
365
+ suffix: lvl.suffix || "."
366
+ })
367
+ ).join("\n");
368
+ return el("hh:numbering", { id: String(n.id) }, "\n" + levels + "\n");
369
+ });
370
+ return el("hh:numberings", {}, "\n" + items.join("\n") + "\n") + "\n";
371
+ }
372
+ function generateBullets(bullets) {
373
+ if (bullets.length === 0) return "";
374
+ const items = bullets.map(
375
+ (b) => el("hh:bullet", {
376
+ id: String(b.id),
377
+ char: b.bulletChar,
378
+ bulletSz: b.bulletSize !== void 0 ? String(b.bulletSize) : void 0
379
+ })
380
+ );
381
+ return el("hh:bullets", {}, "\n" + items.join("\n") + "\n") + "\n";
382
+ }
383
+ function generateCompatibleDoc(compat) {
384
+ if (!compat) return "";
385
+ return el("hh:compatibleDocument", { targetProgram: compat }) + "\n";
386
+ }
387
+ function colorToString(color) {
388
+ return "#" + color.toString(16).padStart(6, "0");
389
+ }
390
+
391
+ // src/codecs/hwpx/xml/section.ts
392
+ function generateSectionXml(section, sectionIndex) {
393
+ return xmlDecl() + "\n" + el(
394
+ "hs:sec",
395
+ {
396
+ "xmlns:hs": NS.hs,
397
+ "xmlns:hp": NS.hp,
398
+ "xmlns:hc": NS.hc
399
+ },
400
+ "\n" + generateSectionDef(section.def) + section.paragraphs.map((p) => generateParagraph(p)).join("\n") + "\n"
401
+ );
402
+ }
403
+ function generateSectionDef(def) {
404
+ const pagePr = el("hs:pagePr", {
405
+ landscape: def.landscape ? "LANDSCAPE" : "PORTRAIT",
406
+ width: String(def.pageWidth),
407
+ height: String(def.pageHeight),
408
+ gutterType: def.gutterType || "LEFT_ONLY"
409
+ });
410
+ const pageMargin = el("hs:pageMargin", {
411
+ left: String(def.pageMargin.left),
412
+ right: String(def.pageMargin.right),
413
+ top: String(def.pageMargin.top),
414
+ bottom: String(def.pageMargin.bottom),
415
+ header: String(def.pageMargin.header),
416
+ footer: String(def.pageMargin.footer),
417
+ gutter: String(def.pageMargin.gutter)
418
+ });
419
+ let extra = "";
420
+ if (def.columns && def.columns.count > 1) {
421
+ extra += generateColumns(def.columns) + "\n";
422
+ }
423
+ if (def.headerFooter) {
424
+ if (def.headerFooter.header) {
425
+ extra += generateHeaderFooter("hs:header", def.headerFooter.header) + "\n";
426
+ }
427
+ if (def.headerFooter.footer) {
428
+ extra += generateHeaderFooter("hs:footer", def.headerFooter.footer) + "\n";
429
+ }
430
+ }
431
+ return el(
432
+ "hs:secPr",
433
+ {},
434
+ "\n" + pagePr + "\n" + pageMargin + "\n" + extra
435
+ ) + "\n";
436
+ }
437
+ function generateColumns(cols) {
438
+ return el("hs:colPr", {
439
+ type: cols.type || "NORMAL",
440
+ count: String(cols.count),
441
+ gap: String(cols.gap || 0),
442
+ sameSz: cols.sameSizes ? "1" : "0"
443
+ });
444
+ }
445
+ function generateHeaderFooter(tag, subDoc) {
446
+ const paragraphs = subDoc.paragraphs.map((p) => generateParagraph(p)).join("\n");
447
+ return el(tag, {}, "\n" + paragraphs + "\n");
448
+ }
449
+ function generateParagraph(para) {
450
+ const runs = para.runs.map((run) => generateRun(run)).join("");
451
+ return el(
452
+ "hp:p",
453
+ {
454
+ paraPrIDRef: String(para.paraPrIDRef),
455
+ styleIDRef: String(para.styleIDRef)
456
+ },
457
+ "\n" + runs
458
+ );
459
+ }
460
+ function generateRun(run) {
461
+ switch (run.t) {
462
+ case "text":
463
+ return generateTextRun(run);
464
+ case "table":
465
+ return generateTableRun(run);
466
+ case "picture":
467
+ return generatePictureRun(run);
468
+ case "break":
469
+ return generateBreakRun(run);
470
+ default:
471
+ return "";
472
+ }
473
+ }
474
+ function generateTextRun(run) {
475
+ return el(
476
+ "hp:run",
477
+ { charPrIDRef: String(run.charPrIDRef) },
478
+ "\n" + el("hp:runText", {}, escapeXml(run.text)) + "\n"
479
+ );
480
+ }
481
+ function generateTableRun(run) {
482
+ return el(
483
+ "hp:run",
484
+ { charPrIDRef: String(run.charPrIDRef) },
485
+ "\n" + generateTable(run.table) + "\n"
486
+ );
487
+ }
488
+ function generatePictureRun(run) {
489
+ return el(
490
+ "hp:run",
491
+ { charPrIDRef: String(run.charPrIDRef) },
492
+ "\n" + generatePicture(run.picture) + "\n"
493
+ );
494
+ }
495
+ function generateBreakRun(run) {
496
+ const tagMap = {
497
+ LINE: "hp:lineBreak",
498
+ PAGE: "hp:pageBreak",
499
+ COLUMN: "hp:colBreak"
500
+ };
501
+ const tag = tagMap[run.breakType] || "hp:lineBreak";
502
+ return el(
503
+ "hp:run",
504
+ { charPrIDRef: String(run.charPrIDRef) },
505
+ `
506
+ <${tag}/>
507
+ `
508
+ );
509
+ }
510
+ function generateTable(table) {
511
+ const colWidths = table.colWidths.map((w) => el("hp:colSz", { width: String(w) })).join("\n");
512
+ const rows = table.rows.map((row, ri) => generateTableRow(row, ri)).join("\n");
513
+ return el(
514
+ "hp:tbl",
515
+ {
516
+ rowCnt: String(table.rowCount),
517
+ colCnt: String(table.colCount),
518
+ cellSpacing: table.cellSpacing !== void 0 ? String(table.cellSpacing) : "0",
519
+ borderFillIDRef: String(table.borderFillIDRef),
520
+ width: String(table.width)
521
+ },
522
+ "\n" + colWidths + "\n" + rows + "\n"
523
+ );
524
+ }
525
+ function generateTableRow(row, rowIndex) {
526
+ const cells = row.cells.map((cell) => generateTableCell(cell)).join("\n");
527
+ return el(
528
+ "hp:tr",
529
+ { height: String(row.height) },
530
+ "\n" + cells + "\n"
531
+ );
532
+ }
533
+ function generateTableCell(cell) {
534
+ const paddingAttrs = {};
535
+ if (cell.padding) {
536
+ paddingAttrs["paddingLeft"] = String(cell.padding.left);
537
+ paddingAttrs["paddingRight"] = String(cell.padding.right);
538
+ paddingAttrs["paddingTop"] = String(cell.padding.top);
539
+ paddingAttrs["paddingBottom"] = String(cell.padding.bottom);
540
+ }
541
+ const paragraphs = cell.paragraphs.map((p) => generateParagraph(p)).join("\n");
542
+ return el(
543
+ "hp:tc",
544
+ {
545
+ colSpan: String(cell.colSpan),
546
+ rowSpan: String(cell.rowSpan),
547
+ width: String(cell.width),
548
+ height: String(cell.height),
549
+ borderFillIDRef: String(cell.borderFillIDRef),
550
+ ...paddingAttrs
551
+ },
552
+ "\n" + el("hp:subList", {}, "\n" + paragraphs + "\n") + "\n"
553
+ );
554
+ }
555
+ function generatePicture(pic) {
556
+ return el(
557
+ "hp:pic",
558
+ {
559
+ binDataIDRef: String(pic.binDataIDRef),
560
+ width: String(pic.width),
561
+ height: String(pic.height),
562
+ offsetX: pic.offsetX !== void 0 ? String(pic.offsetX) : void 0,
563
+ offsetY: pic.offsetY !== void 0 ? String(pic.offsetY) : void 0
564
+ }
565
+ );
566
+ }
567
+
568
+ // src/codecs/hwpx/writer.ts
569
+ function writeHwpx(doc, _opts) {
570
+ const entries = [];
571
+ entries.push({
572
+ path: "mimetype",
573
+ data: encodeUtf8(NS.mimeHwpx),
574
+ store: true
575
+ });
576
+ entries.push({
577
+ path: "META-INF/container.xml",
578
+ data: encodeUtf8(generateContainerXml())
579
+ });
580
+ const manifestEntries = buildManifestEntries(
581
+ doc.sections.length,
582
+ doc.binData.map((b) => ({ name: b.name, format: b.format }))
583
+ );
584
+ entries.push({
585
+ path: "META-INF/manifest.xml",
586
+ data: encodeUtf8(generateManifestXml(manifestEntries))
587
+ });
588
+ entries.push({
589
+ path: "version.xml",
590
+ data: encodeUtf8(generateVersionXml(doc.meta))
591
+ });
592
+ entries.push({
593
+ path: "settings.xml",
594
+ data: encodeUtf8(generateSettingsXml())
595
+ });
596
+ entries.push({
597
+ path: "Contents/content.hpf",
598
+ data: encodeUtf8(generateContentHpfXml(doc))
599
+ });
600
+ entries.push({
601
+ path: "Contents/header.xml",
602
+ data: encodeUtf8(generateHeaderXml(doc.head))
603
+ });
604
+ for (let i = 0; i < doc.sections.length; i++) {
605
+ entries.push({
606
+ path: `Contents/section${i}.xml`,
607
+ data: encodeUtf8(generateSectionXml(doc.sections[i], i))
608
+ });
609
+ }
610
+ for (const bin of doc.binData) {
611
+ entries.push({
612
+ path: `BinData/${bin.name}`,
613
+ data: bin.data
614
+ });
615
+ }
616
+ return createZip(entries);
617
+ }
618
+
619
+ // src/codecs/hwpx/xml/parser.ts
620
+ function parseXml(xml) {
621
+ const parser = new XmlParser(xml);
622
+ return parser.parse();
623
+ }
624
+ function findChild(node, tag) {
625
+ return node.children.find((c) => c.tag === tag || stripNs(c.tag) === tag);
626
+ }
627
+ function findChildren(node, tag) {
628
+ return node.children.filter((c) => c.tag === tag || stripNs(c.tag) === tag);
629
+ }
630
+ function stripNs(tag) {
631
+ const idx = tag.indexOf(":");
632
+ return idx >= 0 ? tag.substring(idx + 1) : tag;
633
+ }
634
+ function attrInt(node, name, defaultValue = 0) {
635
+ const v = node.attrs[name];
636
+ if (v === void 0) return defaultValue;
637
+ const n = parseInt(v, 10);
638
+ return isNaN(n) ? defaultValue : n;
639
+ }
640
+ function attrStr(node, name, defaultValue = "") {
641
+ return node.attrs[name] ?? defaultValue;
642
+ }
643
+ function attrBool(node, name, defaultValue = false) {
644
+ const v = node.attrs[name];
645
+ if (v === void 0) return defaultValue;
646
+ return v === "1" || v === "true";
647
+ }
648
+ var XmlParser = class {
649
+ constructor(xml) {
650
+ this.xml = xml;
651
+ this.pos = 0;
652
+ }
653
+ parse() {
654
+ this.skipWhitespace();
655
+ if (this.xml.startsWith("<?", this.pos)) {
656
+ const end = this.xml.indexOf("?>", this.pos);
657
+ if (end >= 0) this.pos = end + 2;
658
+ }
659
+ this.skipWhitespace();
660
+ return this.parseElement();
661
+ }
662
+ parseElement() {
663
+ this.expect("<");
664
+ const tag = this.readTagName();
665
+ const attrs3 = this.readAttributes();
666
+ this.skipWhitespace();
667
+ if (this.xml.startsWith("/>", this.pos)) {
668
+ this.pos += 2;
669
+ return { tag, attrs: attrs3, children: [], text: "" };
670
+ }
671
+ this.expect(">");
672
+ const children = [];
673
+ let text = "";
674
+ let hasChildElements = false;
675
+ while (this.pos < this.xml.length) {
676
+ if (this.xml.startsWith("</", this.pos)) {
677
+ this.pos += 2;
678
+ const closingTag = this.readTagName();
679
+ this.skipWhitespace();
680
+ this.expect(">");
681
+ const finalText2 = hasChildElements ? text.trim() : text;
682
+ return { tag, attrs: attrs3, children, text: finalText2 };
683
+ }
684
+ if (this.xml.startsWith("<!--", this.pos)) {
685
+ const end = this.xml.indexOf("-->", this.pos);
686
+ if (end >= 0) this.pos = end + 3;
687
+ continue;
688
+ }
689
+ if (this.xml.startsWith("<![CDATA[", this.pos)) {
690
+ const end = this.xml.indexOf("]]>", this.pos);
691
+ if (end >= 0) {
692
+ text += this.xml.substring(this.pos + 9, end);
693
+ this.pos = end + 3;
694
+ }
695
+ continue;
696
+ }
697
+ if (this.xml[this.pos] === "<") {
698
+ hasChildElements = true;
699
+ children.push(this.parseElement());
700
+ } else {
701
+ const nextTag = this.xml.indexOf("<", this.pos);
702
+ if (nextTag >= 0) {
703
+ text += this.xml.substring(this.pos, nextTag);
704
+ this.pos = nextTag;
705
+ } else {
706
+ text += this.xml.substring(this.pos);
707
+ this.pos = this.xml.length;
708
+ }
709
+ }
710
+ }
711
+ const finalText = hasChildElements ? text.trim() : text;
712
+ return { tag, attrs: attrs3, children, text: finalText };
713
+ }
714
+ readTagName() {
715
+ const start = this.pos;
716
+ while (this.pos < this.xml.length) {
717
+ const ch = this.xml[this.pos];
718
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === ">" || ch === "/") break;
719
+ this.pos++;
720
+ }
721
+ return this.xml.substring(start, this.pos);
722
+ }
723
+ readAttributes() {
724
+ const attrs3 = {};
725
+ while (this.pos < this.xml.length) {
726
+ this.skipWhitespace();
727
+ const ch = this.xml[this.pos];
728
+ if (ch === ">" || ch === "/") break;
729
+ const name = this.readAttrName();
730
+ this.skipWhitespace();
731
+ this.expect("=");
732
+ this.skipWhitespace();
733
+ const value = this.readAttrValue();
734
+ attrs3[name] = this.unescapeXml(value);
735
+ }
736
+ return attrs3;
737
+ }
738
+ readAttrName() {
739
+ const start = this.pos;
740
+ while (this.pos < this.xml.length) {
741
+ const ch = this.xml[this.pos];
742
+ if (ch === "=" || ch === " " || ch === " " || ch === "\n" || ch === ">" || ch === "/") break;
743
+ this.pos++;
744
+ }
745
+ return this.xml.substring(start, this.pos);
746
+ }
747
+ readAttrValue() {
748
+ const quote = this.xml[this.pos];
749
+ if (quote !== '"' && quote !== "'") {
750
+ throw new Error(`Expected quote at pos ${this.pos}`);
751
+ }
752
+ this.pos++;
753
+ const start = this.pos;
754
+ const end = this.xml.indexOf(quote, this.pos);
755
+ if (end < 0) throw new Error(`Unterminated attribute value at pos ${start}`);
756
+ this.pos = end + 1;
757
+ return this.xml.substring(start, end);
758
+ }
759
+ skipWhitespace() {
760
+ while (this.pos < this.xml.length) {
761
+ const ch = this.xml[this.pos];
762
+ if (ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") break;
763
+ this.pos++;
764
+ }
765
+ }
766
+ expect(ch) {
767
+ if (!this.xml.startsWith(ch, this.pos)) {
768
+ throw new Error(
769
+ `Expected "${ch}" at pos ${this.pos}, got "${this.xml.substring(this.pos, this.pos + 10)}"`
770
+ );
771
+ }
772
+ this.pos += ch.length;
773
+ }
774
+ unescapeXml(str) {
775
+ return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
776
+ }
777
+ };
778
+
779
+ // src/codecs/hwpx/xml/parse-header.ts
780
+ function parseHeaderXml(xml) {
781
+ const root = parseXml(xml);
782
+ return {
783
+ fontFaces: parseFontFaces(root),
784
+ borderFills: parseBorderFills(root),
785
+ charProperties: parseCharProperties(root),
786
+ paraProperties: parseParaProperties(root),
787
+ styles: parseStyles(root),
788
+ bulletProperties: parseBullets(root),
789
+ numberingProperties: parseNumberings(root),
790
+ compatibleDoc: parseCompatibleDoc(root)
791
+ };
792
+ }
793
+ function parseFontFaces(root) {
794
+ const faces = [];
795
+ const fontfacesNode = findChild(root, "hh:fontfaces");
796
+ if (!fontfacesNode) return faces;
797
+ for (const faceNode of findChildren(fontfacesNode, "hh:fontface")) {
798
+ const lang = attrStr(faceNode, "lang", "HANGUL");
799
+ for (const fontNode of findChildren(faceNode, "hh:font")) {
800
+ faces.push({
801
+ lang,
802
+ fontName: attrStr(fontNode, "face"),
803
+ fontType: attrStr(fontNode, "type", "ttf")
804
+ });
805
+ }
806
+ }
807
+ return faces;
808
+ }
809
+ function parseBorderFills(root) {
810
+ const fills = [];
811
+ const fillsNode = findChild(root, "hh:borderFills");
812
+ if (!fillsNode) return fills;
813
+ for (const bfNode of findChildren(fillsNode, "hh:borderFill")) {
814
+ const leftBorder = findChild(bfNode, "hc:leftBorder");
815
+ const rightBorder = findChild(bfNode, "hc:rightBorder");
816
+ const topBorder = findChild(bfNode, "hc:topBorder");
817
+ const bottomBorder = findChild(bfNode, "hc:bottomBorder");
818
+ let fillBrush;
819
+ const fillBrushNode = findChild(bfNode, "hc:fillBrush");
820
+ if (fillBrushNode) {
821
+ const winBrush = findChild(fillBrushNode, "hc:winBrush");
822
+ if (winBrush) {
823
+ fillBrush = {
824
+ type: "SOLID",
825
+ faceColor: parseColor(attrStr(winBrush, "faceColor", "#000000")),
826
+ patternColor: winBrush.attrs["hatchColor"] ? parseColor(attrStr(winBrush, "hatchColor")) : void 0
827
+ };
828
+ }
829
+ }
830
+ fills.push({
831
+ id: attrInt(bfNode, "id"),
832
+ threeD: attrBool(bfNode, "threeD"),
833
+ shadow: attrBool(bfNode, "shadow"),
834
+ slash: attrStr(bfNode, "slash", "NONE"),
835
+ backSlash: attrStr(bfNode, "backSlash", "NONE"),
836
+ borders: {
837
+ left: parseBorderLine(leftBorder),
838
+ right: parseBorderLine(rightBorder),
839
+ top: parseBorderLine(topBorder),
840
+ bottom: parseBorderLine(bottomBorder)
841
+ },
842
+ fillBrush
843
+ });
844
+ }
845
+ return fills;
846
+ }
847
+ function parseBorderLine(node) {
848
+ if (!node) {
849
+ return { type: "NONE", width: "0.1mm", color: 0 };
850
+ }
851
+ return {
852
+ type: attrStr(node, "type", "NONE"),
853
+ width: attrStr(node, "width", "0.1mm"),
854
+ color: parseColor(attrStr(node, "color", "#000000"))
855
+ };
856
+ }
857
+ function parseCharProperties(root) {
858
+ const props = [];
859
+ const propsNode = findChild(root, "hh:charProperties");
860
+ if (!propsNode) return props;
861
+ for (const cpNode of findChildren(propsNode, "hh:charPr")) {
862
+ const fontRefNode = findChild(cpNode, "hh:fontRef");
863
+ props.push({
864
+ id: attrInt(cpNode, "id"),
865
+ height: attrInt(cpNode, "height", 1e3),
866
+ textColor: parseColor(attrStr(cpNode, "textColor", "#000000")),
867
+ shadeColor: cpNode.attrs["shadeColor"] ? parseColor(attrStr(cpNode, "shadeColor")) : void 0,
868
+ bold: attrBool(cpNode, "bold"),
869
+ italic: attrBool(cpNode, "italic"),
870
+ underline: attrStr(cpNode, "underline", "NONE"),
871
+ strikeout: attrStr(cpNode, "strikeout", "NONE"),
872
+ useFontSpace: attrBool(cpNode, "useFontSpace"),
873
+ useKerning: attrBool(cpNode, "useKerning"),
874
+ spacing: cpNode.attrs["spacing"] !== void 0 ? attrInt(cpNode, "spacing") : void 0,
875
+ relSize: cpNode.attrs["relSz"] !== void 0 ? attrInt(cpNode, "relSz") : void 0,
876
+ charOffset: cpNode.attrs["offset"] !== void 0 ? attrInt(cpNode, "offset") : void 0,
877
+ fontRef: fontRefNode ? {
878
+ hangul: attrInt(fontRefNode, "hangul"),
879
+ latin: attrInt(fontRefNode, "latin"),
880
+ hanja: attrInt(fontRefNode, "hanja"),
881
+ japanese: attrInt(fontRefNode, "japanese"),
882
+ other: attrInt(fontRefNode, "other"),
883
+ symbol: attrInt(fontRefNode, "symbol"),
884
+ user: attrInt(fontRefNode, "user")
885
+ } : { hangul: 0, latin: 0, hanja: 0, japanese: 0, other: 0, symbol: 0, user: 0 }
886
+ });
887
+ }
888
+ return props;
889
+ }
890
+ function parseParaProperties(root) {
891
+ const props = [];
892
+ const propsNode = findChild(root, "hh:paraProperties");
893
+ if (!propsNode) return props;
894
+ for (const ppNode of findChildren(propsNode, "hh:paraPr")) {
895
+ const marginNode = findChild(ppNode, "hh:parMargin");
896
+ const lineSpacingNode = findChild(ppNode, "hh:lineSpacing");
897
+ props.push({
898
+ id: attrInt(ppNode, "id"),
899
+ alignment: attrStr(ppNode, "align", "JUSTIFY"),
900
+ heading: ppNode.attrs["heading"] ? ppNode.attrs["heading"] : void 0,
901
+ breakBefore: ppNode.attrs["breakBefore"] ? ppNode.attrs["breakBefore"] : void 0,
902
+ lineSpacing: lineSpacingNode ? {
903
+ type: attrStr(lineSpacingNode, "type", "PERCENT"),
904
+ value: attrInt(lineSpacingNode, "value", 160)
905
+ } : { type: "PERCENT", value: 160 },
906
+ paraMargin: marginNode ? {
907
+ left: attrInt(marginNode, "left"),
908
+ right: attrInt(marginNode, "right"),
909
+ indent: attrInt(marginNode, "indent"),
910
+ prevSpacing: attrInt(marginNode, "prev"),
911
+ nextSpacing: attrInt(marginNode, "next")
912
+ } : { left: 0, right: 0, indent: 0, prevSpacing: 0, nextSpacing: 0 }
913
+ });
914
+ }
915
+ return props;
916
+ }
917
+ function parseStyles(root) {
918
+ const styles = [];
919
+ const stylesNode = findChild(root, "hh:styles");
920
+ if (!stylesNode) return styles;
921
+ for (const sNode of findChildren(stylesNode, "hh:style")) {
922
+ styles.push({
923
+ id: attrInt(sNode, "id"),
924
+ type: attrStr(sNode, "type", "PARA"),
925
+ name: attrStr(sNode, "name"),
926
+ engName: sNode.attrs["engName"] || void 0,
927
+ paraPrIDRef: sNode.attrs["paraPrIDRef"] !== void 0 ? attrInt(sNode, "paraPrIDRef") : void 0,
928
+ charPrIDRef: sNode.attrs["charPrIDRef"] !== void 0 ? attrInt(sNode, "charPrIDRef") : void 0,
929
+ nextStyleIDRef: sNode.attrs["nextStyleIDRef"] !== void 0 ? attrInt(sNode, "nextStyleIDRef") : void 0
930
+ });
931
+ }
932
+ return styles;
933
+ }
934
+ function parseNumberings(root) {
935
+ const result = [];
936
+ const node = findChild(root, "hh:numberings");
937
+ if (!node) return result;
938
+ for (const nNode of findChildren(node, "hh:numbering")) {
939
+ const levels = [];
940
+ for (const lNode of findChildren(nNode, "hh:paraHead")) {
941
+ levels.push({
942
+ format: attrStr(lNode, "numFormat", "DIGIT"),
943
+ start: attrInt(lNode, "start", 1),
944
+ prefix: lNode.attrs["prefix"] || void 0,
945
+ suffix: lNode.attrs["suffix"] || void 0
946
+ });
947
+ }
948
+ result.push({ id: attrInt(nNode, "id"), levels });
949
+ }
950
+ return result;
951
+ }
952
+ function parseBullets(root) {
953
+ const result = [];
954
+ const node = findChild(root, "hh:bullets");
955
+ if (!node) return result;
956
+ for (const bNode of findChildren(node, "hh:bullet")) {
957
+ result.push({
958
+ id: attrInt(bNode, "id"),
959
+ bulletChar: attrStr(bNode, "char", "\u25CF"),
960
+ bulletSize: bNode.attrs["bulletSz"] !== void 0 ? attrInt(bNode, "bulletSz") : void 0
961
+ });
962
+ }
963
+ return result;
964
+ }
965
+ function parseCompatibleDoc(root) {
966
+ const node = findChild(root, "hh:compatibleDocument");
967
+ if (!node) return void 0;
968
+ return attrStr(node, "targetProgram") || void 0;
969
+ }
970
+ function parseColor(hex) {
971
+ const h = hex.replace("#", "");
972
+ return parseInt(h, 16);
973
+ }
974
+
975
+ // src/codecs/hwpx/xml/parse-section.ts
976
+ function parseSectionXml(xml) {
977
+ const root = parseXml(xml);
978
+ return {
979
+ def: parseSectionDef(root),
980
+ paragraphs: parseParagraphs(root)
981
+ };
982
+ }
983
+ function parseSectionDef(root) {
984
+ const secPr = findChild(root, "hs:secPr");
985
+ if (!secPr) {
986
+ return defaultSectionDef();
987
+ }
988
+ const pagePr = findChild(secPr, "hs:pagePr");
989
+ const pageMarginNode = findChild(secPr, "hs:pageMargin");
990
+ const colPr = findChild(secPr, "hs:colPr");
991
+ let columns;
992
+ if (colPr && attrInt(colPr, "count", 1) > 1) {
993
+ columns = {
994
+ type: attrStr(colPr, "type", "NORMAL"),
995
+ count: attrInt(colPr, "count", 1),
996
+ gap: attrInt(colPr, "gap", 0),
997
+ sameSizes: attrStr(colPr, "sameSz") === "1"
998
+ };
999
+ }
1000
+ let headerFooter;
1001
+ const headerNode = findChild(secPr, "hs:header");
1002
+ const footerNode = findChild(secPr, "hs:footer");
1003
+ if (headerNode || footerNode) {
1004
+ headerFooter = {};
1005
+ if (headerNode) {
1006
+ headerFooter.header = { paragraphs: parseParagraphs(headerNode) };
1007
+ }
1008
+ if (footerNode) {
1009
+ headerFooter.footer = { paragraphs: parseParagraphs(footerNode) };
1010
+ }
1011
+ }
1012
+ return {
1013
+ pageWidth: pagePr ? attrInt(pagePr, "width", 59528) : 59528,
1014
+ pageHeight: pagePr ? attrInt(pagePr, "height", 84188) : 84188,
1015
+ landscape: pagePr ? attrStr(pagePr, "landscape") === "LANDSCAPE" : false,
1016
+ gutterType: pagePr ? attrStr(pagePr, "gutterType", "LEFT_ONLY") : "LEFT_ONLY",
1017
+ pageMargin: pageMarginNode ? parsePageMargin(pageMarginNode) : defaultPageMargin(),
1018
+ columns,
1019
+ headerFooter
1020
+ };
1021
+ }
1022
+ function parsePageMargin(node) {
1023
+ return {
1024
+ left: attrInt(node, "left"),
1025
+ right: attrInt(node, "right"),
1026
+ top: attrInt(node, "top"),
1027
+ bottom: attrInt(node, "bottom"),
1028
+ header: attrInt(node, "header"),
1029
+ footer: attrInt(node, "footer"),
1030
+ gutter: attrInt(node, "gutter")
1031
+ };
1032
+ }
1033
+ function parseParagraphs(parent) {
1034
+ const paragraphs = [];
1035
+ for (const pNode of findChildren(parent, "hp:p")) {
1036
+ paragraphs.push(parseParagraph(pNode));
1037
+ }
1038
+ return paragraphs;
1039
+ }
1040
+ function parseParagraph(pNode) {
1041
+ const runs = [];
1042
+ for (const runNode of findChildren(pNode, "hp:run")) {
1043
+ const charPrIDRef = attrInt(runNode, "charPrIDRef");
1044
+ const lineBreak = findChild(runNode, "hp:lineBreak");
1045
+ const pageBreak = findChild(runNode, "hp:pageBreak");
1046
+ const colBreak = findChild(runNode, "hp:colBreak");
1047
+ if (lineBreak) {
1048
+ runs.push({ t: "break", breakType: "LINE", charPrIDRef });
1049
+ } else if (pageBreak) {
1050
+ runs.push({ t: "break", breakType: "PAGE", charPrIDRef });
1051
+ } else if (colBreak) {
1052
+ runs.push({ t: "break", breakType: "COLUMN", charPrIDRef });
1053
+ } else {
1054
+ const tblNode = findChild(runNode, "hp:tbl");
1055
+ if (tblNode) {
1056
+ runs.push({ t: "table", table: parseTable(tblNode), charPrIDRef });
1057
+ continue;
1058
+ }
1059
+ const picNode = findChild(runNode, "hp:pic");
1060
+ if (picNode) {
1061
+ runs.push({ t: "picture", picture: parsePicture(picNode), charPrIDRef });
1062
+ continue;
1063
+ }
1064
+ const runTextNode = findChild(runNode, "hp:runText");
1065
+ const text = runTextNode ? getTextContent(runTextNode) : "";
1066
+ runs.push({ t: "text", text, charPrIDRef });
1067
+ }
1068
+ }
1069
+ return {
1070
+ paraPrIDRef: attrInt(pNode, "paraPrIDRef"),
1071
+ styleIDRef: attrInt(pNode, "styleIDRef"),
1072
+ runs
1073
+ };
1074
+ }
1075
+ function parseTable(tblNode) {
1076
+ const rowCount = attrInt(tblNode, "rowCnt", 1);
1077
+ const colCount = attrInt(tblNode, "colCnt", 1);
1078
+ const width = attrInt(tblNode, "width", 0);
1079
+ const borderFillIDRef = attrInt(tblNode, "borderFillIDRef", 1);
1080
+ const cellSpacing = attrInt(tblNode, "cellSpacing", 0);
1081
+ const colSzNodes = findChildren(tblNode, "hp:colSz");
1082
+ const colWidths = colSzNodes.map((n) => attrInt(n, "width", 0));
1083
+ const rows = [];
1084
+ for (const trNode of findChildren(tblNode, "hp:tr")) {
1085
+ rows.push(parseTableRow(trNode));
1086
+ }
1087
+ return {
1088
+ rowCount,
1089
+ colCount,
1090
+ width,
1091
+ borderFillIDRef,
1092
+ cellSpacing: cellSpacing || void 0,
1093
+ colWidths,
1094
+ rows
1095
+ };
1096
+ }
1097
+ function parseTableRow(trNode) {
1098
+ const height = attrInt(trNode, "height", 0);
1099
+ const cells = [];
1100
+ for (const tcNode of findChildren(trNode, "hp:tc")) {
1101
+ cells.push(parseTableCell(tcNode));
1102
+ }
1103
+ return { height, cells };
1104
+ }
1105
+ function parseTableCell(tcNode) {
1106
+ const colSpan = attrInt(tcNode, "colSpan", 1);
1107
+ const rowSpan = attrInt(tcNode, "rowSpan", 1);
1108
+ const width = attrInt(tcNode, "width", 0);
1109
+ const height = attrInt(tcNode, "height", 0);
1110
+ const borderFillIDRef = attrInt(tcNode, "borderFillIDRef", 1);
1111
+ let padding;
1112
+ if (tcNode.attrs["paddingLeft"] !== void 0) {
1113
+ padding = {
1114
+ left: attrInt(tcNode, "paddingLeft"),
1115
+ right: attrInt(tcNode, "paddingRight"),
1116
+ top: attrInt(tcNode, "paddingTop"),
1117
+ bottom: attrInt(tcNode, "paddingBottom")
1118
+ };
1119
+ }
1120
+ const subList = findChild(tcNode, "hp:subList");
1121
+ const paragraphs = subList ? parseParagraphs(subList) : [];
1122
+ return { paragraphs, colSpan, rowSpan, width, height, borderFillIDRef, padding };
1123
+ }
1124
+ function parsePicture(picNode) {
1125
+ return {
1126
+ binDataIDRef: attrInt(picNode, "binDataIDRef", 0),
1127
+ width: attrInt(picNode, "width", 0),
1128
+ height: attrInt(picNode, "height", 0),
1129
+ offsetX: picNode.attrs["offsetX"] !== void 0 ? attrInt(picNode, "offsetX") : void 0,
1130
+ offsetY: picNode.attrs["offsetY"] !== void 0 ? attrInt(picNode, "offsetY") : void 0
1131
+ };
1132
+ }
1133
+ function getTextContent(node) {
1134
+ let text = node.text;
1135
+ for (const child of node.children) {
1136
+ text += getTextContent(child);
1137
+ }
1138
+ return unescapeXml(text);
1139
+ }
1140
+ function unescapeXml(str) {
1141
+ return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'");
1142
+ }
1143
+ function defaultSectionDef() {
1144
+ return {
1145
+ pageWidth: 59528,
1146
+ pageHeight: 84188,
1147
+ pageMargin: defaultPageMargin(),
1148
+ landscape: false
1149
+ };
1150
+ }
1151
+ function defaultPageMargin() {
1152
+ return {
1153
+ left: 8504,
1154
+ right: 8504,
1155
+ top: 7087,
1156
+ bottom: 7087,
1157
+ header: 4252,
1158
+ footer: 4252,
1159
+ gutter: 0
1160
+ };
1161
+ }
1162
+
1163
+ // src/codecs/hwpx/xml/parse-content-hpf.ts
1164
+ function parseContentHpfXml(xml) {
1165
+ const root = parseXml(xml);
1166
+ const metadata = findChild(root, "opf:metadata");
1167
+ const titleNode = metadata ? findChild(metadata, "opf:title") : void 0;
1168
+ const title = titleNode ? titleNode.text : "";
1169
+ const manifest = findChild(root, "opf:manifest");
1170
+ const items = manifest ? findChildren(manifest, "opf:item") : [];
1171
+ const sectionFiles = [];
1172
+ const binDataFiles = [];
1173
+ for (const item of items) {
1174
+ const id = attrStr(item, "id");
1175
+ const href = attrStr(item, "href");
1176
+ const mediaType = attrStr(item, "media-type");
1177
+ if (id.startsWith("section")) {
1178
+ sectionFiles.push(href);
1179
+ } else if (id.startsWith("bindata")) {
1180
+ binDataFiles.push({ id, href, mediaType });
1181
+ }
1182
+ }
1183
+ if (sectionFiles.length === 0) {
1184
+ const spine = findChild(root, "opf:spine");
1185
+ if (spine) {
1186
+ for (const itemref of findChildren(spine, "opf:itemref")) {
1187
+ const idref = attrStr(itemref, "idref");
1188
+ if (idref.startsWith("section")) {
1189
+ sectionFiles.push(`${idref}.xml`);
1190
+ }
1191
+ }
1192
+ }
1193
+ }
1194
+ return { title, sectionFiles, binDataFiles };
1195
+ }
1196
+
1197
+ // src/errors.ts
1198
+ var HWPXError = class extends Error {
1199
+ constructor(message) {
1200
+ super(message);
1201
+ this.name = "HWPXError";
1202
+ }
1203
+ };
1204
+ var HWPXValidationError = class extends HWPXError {
1205
+ constructor(field, message) {
1206
+ super(`Validation error on "${field}": ${message}`);
1207
+ this.name = "HWPXValidationError";
1208
+ this.field = field;
1209
+ }
1210
+ };
1211
+
1212
+ // src/codecs/hwpx/reader.ts
1213
+ function readHwpx(data, _opts) {
1214
+ const files = extractZip(data);
1215
+ const mimetype = files["mimetype"];
1216
+ if (mimetype) {
1217
+ const mt = decodeUtf8(mimetype);
1218
+ if (!mt.includes("hwp")) {
1219
+ throw new HWPXError(`Invalid mimetype: "${mt}"`);
1220
+ }
1221
+ }
1222
+ const contentHpf = getFile(files, "Contents/content.hpf");
1223
+ const hpfInfo = parseContentHpfXml(decodeUtf8(contentHpf));
1224
+ const headerXml = getFile(files, "Contents/header.xml");
1225
+ const head = parseHeaderXml(decodeUtf8(headerXml));
1226
+ const sections = hpfInfo.sectionFiles.map((file) => {
1227
+ const path = file.startsWith("Contents/") ? file : `Contents/${file}`;
1228
+ const sectionData = getFile(files, path);
1229
+ return parseSectionXml(decodeUtf8(sectionData));
1230
+ });
1231
+ const meta = parseMeta(files, hpfInfo.title);
1232
+ const binData = parseBinData(files, hpfInfo.binDataFiles);
1233
+ return { meta, head, sections, binData };
1234
+ }
1235
+ function getFile(files, path) {
1236
+ const data = files[path];
1237
+ if (!data) {
1238
+ throw new HWPXError(`Required file not found in HWPX archive: ${path}`);
1239
+ }
1240
+ return data;
1241
+ }
1242
+ function parseMeta(files, title) {
1243
+ const meta = {
1244
+ hwpVersion: "5.1.0.1",
1245
+ title: title || void 0
1246
+ };
1247
+ const versionData = files["version.xml"];
1248
+ if (versionData) {
1249
+ const versionXml = decodeUtf8(versionData);
1250
+ const versionMatch = versionXml.match(/<ha:appversion[^>]*>([^<]+)/);
1251
+ if (versionMatch) {
1252
+ meta.hwpVersion = versionMatch[1];
1253
+ }
1254
+ }
1255
+ return meta;
1256
+ }
1257
+ function parseBinData(files, binDataFiles) {
1258
+ const items = [];
1259
+ for (let i = 0; i < binDataFiles.length; i++) {
1260
+ const { href, mediaType } = binDataFiles[i];
1261
+ const cleanPath = href.replace(/^\.\.\//, "");
1262
+ const path = cleanPath.startsWith("BinData/") ? cleanPath : `BinData/${cleanPath}`;
1263
+ const data = files[path];
1264
+ if (!data) continue;
1265
+ const name = path.replace("BinData/", "");
1266
+ const format = mediaTypeToFormat(mediaType);
1267
+ items.push({
1268
+ id: i + 1,
1269
+ format,
1270
+ name,
1271
+ data
1272
+ });
1273
+ }
1274
+ return items;
1275
+ }
1276
+ function mediaTypeToFormat(mediaType) {
1277
+ const map = {
1278
+ "image/jpeg": "jpg",
1279
+ "image/png": "png",
1280
+ "image/gif": "gif",
1281
+ "image/bmp": "bmp",
1282
+ "image/tiff": "tiff",
1283
+ "image/x-wmf": "wmf",
1284
+ "image/x-emf": "emf"
1285
+ };
1286
+ return map[mediaType] || "png";
1287
+ }
1288
+
1289
+ // src/codecs/hwp5/reader.ts
1290
+ import { inflateSync } from "fflate";
1291
+
1292
+ // src/codecs/hwp5/ole.ts
1293
+ var HEADER_SIZE = 512;
1294
+ var DIR_ENTRY_SIZE = 128;
1295
+ var ENDOFCHAIN = 4294967294;
1296
+ function parseOle(data) {
1297
+ if (data.length < HEADER_SIZE) {
1298
+ throw new HWPXError("File too small for OLE format");
1299
+ }
1300
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1301
+ if (view.getUint32(0, true) !== 3759263696 || view.getUint32(4, true) !== 2712738529) {
1302
+ throw new HWPXError("Invalid OLE magic number");
1303
+ }
1304
+ const sectorSize = 1 << view.getUint16(30, true);
1305
+ const miniSectorSize = 1 << view.getUint16(32, true);
1306
+ const fatSectors = view.getInt32(44, true);
1307
+ const firstDirSector = view.getInt32(48, true);
1308
+ const miniStreamCutoff = view.getUint32(56, true);
1309
+ const firstMiniFatSector = view.getInt32(60, true);
1310
+ const miniSectorCount = view.getInt32(64, true);
1311
+ const firstDifatSector = view.getInt32(68, true);
1312
+ const difatCount = view.getInt32(72, true);
1313
+ const difat = [];
1314
+ for (let i = 0; i < 109 && i < fatSectors; i++) {
1315
+ difat.push(view.getInt32(76 + i * 4, true));
1316
+ }
1317
+ let difatSector = firstDifatSector;
1318
+ while (difatSector >= 0 && difatSector !== ENDOFCHAIN && difat.length < fatSectors) {
1319
+ const offset = sectorOffset(difatSector, sectorSize);
1320
+ const entriesPerSector = sectorSize / 4 - 1;
1321
+ for (let i = 0; i < entriesPerSector && difat.length < fatSectors; i++) {
1322
+ difat.push(view.getInt32(offset + i * 4, true));
1323
+ }
1324
+ difatSector = view.getInt32(offset + entriesPerSector * 4, true);
1325
+ }
1326
+ const fat = [];
1327
+ for (const fatSectorIdx of difat) {
1328
+ if (fatSectorIdx < 0) break;
1329
+ const offset = sectorOffset(fatSectorIdx, sectorSize);
1330
+ for (let i = 0; i < sectorSize / 4; i++) {
1331
+ fat.push(view.getInt32(offset + i * 4, true));
1332
+ }
1333
+ }
1334
+ const dirData = readSectorChain(data, fat, firstDirSector, sectorSize);
1335
+ const entries = parseDirectoryEntries(dirData);
1336
+ let miniFat = [];
1337
+ if (firstMiniFatSector >= 0 && firstMiniFatSector !== ENDOFCHAIN) {
1338
+ const miniFatData = readSectorChain(data, fat, firstMiniFatSector, sectorSize);
1339
+ const miniFatView = new DataView(miniFatData.buffer, miniFatData.byteOffset, miniFatData.byteLength);
1340
+ for (let i = 0; i < miniFatData.length / 4; i++) {
1341
+ miniFat.push(miniFatView.getInt32(i * 4, true));
1342
+ }
1343
+ }
1344
+ let miniStream = new Uint8Array(0);
1345
+ if (entries.length > 0 && entries[0].startSector >= 0) {
1346
+ miniStream = readSectorChain(data, fat, entries[0].startSector, sectorSize);
1347
+ }
1348
+ function getStreamByEntry(entry) {
1349
+ if (entry.size === 0) return new Uint8Array(0);
1350
+ if (entry.size < miniStreamCutoff) {
1351
+ return readMiniSectorChain(miniStream, miniFat, entry.startSector, miniSectorSize, entry.size);
1352
+ } else {
1353
+ const raw = readSectorChain(data, fat, entry.startSector, sectorSize);
1354
+ return raw.slice(0, entry.size);
1355
+ }
1356
+ }
1357
+ function getStream(name) {
1358
+ const entry = entries.find((e) => e.name === name && e.type === 2);
1359
+ if (!entry) {
1360
+ throw new HWPXError(`OLE stream not found: ${name}`);
1361
+ }
1362
+ return getStreamByEntry(entry);
1363
+ }
1364
+ function listStreams() {
1365
+ return entries.filter((e) => e.type === 2).map((e) => e.name);
1366
+ }
1367
+ return { entries, getStream, getStreamByEntry, listStreams };
1368
+ }
1369
+ function sectorOffset(sector, sectorSize) {
1370
+ return HEADER_SIZE + sector * sectorSize;
1371
+ }
1372
+ function readSectorChain(data, fat, startSector, sectorSize) {
1373
+ const sectors = [];
1374
+ let sector = startSector;
1375
+ const maxSectors = fat.length;
1376
+ while (sector >= 0 && sector !== ENDOFCHAIN && sectors.length < maxSectors) {
1377
+ sectors.push(sector);
1378
+ sector = fat[sector] ?? ENDOFCHAIN;
1379
+ }
1380
+ const result = new Uint8Array(sectors.length * sectorSize);
1381
+ for (let i = 0; i < sectors.length; i++) {
1382
+ const offset = sectorOffset(sectors[i], sectorSize);
1383
+ const chunk = data.subarray(offset, offset + sectorSize);
1384
+ result.set(chunk, i * sectorSize);
1385
+ }
1386
+ return result;
1387
+ }
1388
+ function readMiniSectorChain(miniStream, miniFat, startSector, miniSectorSize, size) {
1389
+ const sectors = [];
1390
+ let sector = startSector;
1391
+ const maxSectors = miniFat.length;
1392
+ while (sector >= 0 && sector !== ENDOFCHAIN && sectors.length < maxSectors) {
1393
+ sectors.push(sector);
1394
+ sector = miniFat[sector] ?? ENDOFCHAIN;
1395
+ }
1396
+ const result = new Uint8Array(sectors.length * miniSectorSize);
1397
+ for (let i = 0; i < sectors.length; i++) {
1398
+ const offset = sectors[i] * miniSectorSize;
1399
+ const chunk = miniStream.subarray(offset, offset + miniSectorSize);
1400
+ result.set(chunk, i * miniSectorSize);
1401
+ }
1402
+ return result.slice(0, size);
1403
+ }
1404
+ function parseDirectoryEntries(data) {
1405
+ const entries = [];
1406
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1407
+ for (let offset = 0; offset + DIR_ENTRY_SIZE <= data.length; offset += DIR_ENTRY_SIZE) {
1408
+ const nameLen = view.getUint16(offset + 64, true);
1409
+ if (nameLen === 0) continue;
1410
+ const nameBytes = data.subarray(offset, offset + nameLen - 2);
1411
+ let name = "";
1412
+ for (let i = 0; i < nameBytes.length; i += 2) {
1413
+ const code = nameBytes[i] | nameBytes[i + 1] << 8;
1414
+ if (code === 0) break;
1415
+ name += String.fromCharCode(code);
1416
+ }
1417
+ const type = data[offset + 66];
1418
+ const startSector = view.getInt32(offset + 116, true);
1419
+ const size = view.getUint32(offset + 120, true);
1420
+ const leftSiblingId = view.getInt32(offset + 68, true);
1421
+ const rightSiblingId = view.getInt32(offset + 72, true);
1422
+ const childId = view.getInt32(offset + 76, true);
1423
+ entries.push({
1424
+ name,
1425
+ type,
1426
+ size,
1427
+ startSector,
1428
+ childId,
1429
+ leftSiblingId,
1430
+ rightSiblingId
1431
+ });
1432
+ }
1433
+ return entries;
1434
+ }
1435
+
1436
+ // src/codecs/hwp5/records.ts
1437
+ var TAG = {
1438
+ DOCUMENT_PROPERTIES: 16,
1439
+ ID_MAPPINGS: 17,
1440
+ BIN_DATA: 18,
1441
+ FACE_NAME: 19,
1442
+ BORDER_FILL: 20,
1443
+ CHAR_SHAPE: 21,
1444
+ TAB_DEF: 22,
1445
+ NUMBERING: 23,
1446
+ BULLET: 24,
1447
+ PARA_SHAPE: 25,
1448
+ STYLE: 26,
1449
+ // BodyText
1450
+ PARA_HEADER: 66,
1451
+ PARA_TEXT: 67,
1452
+ PARA_CHAR_SHAPE: 68,
1453
+ PARA_LINE_SEG: 69,
1454
+ CTRL_HEADER: 71,
1455
+ PAGE_DEF: 72,
1456
+ FOOTNOTE_SHAPE: 73,
1457
+ PAGE_BORDER_FILL: 74,
1458
+ LIST_HEADER: 75,
1459
+ TABLE: 79,
1460
+ CTRL_DATA: 80,
1461
+ SHAPE_COMPONENT: 82
1462
+ };
1463
+ function parseRecords(data) {
1464
+ const records = [];
1465
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1466
+ let offset = 0;
1467
+ while (offset + 4 <= data.length) {
1468
+ const header = view.getUint32(offset, true);
1469
+ const tagId = header & 1023;
1470
+ const level = header >> 10 & 1023;
1471
+ let size = header >> 20 & 4095;
1472
+ offset += 4;
1473
+ if (size === 4095) {
1474
+ if (offset + 4 > data.length) break;
1475
+ size = view.getUint32(offset, true);
1476
+ offset += 4;
1477
+ }
1478
+ if (offset + size > data.length) break;
1479
+ records.push({
1480
+ tagId,
1481
+ level,
1482
+ size,
1483
+ data: data.subarray(offset, offset + size)
1484
+ });
1485
+ offset += size;
1486
+ }
1487
+ return records;
1488
+ }
1489
+ function readUtf16(data, offset, charCount) {
1490
+ let str = "";
1491
+ for (let i = 0; i < charCount; i++) {
1492
+ const code = data[offset + i * 2] | data[offset + i * 2 + 1] << 8;
1493
+ if (code === 0) break;
1494
+ str += String.fromCharCode(code);
1495
+ }
1496
+ return str;
1497
+ }
1498
+ function readU16(data, offset) {
1499
+ return data[offset] | data[offset + 1] << 8;
1500
+ }
1501
+ function readU32(data, offset) {
1502
+ return (data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16 | data[offset + 3] << 24) >>> 0;
1503
+ }
1504
+ function readI32(data, offset) {
1505
+ return data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16 | data[offset + 3] << 24;
1506
+ }
1507
+
1508
+ // src/utils/units.ts
1509
+ var HWPUNIT_PER_INCH = 7200;
1510
+ var MM_PER_INCH = 25.4;
1511
+ var PT_PER_INCH = 72;
1512
+ function mmToHwpunit(mm) {
1513
+ return Math.round(mm / MM_PER_INCH * HWPUNIT_PER_INCH);
1514
+ }
1515
+ function hwpunitToMm(hu) {
1516
+ return hu / HWPUNIT_PER_INCH * MM_PER_INCH;
1517
+ }
1518
+ function ptToHwpunit(pt) {
1519
+ return Math.round(pt / PT_PER_INCH * HWPUNIT_PER_INCH);
1520
+ }
1521
+ function hwpunitToPt(hu) {
1522
+ return hu / HWPUNIT_PER_INCH * PT_PER_INCH;
1523
+ }
1524
+ function pxToHwpunit(px, dpi = 96) {
1525
+ return Math.round(px / dpi * HWPUNIT_PER_INCH);
1526
+ }
1527
+ function hwpunitToPx(hu, dpi = 96) {
1528
+ return hu / HWPUNIT_PER_INCH * dpi;
1529
+ }
1530
+ function ptToCharHeight(pt) {
1531
+ return Math.round(pt * 100);
1532
+ }
1533
+ function charHeightToPt(height) {
1534
+ return height / 100;
1535
+ }
1536
+
1537
+ // src/utils/color.ts
1538
+ function hexToColorref(hex) {
1539
+ const h = hex.replace("#", "");
1540
+ const r = parseInt(h.substring(0, 2), 16);
1541
+ const g = parseInt(h.substring(2, 4), 16);
1542
+ const b = parseInt(h.substring(4, 6), 16);
1543
+ return b << 16 | g << 8 | r;
1544
+ }
1545
+ function colorrefToHex(colorref) {
1546
+ const r = colorref & 255;
1547
+ const g = colorref >> 8 & 255;
1548
+ const b = colorref >> 16 & 255;
1549
+ return "#" + [r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("");
1550
+ }
1551
+ function colorrefToRgbString(colorref) {
1552
+ const r = colorref & 255;
1553
+ const g = colorref >> 8 & 255;
1554
+ const b = colorref >> 16 & 255;
1555
+ return `${r}, ${g}, ${b}`;
1556
+ }
1557
+ function rgbToColorref(rgb) {
1558
+ const r = rgb >> 16 & 255;
1559
+ const g = rgb >> 8 & 255;
1560
+ const b = rgb & 255;
1561
+ return b << 16 | g << 8 | r;
1562
+ }
1563
+
1564
+ // src/defaults.ts
1565
+ function defaultFontRef() {
1566
+ return { hangul: 0, latin: 0, hanja: 0, japanese: 0, other: 0, symbol: 0, user: 0 };
1567
+ }
1568
+ function createDefaultFontFaces() {
1569
+ return [
1570
+ { lang: "HANGUL", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" },
1571
+ { lang: "LATIN", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" },
1572
+ { lang: "HANJA", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" },
1573
+ { lang: "JAPANESE", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" },
1574
+ { lang: "OTHER", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" },
1575
+ { lang: "SYMBOL", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" },
1576
+ { lang: "USER", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" }
1577
+ ];
1578
+ }
1579
+ function createDefaultCharProperty(id = 0) {
1580
+ return {
1581
+ id,
1582
+ height: ptToCharHeight(10),
1583
+ // 10pt
1584
+ textColor: hexToColorref("#000000"),
1585
+ bold: false,
1586
+ italic: false,
1587
+ underline: "NONE",
1588
+ strikeout: "NONE",
1589
+ fontRef: defaultFontRef(),
1590
+ useFontSpace: false,
1591
+ useKerning: true,
1592
+ relSize: 100,
1593
+ spacing: 0,
1594
+ charOffset: 0
1595
+ };
1596
+ }
1597
+ function createDefaultParaProperty(id = 0) {
1598
+ return {
1599
+ id,
1600
+ alignment: "JUSTIFY",
1601
+ lineSpacing: { type: "PERCENT", value: 160 },
1602
+ paraMargin: {
1603
+ left: 0,
1604
+ right: 0,
1605
+ indent: 0,
1606
+ prevSpacing: 0,
1607
+ nextSpacing: 0
1608
+ }
1609
+ };
1610
+ }
1611
+ function createDefaultBorderFill(id = 1) {
1612
+ const noBorder = {
1613
+ type: "NONE",
1614
+ width: "0.1mm",
1615
+ color: hexToColorref("#000000")
1616
+ };
1617
+ return {
1618
+ id,
1619
+ borders: {
1620
+ left: { ...noBorder },
1621
+ right: { ...noBorder },
1622
+ top: { ...noBorder },
1623
+ bottom: { ...noBorder }
1624
+ }
1625
+ };
1626
+ }
1627
+ function createDefaultStyles() {
1628
+ return [
1629
+ {
1630
+ id: 0,
1631
+ type: "PARA",
1632
+ name: "\uBC14\uD0D5\uAE00",
1633
+ engName: "Normal",
1634
+ paraPrIDRef: 0,
1635
+ charPrIDRef: 0,
1636
+ nextStyleIDRef: 0
1637
+ }
1638
+ ];
1639
+ }
1640
+ function createDefaultSectionDef() {
1641
+ return {
1642
+ pageWidth: mmToHwpunit(210),
1643
+ pageHeight: mmToHwpunit(297),
1644
+ pageMargin: createDefaultPageMargin(),
1645
+ landscape: false,
1646
+ gutterType: "LEFT_ONLY"
1647
+ };
1648
+ }
1649
+ function createDefaultPageMargin() {
1650
+ return {
1651
+ left: mmToHwpunit(30),
1652
+ right: mmToHwpunit(30),
1653
+ top: mmToHwpunit(25),
1654
+ bottom: mmToHwpunit(25),
1655
+ header: mmToHwpunit(15),
1656
+ footer: mmToHwpunit(15),
1657
+ gutter: 0
1658
+ };
1659
+ }
1660
+ function createDefaultHead() {
1661
+ return {
1662
+ fontFaces: createDefaultFontFaces(),
1663
+ charProperties: [createDefaultCharProperty(0)],
1664
+ paraProperties: [createDefaultParaProperty(0)],
1665
+ styles: createDefaultStyles(),
1666
+ borderFills: [createDefaultBorderFill(1)],
1667
+ bulletProperties: [],
1668
+ numberingProperties: [],
1669
+ compatibleDoc: "HWP"
1670
+ };
1671
+ }
1672
+ function createDefaultMeta() {
1673
+ return {
1674
+ hwpVersion: "5.1.0.1"
1675
+ };
1676
+ }
1677
+ function createDefaultDocument() {
1678
+ return {
1679
+ meta: createDefaultMeta(),
1680
+ head: createDefaultHead(),
1681
+ sections: [
1682
+ {
1683
+ def: createDefaultSectionDef(),
1684
+ paragraphs: []
1685
+ }
1686
+ ],
1687
+ binData: []
1688
+ };
1689
+ }
1690
+
1691
+ // src/codecs/hwp5/reader.ts
1692
+ function readHwp5(data) {
1693
+ const ole = parseOle(data);
1694
+ const fileHeader = ole.getStream("FileHeader");
1695
+ const compressed = isCompressed(fileHeader);
1696
+ const hwpVersion = readHwpVersion(fileHeader);
1697
+ const docInfoRaw = ole.getStream("DocInfo");
1698
+ const docInfoData = compressed ? tryDecompress(docInfoRaw) : docInfoRaw;
1699
+ const docInfoRecords = parseRecords(docInfoData);
1700
+ const head = parseDocInfo(docInfoRecords);
1701
+ const sections = parseBodyText(ole, compressed);
1702
+ const meta = {
1703
+ ...createDefaultMeta(),
1704
+ hwpVersion
1705
+ };
1706
+ return { meta, head, sections, binData: [] };
1707
+ }
1708
+ function isCompressed(fileHeader) {
1709
+ if (fileHeader.length < 40) return false;
1710
+ const flags = readU32(fileHeader, 36);
1711
+ return (flags & 1) !== 0;
1712
+ }
1713
+ function readHwpVersion(fileHeader) {
1714
+ if (fileHeader.length < 36) return "5.0.0.0";
1715
+ const major = fileHeader[35];
1716
+ const minor = fileHeader[34];
1717
+ const build = fileHeader[33];
1718
+ const revision = fileHeader[32];
1719
+ return `${major}.${minor}.${build}.${revision}`;
1720
+ }
1721
+ function tryDecompress(data) {
1722
+ try {
1723
+ return inflateSync(data);
1724
+ } catch {
1725
+ return data;
1726
+ }
1727
+ }
1728
+ function parseDocInfo(records) {
1729
+ const head = createDefaultHead();
1730
+ head.fontFaces = [];
1731
+ head.charProperties = [];
1732
+ head.paraProperties = [];
1733
+ head.styles = [];
1734
+ head.borderFills = [];
1735
+ let fontId = 0;
1736
+ let charPrId = 0;
1737
+ let paraPrId = 0;
1738
+ let styleId = 0;
1739
+ let borderFillId = 1;
1740
+ for (const rec of records) {
1741
+ switch (rec.tagId) {
1742
+ case TAG.FACE_NAME: {
1743
+ const face = parseFaceName(rec.data, fontId);
1744
+ if (face) {
1745
+ head.fontFaces.push(face);
1746
+ fontId++;
1747
+ }
1748
+ break;
1749
+ }
1750
+ case TAG.CHAR_SHAPE: {
1751
+ const cp = parseCharShape(rec.data, charPrId);
1752
+ if (cp) {
1753
+ head.charProperties.push(cp);
1754
+ charPrId++;
1755
+ }
1756
+ break;
1757
+ }
1758
+ case TAG.PARA_SHAPE: {
1759
+ const pp = parseParaShape(rec.data, paraPrId);
1760
+ if (pp) {
1761
+ head.paraProperties.push(pp);
1762
+ paraPrId++;
1763
+ }
1764
+ break;
1765
+ }
1766
+ case TAG.STYLE: {
1767
+ const style = parseStyle(rec.data, styleId);
1768
+ if (style) {
1769
+ head.styles.push(style);
1770
+ styleId++;
1771
+ }
1772
+ break;
1773
+ }
1774
+ case TAG.BORDER_FILL: {
1775
+ const bf = parseBorderFillRecord(rec.data, borderFillId);
1776
+ if (bf) {
1777
+ head.borderFills.push(bf);
1778
+ borderFillId++;
1779
+ }
1780
+ break;
1781
+ }
1782
+ }
1783
+ }
1784
+ if (head.fontFaces.length === 0) head.fontFaces = [{ lang: "HANGUL", fontName: "\uD568\uCD08\uB86C\uB3CB\uC6C0", fontType: "ttf" }];
1785
+ if (head.charProperties.length === 0) head.charProperties = [{ id: 0, height: 1e3, textColor: 0, fontRef: { hangul: 0, latin: 0, hanja: 0, japanese: 0, other: 0, symbol: 0, user: 0 } }];
1786
+ if (head.paraProperties.length === 0) head.paraProperties = [{ id: 0, alignment: "JUSTIFY", lineSpacing: { type: "PERCENT", value: 160 }, paraMargin: { left: 0, right: 0, indent: 0, prevSpacing: 0, nextSpacing: 0 } }];
1787
+ if (head.styles.length === 0) head.styles = [{ id: 0, type: "PARA", name: "\uBC14\uD0D5\uAE00", engName: "Normal", paraPrIDRef: 0, charPrIDRef: 0 }];
1788
+ if (head.borderFills.length === 0) head.borderFills = [createDefaultBorderFill(1)];
1789
+ return head;
1790
+ }
1791
+ function parseFaceName(data, id) {
1792
+ if (data.length < 6) return null;
1793
+ const nameLen = readU16(data, 2);
1794
+ if (data.length < 4 + nameLen * 2) return null;
1795
+ const fontName = readUtf16(data, 4, nameLen);
1796
+ const langMap = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
1797
+ const lang = langMap[id % 7] || "HANGUL";
1798
+ return { lang, fontName, fontType: "ttf" };
1799
+ }
1800
+ function parseCharShape(data, id) {
1801
+ if (data.length < 72) return null;
1802
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1803
+ const fontRef = {
1804
+ hangul: readU16(data, 0),
1805
+ latin: readU16(data, 2),
1806
+ hanja: readU16(data, 4),
1807
+ japanese: readU16(data, 6),
1808
+ other: readU16(data, 8),
1809
+ symbol: readU16(data, 10),
1810
+ user: readU16(data, 12)
1811
+ };
1812
+ const height = readI32(data, 70);
1813
+ let textColor = 0;
1814
+ if (data.length >= 78) {
1815
+ textColor = readU32(data, 74);
1816
+ }
1817
+ let bold = false;
1818
+ let italic = false;
1819
+ if (data.length >= 74) {
1820
+ const attr = readU32(data, 70);
1821
+ }
1822
+ return {
1823
+ id,
1824
+ height: height > 0 ? height : 1e3,
1825
+ textColor: textColor & 16777215,
1826
+ fontRef,
1827
+ bold,
1828
+ italic
1829
+ };
1830
+ }
1831
+ function parseParaShape(data, id) {
1832
+ if (data.length < 8) return null;
1833
+ const attr = readU32(data, 0);
1834
+ const alignment = ["JUSTIFY", "LEFT", "RIGHT", "CENTER", "DISTRIBUTE", "DISTRIBUTE_SPACE"][attr & 7] || "JUSTIFY";
1835
+ let leftMargin = 0, rightMargin = 0, indent = 0;
1836
+ if (data.length >= 20) {
1837
+ leftMargin = readI32(data, 4);
1838
+ rightMargin = readI32(data, 8);
1839
+ indent = readI32(data, 12);
1840
+ }
1841
+ let prevSpacing = 0, nextSpacing = 0;
1842
+ if (data.length >= 28) {
1843
+ prevSpacing = readI32(data, 16);
1844
+ nextSpacing = readI32(data, 20);
1845
+ }
1846
+ let lineSpacingValue = 160;
1847
+ if (data.length >= 32) {
1848
+ lineSpacingValue = readI32(data, 24);
1849
+ }
1850
+ return {
1851
+ id,
1852
+ alignment,
1853
+ lineSpacing: { type: "PERCENT", value: lineSpacingValue > 0 ? lineSpacingValue : 160 },
1854
+ paraMargin: {
1855
+ left: Math.max(0, leftMargin),
1856
+ right: Math.max(0, rightMargin),
1857
+ indent,
1858
+ prevSpacing: Math.max(0, prevSpacing),
1859
+ nextSpacing: Math.max(0, nextSpacing)
1860
+ }
1861
+ };
1862
+ }
1863
+ function parseStyle(data, id) {
1864
+ if (data.length < 8) return null;
1865
+ const nameLen = readU16(data, 0);
1866
+ let offset = 2;
1867
+ const name = readUtf16(data, offset, nameLen);
1868
+ offset += nameLen * 2;
1869
+ let engName = "";
1870
+ if (offset + 2 <= data.length) {
1871
+ const engNameLen = readU16(data, offset);
1872
+ offset += 2;
1873
+ if (offset + engNameLen * 2 <= data.length) {
1874
+ engName = readUtf16(data, offset, engNameLen);
1875
+ offset += engNameLen * 2;
1876
+ }
1877
+ }
1878
+ let type = "PARA";
1879
+ let paraPrIDRef = 0;
1880
+ let charPrIDRef = 0;
1881
+ if (offset + 4 <= data.length) {
1882
+ const styleType = data[offset];
1883
+ type = styleType === 1 ? "CHAR" : "PARA";
1884
+ offset += 1;
1885
+ }
1886
+ return {
1887
+ id,
1888
+ type,
1889
+ name: name || `Style${id}`,
1890
+ engName: engName || void 0,
1891
+ paraPrIDRef,
1892
+ charPrIDRef
1893
+ };
1894
+ }
1895
+ function parseBorderFillRecord(data, id) {
1896
+ if (data.length < 2) return null;
1897
+ return createDefaultBorderFill(id);
1898
+ }
1899
+ function parseBodyText(ole, compressed) {
1900
+ const sections = [];
1901
+ const streams = ole.listStreams();
1902
+ const sectionStreams = streams.filter((name) => /^Section\d+$/.test(name)).sort((a, b) => {
1903
+ const numA = parseInt(a.replace("Section", ""), 10);
1904
+ const numB = parseInt(b.replace("Section", ""), 10);
1905
+ return numA - numB;
1906
+ });
1907
+ if (sectionStreams.length === 0) {
1908
+ for (const entry of ole.entries) {
1909
+ if (/^Section\d+$/.test(entry.name) && entry.type === 2) {
1910
+ sectionStreams.push(entry.name);
1911
+ }
1912
+ }
1913
+ sectionStreams.sort();
1914
+ }
1915
+ for (const streamName of sectionStreams) {
1916
+ try {
1917
+ const raw = ole.getStream(streamName);
1918
+ const sectionData = compressed ? tryDecompress(raw) : raw;
1919
+ const records = parseRecords(sectionData);
1920
+ sections.push(parseSectionRecords(records));
1921
+ } catch {
1922
+ sections.push({ def: createDefaultSectionDef(), paragraphs: [] });
1923
+ }
1924
+ }
1925
+ if (sections.length === 0) {
1926
+ sections.push({ def: createDefaultSectionDef(), paragraphs: [] });
1927
+ }
1928
+ return sections;
1929
+ }
1930
+ function parseSectionRecords(records) {
1931
+ const paragraphs = [];
1932
+ let currentParaCharShapeRef = 0;
1933
+ let currentParaStyleRef = 0;
1934
+ for (let i = 0; i < records.length; i++) {
1935
+ const rec = records[i];
1936
+ if (rec.tagId === TAG.PARA_HEADER) {
1937
+ if (rec.data.length >= 4) {
1938
+ const nCharShapeRef = readU32(rec.data, 0);
1939
+ }
1940
+ let textRec = null;
1941
+ let charShapeRec = null;
1942
+ for (let j = i + 1; j < records.length && records[j].level > rec.level; j++) {
1943
+ if (records[j].tagId === TAG.PARA_TEXT && !textRec) {
1944
+ textRec = records[j];
1945
+ }
1946
+ if (records[j].tagId === TAG.PARA_CHAR_SHAPE && !charShapeRec) {
1947
+ charShapeRec = records[j];
1948
+ }
1949
+ }
1950
+ const text = textRec ? parseParaText(textRec.data) : "";
1951
+ const charPrIDRef = charShapeRec ? readU32(charShapeRec.data, 4) : 0;
1952
+ const paraPrIDRef = rec.data.length >= 6 ? readU16(rec.data, 4) : 0;
1953
+ const styleIDRef = rec.data.length >= 8 ? readU16(rec.data, 6) : 0;
1954
+ paragraphs.push({
1955
+ paraPrIDRef,
1956
+ styleIDRef,
1957
+ runs: [{ t: "text", text, charPrIDRef }]
1958
+ });
1959
+ }
1960
+ if (rec.tagId === TAG.PAGE_DEF) {
1961
+ }
1962
+ }
1963
+ return {
1964
+ def: createDefaultSectionDef(),
1965
+ paragraphs
1966
+ };
1967
+ }
1968
+ function parseParaText(data) {
1969
+ let text = "";
1970
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1971
+ for (let offset = 0; offset + 1 < data.length; offset += 2) {
1972
+ const ch = view.getUint16(offset, true);
1973
+ if (ch < 32) {
1974
+ switch (ch) {
1975
+ case 13:
1976
+ // paragraph break
1977
+ case 10:
1978
+ break;
1979
+ case 9:
1980
+ text += " ";
1981
+ break;
1982
+ case 1:
1983
+ // reserved
1984
+ case 2:
1985
+ // section/column control
1986
+ case 3:
1987
+ offset += 14;
1988
+ break;
1989
+ case 4:
1990
+ // field end
1991
+ case 11:
1992
+ // control char (inline)
1993
+ case 12:
1994
+ // control char (extended)
1995
+ case 15:
1996
+ // hyphen
1997
+ case 16:
1998
+ // reserved
1999
+ case 17:
2000
+ // reserved
2001
+ case 18:
2002
+ // reserved
2003
+ case 23:
2004
+ // reserved
2005
+ case 24:
2006
+ break;
2007
+ default:
2008
+ break;
2009
+ }
2010
+ } else {
2011
+ text += String.fromCharCode(ch);
2012
+ }
2013
+ }
2014
+ return text;
2015
+ }
2016
+
2017
+ // src/utils/detect.ts
2018
+ function detectFormat(data) {
2019
+ if (data.length < 4) return "unknown";
2020
+ if (data[0] === 80 && data[1] === 75 && data[2] === 3 && data[3] === 4) {
2021
+ return "hwpx";
2022
+ }
2023
+ if (data[0] === 208 && data[1] === 207 && data[2] === 17 && data[3] === 224) {
2024
+ return "hwp5";
2025
+ }
2026
+ return "unknown";
2027
+ }
2028
+
2029
+ // src/utils/id.ts
2030
+ var IdCounter = class {
2031
+ constructor(start = 0) {
2032
+ this.value = start;
2033
+ }
2034
+ next() {
2035
+ return this.value++;
2036
+ }
2037
+ current() {
2038
+ return this.value;
2039
+ }
2040
+ reset(start = 0) {
2041
+ this.value = start;
2042
+ }
2043
+ };
2044
+
2045
+ // src/builder.ts
2046
+ var HWPXBuilder = class {
2047
+ constructor() {
2048
+ this.charPrCache = /* @__PURE__ */ new Map();
2049
+ this.doc = createDefaultDocument();
2050
+ this.charPrIdCounter = new IdCounter(1);
2051
+ this.paraPrIdCounter = new IdCounter(1);
2052
+ this.borderFillIdCounter = new IdCounter(2);
2053
+ this.binDataIdCounter = new IdCounter(1);
2054
+ }
2055
+ /** 단일 스타일 문단 추가 */
2056
+ addParagraph(text, style) {
2057
+ const charPrId = this.getOrCreateCharPr(style);
2058
+ const para = {
2059
+ paraPrIDRef: 0,
2060
+ styleIDRef: 0,
2061
+ runs: [
2062
+ {
2063
+ t: "text",
2064
+ text,
2065
+ charPrIDRef: charPrId
2066
+ }
2067
+ ]
2068
+ };
2069
+ this.currentSection().paragraphs.push(para);
2070
+ return this;
2071
+ }
2072
+ /** 복수 스타일 세그먼트로 구성된 문단 추가 */
2073
+ addStyledText(segments) {
2074
+ const runs = segments.map((seg) => ({
2075
+ t: "text",
2076
+ text: seg.text,
2077
+ charPrIDRef: this.getOrCreateCharPr(seg.style)
2078
+ }));
2079
+ const para = {
2080
+ paraPrIDRef: 0,
2081
+ styleIDRef: 0,
2082
+ runs
2083
+ };
2084
+ this.currentSection().paragraphs.push(para);
2085
+ return this;
2086
+ }
2087
+ /** 빈 문단(빈 줄) 추가 */
2088
+ addEmptyParagraph() {
2089
+ const para = {
2090
+ paraPrIDRef: 0,
2091
+ styleIDRef: 0,
2092
+ runs: [
2093
+ {
2094
+ t: "text",
2095
+ text: "",
2096
+ charPrIDRef: 0
2097
+ }
2098
+ ]
2099
+ };
2100
+ this.currentSection().paragraphs.push(para);
2101
+ return this;
2102
+ }
2103
+ /** 페이지 설정 변경 */
2104
+ setPageSettings(opts) {
2105
+ const def = this.currentSection().def;
2106
+ if (opts.width !== void 0) def.pageWidth = mmToHwpunit(opts.width);
2107
+ if (opts.height !== void 0) def.pageHeight = mmToHwpunit(opts.height);
2108
+ if (opts.landscape !== void 0) def.landscape = opts.landscape;
2109
+ if (opts.marginLeft !== void 0) def.pageMargin.left = mmToHwpunit(opts.marginLeft);
2110
+ if (opts.marginRight !== void 0) def.pageMargin.right = mmToHwpunit(opts.marginRight);
2111
+ if (opts.marginTop !== void 0) def.pageMargin.top = mmToHwpunit(opts.marginTop);
2112
+ if (opts.marginBottom !== void 0) def.pageMargin.bottom = mmToHwpunit(opts.marginBottom);
2113
+ return this;
2114
+ }
2115
+ /** 테이블 추가 (string[][] 데이터, 셀 병합은 merges 옵션) */
2116
+ addTable(data, opts) {
2117
+ const rowCount = data.length;
2118
+ const colCount = data.length > 0 ? data[0].length : 0;
2119
+ if (rowCount === 0 || colCount === 0) return this;
2120
+ const secDef = this.currentSection().def;
2121
+ const bodyWidth = opts?.width ? mmToHwpunit(opts.width) : secDef.pageWidth - secDef.pageMargin.left - secDef.pageMargin.right;
2122
+ const colWidths = opts?.colWidths ? opts.colWidths.map((w) => mmToHwpunit(w)) : Array(colCount).fill(Math.floor(bodyWidth / colCount));
2123
+ const borderFillId = this.getOrCreateTableBorderFill(opts?.borderStyle || "SOLID");
2124
+ const cellPadding = opts?.cellPadding ? mmToHwpunit(opts.cellPadding) : mmToHwpunit(1.5);
2125
+ const rowHeight = mmToHwpunit(10);
2126
+ const mergeMap = /* @__PURE__ */ new Map();
2127
+ if (opts?.merges) {
2128
+ for (const m of opts.merges) {
2129
+ mergeMap.set(`${m.row},${m.col}`, { rowSpan: m.rowSpan, colSpan: m.colSpan });
2130
+ for (let r = m.row; r < m.row + m.rowSpan; r++) {
2131
+ for (let c = m.col; c < m.col + m.colSpan; c++) {
2132
+ if (r !== m.row || c !== m.col) {
2133
+ mergeMap.set(`${r},${c}`, "hidden");
2134
+ }
2135
+ }
2136
+ }
2137
+ }
2138
+ }
2139
+ const rows = data.map((rowData, ri) => ({
2140
+ height: rowHeight,
2141
+ cells: rowData.map((cellText, ci) => {
2142
+ const key = `${ri},${ci}`;
2143
+ const mergeInfo = mergeMap.get(key);
2144
+ if (mergeInfo === "hidden") return null;
2145
+ const span = mergeInfo ? mergeInfo : { rowSpan: 1, colSpan: 1 };
2146
+ let cellWidth = 0;
2147
+ for (let c = ci; c < ci + span.colSpan && c < colWidths.length; c++) {
2148
+ cellWidth += colWidths[c];
2149
+ }
2150
+ return {
2151
+ paragraphs: [{
2152
+ paraPrIDRef: 0,
2153
+ styleIDRef: 0,
2154
+ runs: [{ t: "text", text: cellText, charPrIDRef: 0 }]
2155
+ }],
2156
+ colSpan: span.colSpan,
2157
+ rowSpan: span.rowSpan,
2158
+ width: cellWidth || colWidths[ci] || colWidths[0],
2159
+ height: rowHeight * span.rowSpan,
2160
+ borderFillIDRef: borderFillId,
2161
+ padding: {
2162
+ left: cellPadding,
2163
+ right: cellPadding,
2164
+ top: cellPadding,
2165
+ bottom: cellPadding
2166
+ }
2167
+ };
2168
+ }).filter((c) => c !== null)
2169
+ }));
2170
+ const table = {
2171
+ rows,
2172
+ borderFillIDRef: borderFillId,
2173
+ width: bodyWidth,
2174
+ rowCount,
2175
+ colCount,
2176
+ colWidths
2177
+ };
2178
+ const para = {
2179
+ paraPrIDRef: 0,
2180
+ styleIDRef: 0,
2181
+ runs: [{ t: "table", table, charPrIDRef: 0 }]
2182
+ };
2183
+ this.currentSection().paragraphs.push(para);
2184
+ return this;
2185
+ }
2186
+ /** 이미지 추가 */
2187
+ addImage(data, format, opts) {
2188
+ const id = this.binDataIdCounter.next();
2189
+ const name = `image${id}.${format}`;
2190
+ this.doc.binData.push({ id, format, name, data });
2191
+ const pic = {
2192
+ binDataIDRef: id,
2193
+ width: mmToHwpunit(opts.width),
2194
+ height: mmToHwpunit(opts.height)
2195
+ };
2196
+ const para = {
2197
+ paraPrIDRef: 0,
2198
+ styleIDRef: 0,
2199
+ runs: [{ t: "picture", picture: pic, charPrIDRef: 0 }]
2200
+ };
2201
+ this.currentSection().paragraphs.push(para);
2202
+ return this;
2203
+ }
2204
+ /** 머리글/바닥글 설정 */
2205
+ setHeaderFooter(opts) {
2206
+ const def = this.currentSection().def;
2207
+ if (!def.headerFooter) def.headerFooter = {};
2208
+ if (opts.header !== void 0) {
2209
+ def.headerFooter.header = {
2210
+ paragraphs: [{
2211
+ paraPrIDRef: 0,
2212
+ styleIDRef: 0,
2213
+ runs: [{ t: "text", text: opts.header, charPrIDRef: 0 }]
2214
+ }]
2215
+ };
2216
+ }
2217
+ if (opts.footer !== void 0) {
2218
+ def.headerFooter.footer = {
2219
+ paragraphs: [{
2220
+ paraPrIDRef: 0,
2221
+ styleIDRef: 0,
2222
+ runs: [{ t: "text", text: opts.footer, charPrIDRef: 0 }]
2223
+ }]
2224
+ };
2225
+ }
2226
+ return this;
2227
+ }
2228
+ /** 다단 설정 */
2229
+ setColumns(opts) {
2230
+ const def = this.currentSection().def;
2231
+ def.columns = {
2232
+ type: opts.type || "NORMAL",
2233
+ count: opts.count,
2234
+ gap: mmToHwpunit(opts.gap ?? 10),
2235
+ sameSizes: true
2236
+ };
2237
+ return this;
2238
+ }
2239
+ /** 새 섹션 시작 */
2240
+ addSection() {
2241
+ this.doc.sections.push({
2242
+ def: createDefaultSectionDef(),
2243
+ paragraphs: []
2244
+ });
2245
+ return this;
2246
+ }
2247
+ /** 문서 빌드 */
2248
+ build() {
2249
+ return this.doc;
2250
+ }
2251
+ // ─── Internal ───
2252
+ currentSection() {
2253
+ return this.doc.sections[this.doc.sections.length - 1];
2254
+ }
2255
+ getOrCreateCharPr(style) {
2256
+ if (!style) return 0;
2257
+ const key = this.styleKey(style);
2258
+ const cached = this.charPrCache.get(key);
2259
+ if (cached !== void 0) return cached;
2260
+ const id = this.charPrIdCounter.next();
2261
+ const base = createDefaultCharProperty(id);
2262
+ if (style.fontSize !== void 0) base.height = ptToCharHeight(style.fontSize);
2263
+ if (style.bold !== void 0) base.bold = style.bold;
2264
+ if (style.italic !== void 0) base.italic = style.italic;
2265
+ if (style.color !== void 0) base.textColor = hexToColorref(style.color);
2266
+ if (style.underline) base.underline = "BOTTOM";
2267
+ if (style.strikeout) base.strikeout = "CONTINUOUS";
2268
+ if (style.fontName) {
2269
+ const fontIdx = this.ensureFont(style.fontName);
2270
+ base.fontRef = {
2271
+ hangul: fontIdx,
2272
+ latin: fontIdx,
2273
+ hanja: fontIdx,
2274
+ japanese: fontIdx,
2275
+ other: fontIdx,
2276
+ symbol: fontIdx,
2277
+ user: fontIdx
2278
+ };
2279
+ }
2280
+ this.doc.head.charProperties.push(base);
2281
+ this.charPrCache.set(key, id);
2282
+ return id;
2283
+ }
2284
+ ensureFont(fontName) {
2285
+ const faces = this.doc.head.fontFaces;
2286
+ const hangulFaces = faces.filter((f) => f.lang === "HANGUL");
2287
+ const existing = hangulFaces.findIndex((f) => f.fontName === fontName);
2288
+ if (existing !== -1) return existing;
2289
+ const newIdx = hangulFaces.length;
2290
+ const langs = [];
2291
+ const allLangs = ["HANGUL", "LATIN", "HANJA", "JAPANESE", "OTHER", "SYMBOL", "USER"];
2292
+ for (const lang of allLangs) {
2293
+ faces.push({ lang, fontName, fontType: "ttf" });
2294
+ }
2295
+ return newIdx;
2296
+ }
2297
+ getOrCreateTableBorderFill(lineType) {
2298
+ const line = {
2299
+ type: lineType,
2300
+ width: "0.12mm",
2301
+ color: hexToColorref("#000000")
2302
+ };
2303
+ const bf = {
2304
+ id: this.borderFillIdCounter.next(),
2305
+ borders: {
2306
+ left: { ...line },
2307
+ right: { ...line },
2308
+ top: { ...line },
2309
+ bottom: { ...line }
2310
+ }
2311
+ };
2312
+ this.doc.head.borderFills.push(bf);
2313
+ return bf.id;
2314
+ }
2315
+ styleKey(style) {
2316
+ return JSON.stringify({
2317
+ fs: style.fontSize,
2318
+ b: style.bold,
2319
+ i: style.italic,
2320
+ c: style.color,
2321
+ u: style.underline,
2322
+ s: style.strikeout,
2323
+ f: style.fontName
2324
+ });
2325
+ }
2326
+ };
2327
+
2328
+ // src/utils/index.ts
2329
+ var utils_exports = {};
2330
+ __export(utils_exports, {
2331
+ IdCounter: () => IdCounter,
2332
+ charHeightToPt: () => charHeightToPt,
2333
+ colorrefToHex: () => colorrefToHex,
2334
+ colorrefToRgbString: () => colorrefToRgbString,
2335
+ detectFormat: () => detectFormat,
2336
+ hexToColorref: () => hexToColorref,
2337
+ hwpunitToMm: () => hwpunitToMm,
2338
+ hwpunitToPt: () => hwpunitToPt,
2339
+ hwpunitToPx: () => hwpunitToPx,
2340
+ mmToHwpunit: () => mmToHwpunit,
2341
+ ptToCharHeight: () => ptToCharHeight,
2342
+ ptToHwpunit: () => ptToHwpunit,
2343
+ pxToHwpunit: () => pxToHwpunit,
2344
+ rgbToColorref: () => rgbToColorref
2345
+ });
2346
+
2347
+ // src/index.ts
2348
+ function write(doc, opts) {
2349
+ return writeHwpx(doc, opts);
2350
+ }
2351
+ function read(data, opts) {
2352
+ const format = detectFormat(data);
2353
+ if (format === "hwp5") {
2354
+ return readHwp5(data);
2355
+ }
2356
+ if (format === "unknown") {
2357
+ throw new HWPXError("Unknown file format. Expected HWPX or HWP5 file.");
2358
+ }
2359
+ return readHwpx(data, opts);
2360
+ }
2361
+ export {
2362
+ HWPXBuilder,
2363
+ HWPXError,
2364
+ HWPXValidationError,
2365
+ createDefaultBorderFill,
2366
+ createDefaultCharProperty,
2367
+ createDefaultDocument,
2368
+ createDefaultFontFaces,
2369
+ createDefaultHead,
2370
+ createDefaultMeta,
2371
+ createDefaultParaProperty,
2372
+ createDefaultSectionDef,
2373
+ createDefaultStyles,
2374
+ read,
2375
+ utils_exports as utils,
2376
+ write
2377
+ };
2378
+ //# sourceMappingURL=index.js.map