hwpkit-dev 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/ .npmignore +4 -2
  2. package/README.md +39 -2
  3. package/dist/index.d.mts +41 -14
  4. package/dist/index.d.ts +41 -14
  5. package/dist/index.js +3553 -1159
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +3553 -1159
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +2 -1
  10. package/playground/index.html +346 -0
  11. package/playground/main.ts +302 -0
  12. package/playground/vite.config.ts +16 -0
  13. package/src/contract/decoder.ts +1 -0
  14. package/src/contract/encoder.ts +6 -1
  15. package/src/core/BaseDecoder.ts +118 -0
  16. package/src/core/BaseEncoder.ts +146 -0
  17. package/src/decoders/docx/DocxDecoder.ts +743 -151
  18. package/src/decoders/html/HtmlDecoder.ts +366 -0
  19. package/src/decoders/hwp/HwpScanner.ts +325 -157
  20. package/src/decoders/hwpx/HwpxDecoder.ts +785 -297
  21. package/src/decoders/md/MdDecoder.ts +4 -4
  22. package/src/encoders/docx/DocxEncoder.ts +504 -240
  23. package/src/encoders/html/HtmlEncoder.ts +17 -19
  24. package/src/encoders/hwp/HwpEncoder.ts +1466 -859
  25. package/src/encoders/hwpx/HwpxEncoder.ts +1477 -469
  26. package/src/encoders/hwpx/constants.ts +148 -0
  27. package/src/encoders/hwpx/utils.ts +198 -0
  28. package/src/encoders/md/MdEncoder.ts +20 -15
  29. package/src/model/builders.ts +4 -4
  30. package/src/model/doc-props.ts +19 -5
  31. package/src/model/doc-tree.ts +12 -4
  32. package/src/pipeline/Pipeline.ts +7 -3
  33. package/src/pipeline/registry.ts +13 -2
  34. package/src/safety/StyleBridge.ts +51 -6
  35. package/src/toolkit/ArchiveKit.ts +56 -0
  36. package/src/toolkit/StyleMapper.ts +221 -0
  37. package/src/toolkit/UnitConverter.ts +138 -0
  38. package/src/toolkit/XmlKit.ts +0 -5
  39. package/test-styling.ts +210 -0
  40. package/hwp-analyze.ts +0 -90
  41. package/inspect-doc.ts +0 -57
  42. package/output_test.hwp +0 -0
  43. package/test-docx-to-hwp.ts +0 -45
@@ -1,12 +1,12 @@
1
- import type { Encoder } from '../../contract/encoder';
2
1
  import type { DocRoot, ParaNode, SpanNode, GridNode, ContentNode, ImgNode, LinkNode } from '../../model/doc-tree';
3
2
  import type { Outcome } from '../../contract/result';
4
3
  import { succeed, fail } from '../../contract/result';
5
4
  import { TextKit } from '../../toolkit/TextKit';
6
5
  import { registry } from '../../pipeline/registry';
6
+ import { BaseEncoder } from '../../core/BaseEncoder';
7
7
 
8
- export class HtmlEncoder implements Encoder {
9
- readonly format = 'html';
8
+ export class HtmlEncoder extends BaseEncoder {
9
+ protected getFormat(): string { return 'html'; }
10
10
 
11
11
  async encode(doc: DocRoot): Promise<Outcome<Uint8Array>> {
12
12
  try {
@@ -15,8 +15,8 @@ export class HtmlEncoder implements Encoder {
15
15
 
16
16
  for (const sheet of doc.kids) {
17
17
  // Header/footer as comments
18
- if (sheet.header && sheet.header.length > 0) {
19
- const hText = sheet.header.map(p => encodePara(p, warns)).join('');
18
+ if (sheet.headers?.default && sheet.headers.default.length > 0) {
19
+ const hText = sheet.headers.default.map((p: ParaNode) => encodePara(p, warns)).join('');
20
20
  bodyParts.push(`<div class="hwp-header">${hText}</div>`);
21
21
  }
22
22
 
@@ -24,16 +24,16 @@ export class HtmlEncoder implements Encoder {
24
24
  bodyParts.push(encodeContent(kid, warns));
25
25
  }
26
26
 
27
- if (sheet.footer && sheet.footer.length > 0) {
28
- const fText = sheet.footer.map(p => encodePara(p, warns)).join('');
27
+ if (sheet.footers?.default && sheet.footers.default.length > 0) {
28
+ const fText = sheet.footers.default.map((p: ParaNode) => encodePara(p, warns)).join('');
29
29
  bodyParts.push(`<div class="hwp-footer">${fText}</div>`);
30
30
  }
31
31
  }
32
32
 
33
- const title = esc(doc.meta?.title ?? '');
33
+ const title = this.escapeXml(doc.meta?.title ?? '');
34
34
  const html = `<!DOCTYPE html>\n<html lang="ko">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>${title}</title>\n<style>\n${BASE_CSS}\n</style>\n</head>\n<body>\n<div class="hwp-doc">\n${bodyParts.join('\n')}\n</div>\n</body>\n</html>`;
35
35
 
36
- return succeed(TextKit.encode(html), warns);
36
+ return succeed(this.stringToBytes(html), warns);
37
37
  } catch (e: any) {
38
38
  return fail(`HTML encode error: ${e?.message ?? String(e)}`);
39
39
  }
@@ -45,7 +45,7 @@ body { margin: 0; padding: 0; background: #f0f0f0; }
45
45
  .hwp-doc { max-width: 800px; margin: 0 auto; background: #fff; padding: 40px 60px; box-shadow: 0 0 8px rgba(0,0,0,0.15); }
46
46
  .hwp-header, .hwp-footer { color: #666; font-size: 0.9em; border-bottom: 1px solid #ddd; margin-bottom: 8px; padding-bottom: 4px; }
47
47
  .hwp-footer { border-top: 1px solid #ddd; border-bottom: none; margin-top: 8px; padding-top: 4px; }
48
- p { margin: 0; padding: 0; line-height: 1.6; }
48
+ p { margin: 0; padding: 0; line-height: 1; }
49
49
  table { border-collapse: collapse; width: 100%; margin: 8px 0; }
50
50
  td, th { border: 1px solid #ccc; padding: 4px 8px; vertical-align: top; }
51
51
  img { max-width: 100%; height: auto; }
@@ -62,7 +62,7 @@ function encodePara(para: ParaNode, warns: string[]): string {
62
62
  if (k.tag === 'link') {
63
63
  const link = k as LinkNode;
64
64
  const inner = link.kids.map(s => encodeSpan(s, warns)).join('');
65
- return `<a href="${esc(link.href)}">${inner}</a>`;
65
+ return `<a href="${TextKit.escapeXml(link.href)}">${inner}</a>`;
66
66
  }
67
67
  return '';
68
68
  }).join('');
@@ -100,7 +100,9 @@ function encodeSpan(span: SpanNode, warns: string[]): string {
100
100
 
101
101
  for (const kid of span.kids) {
102
102
  if (kid.tag === 'txt') {
103
- parts.push(esc(kid.content));
103
+ // __EXT_N__ 또는 __EXT_N_W<w>_H<h>__ 자리표시자 제거
104
+ const content = kid.content.replace(/__EXT_\d+(?:_W\d+_H\d+)?__/g, '');
105
+ if (content) parts.push(TextKit.escapeXml(content));
104
106
  } else if (kid.tag === 'br') {
105
107
  parts.push('<br>');
106
108
  } else if (kid.tag === 'pb') {
@@ -119,7 +121,7 @@ function encodeSpan(span: SpanNode, warns: string[]): string {
119
121
 
120
122
  const p = span.props;
121
123
  const css: string[] = [];
122
- if (p.font) css.push(`font-family:${esc(p.font)}`);
124
+ if (p.font) css.push(`font-family:${TextKit.escapeXml(p.font)}`);
123
125
  if (p.pt) css.push(`font-size:${p.pt}pt`);
124
126
  if (p.color) css.push(`color:#${p.color}`);
125
127
  if (p.bg) css.push(`background-color:#${p.bg}`);
@@ -140,7 +142,7 @@ function encodeSpan(span: SpanNode, warns: string[]): string {
140
142
  function encodeImage(img: ImgNode): string {
141
143
  const wStyle = img.w ? ` width="${Math.round(img.w / 72 * 96)}px"` : '';
142
144
  const hStyle = img.h ? ` height="${Math.round(img.h / 72 * 96)}px"` : '';
143
- const alt = esc(img.alt ?? '');
145
+ const alt = TextKit.escapeXml(img.alt ?? '');
144
146
  return `<img src="data:${img.mime};base64,${img.b64}" alt="${alt}"${wStyle}${hStyle}>`;
145
147
  }
146
148
 
@@ -188,7 +190,7 @@ function encodeGrid(grid: GridNode, warns: string[]): string {
188
190
  else if (va === 'bot') styleAttrs.push('vertical-align:bottom');
189
191
  const styleAttr = styleAttrs.length > 0 ? ` style="${styleAttrs.join(';')}"` : '';
190
192
 
191
- const content = cell.kids.map(p => encodePara(p, warns)).join('');
193
+ const content = cell.kids.map(p => p.tag === 'para' ? encodePara(p, warns) : encodeGrid(p, warns)).join('');
192
194
  cells += `<${tag}${cs}${rs}${styleAttr}>${content}</${tag}>`;
193
195
  ci += cell.cs;
194
196
  }
@@ -198,8 +200,4 @@ function encodeGrid(grid: GridNode, warns: string[]): string {
198
200
  return `<table>\n<tbody>\n${rows}</tbody>\n</table>\n`;
199
201
  }
200
202
 
201
- function esc(s: string): string {
202
- return TextKit.escapeXml(s);
203
- }
204
-
205
203
  registry.registerEncoder(new HtmlEncoder());