hwpx-js 0.1.0 → 0.1.2

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.d.cts CHANGED
@@ -184,6 +184,7 @@ interface Paragraph {
184
184
  paraPrIDRef: number;
185
185
  styleIDRef: number;
186
186
  runs: Run[];
187
+ pageBreak?: boolean;
187
188
  }
188
189
  type Run = TextRun | TableRun | PictureRun | DrawingRun | BreakRun;
189
190
  interface TextRun {
@@ -279,6 +280,13 @@ interface ReadOptions {
279
280
  partial?: boolean;
280
281
  }
281
282
 
283
+ interface PdfOptions {
284
+ /** 한글 폰트명 (기본: 'Malgun Gothic'). PDF 뷰어에 설치된 폰트 */
285
+ fontName?: string;
286
+ /** Latin 폰트명 (기본: 'Helvetica') */
287
+ latinFontName?: string;
288
+ }
289
+
282
290
  interface TextStyleOptions {
283
291
  fontSize?: number;
284
292
  bold?: boolean;
@@ -440,5 +448,9 @@ declare function write(doc: HWPXDocument, opts?: WriteOptions): Uint8Array;
440
448
  * 포맷 자동 감지 (HWPX ZIP / HWP5 OLE)
441
449
  */
442
450
  declare function read(data: Uint8Array, opts?: ReadOptions): HWPXDocument;
451
+ /**
452
+ * HWPXDocument를 PDF 바이너리(Uint8Array)로 변환
453
+ */
454
+ declare function writePdf(doc: HWPXDocument, opts?: PdfOptions): Uint8Array;
443
455
 
444
- export { type BinDataItem, type BorderFill, type BorderLine, type BreakRun, type BulletProperty, type CellMerge, type CellPadding, type CharProperty, type ColumnDef, type ColumnSettings, type DocumentHead, type DocumentMeta, type DrawingObject, type DrawingRun, type FillBrush, type FontFace, HWPXBuilder, type HWPXDocument, HWPXError, HWPXValidationError, type HeaderFooterDef, type HeaderFooterOptions, type ImageOptions, type NumberingLevel, type NumberingProperty, type PageMargin, type PageSettings, type ParaProperty, type Paragraph, type Picture, type PictureRun, type ReadOptions, type Run, type Section, type SectionDef, type Style, type StyledSegment, type SubDocument, type Table, type TableCell, type TableOptions, type TableRow, type TableRun, type TextRun, type TextStyleOptions, type WriteOptions, createDefaultBorderFill, createDefaultCharProperty, createDefaultDocument, createDefaultFontFaces, createDefaultHead, createDefaultMeta, createDefaultParaProperty, createDefaultSectionDef, createDefaultStyles, read, index as utils, write };
456
+ export { type BinDataItem, type BorderFill, type BorderLine, type BreakRun, type BulletProperty, type CellMerge, type CellPadding, type CharProperty, type ColumnDef, type ColumnSettings, type DocumentHead, type DocumentMeta, type DrawingObject, type DrawingRun, type FillBrush, type FontFace, HWPXBuilder, type HWPXDocument, HWPXError, HWPXValidationError, type HeaderFooterDef, type HeaderFooterOptions, type ImageOptions, type NumberingLevel, type NumberingProperty, type PageMargin, type PageSettings, type ParaProperty, type Paragraph, type PdfOptions, type Picture, type PictureRun, type ReadOptions, type Run, type Section, type SectionDef, type Style, type StyledSegment, type SubDocument, type Table, type TableCell, type TableOptions, type TableRow, type TableRun, type TextRun, type TextStyleOptions, type WriteOptions, createDefaultBorderFill, createDefaultCharProperty, createDefaultDocument, createDefaultFontFaces, createDefaultHead, createDefaultMeta, createDefaultParaProperty, createDefaultSectionDef, createDefaultStyles, read, index as utils, write, writePdf };
package/dist/index.d.ts CHANGED
@@ -184,6 +184,7 @@ interface Paragraph {
184
184
  paraPrIDRef: number;
185
185
  styleIDRef: number;
186
186
  runs: Run[];
187
+ pageBreak?: boolean;
187
188
  }
188
189
  type Run = TextRun | TableRun | PictureRun | DrawingRun | BreakRun;
189
190
  interface TextRun {
@@ -279,6 +280,13 @@ interface ReadOptions {
279
280
  partial?: boolean;
280
281
  }
281
282
 
283
+ interface PdfOptions {
284
+ /** 한글 폰트명 (기본: 'Malgun Gothic'). PDF 뷰어에 설치된 폰트 */
285
+ fontName?: string;
286
+ /** Latin 폰트명 (기본: 'Helvetica') */
287
+ latinFontName?: string;
288
+ }
289
+
282
290
  interface TextStyleOptions {
283
291
  fontSize?: number;
284
292
  bold?: boolean;
@@ -440,5 +448,9 @@ declare function write(doc: HWPXDocument, opts?: WriteOptions): Uint8Array;
440
448
  * 포맷 자동 감지 (HWPX ZIP / HWP5 OLE)
441
449
  */
442
450
  declare function read(data: Uint8Array, opts?: ReadOptions): HWPXDocument;
451
+ /**
452
+ * HWPXDocument를 PDF 바이너리(Uint8Array)로 변환
453
+ */
454
+ declare function writePdf(doc: HWPXDocument, opts?: PdfOptions): Uint8Array;
443
455
 
444
- export { type BinDataItem, type BorderFill, type BorderLine, type BreakRun, type BulletProperty, type CellMerge, type CellPadding, type CharProperty, type ColumnDef, type ColumnSettings, type DocumentHead, type DocumentMeta, type DrawingObject, type DrawingRun, type FillBrush, type FontFace, HWPXBuilder, type HWPXDocument, HWPXError, HWPXValidationError, type HeaderFooterDef, type HeaderFooterOptions, type ImageOptions, type NumberingLevel, type NumberingProperty, type PageMargin, type PageSettings, type ParaProperty, type Paragraph, type Picture, type PictureRun, type ReadOptions, type Run, type Section, type SectionDef, type Style, type StyledSegment, type SubDocument, type Table, type TableCell, type TableOptions, type TableRow, type TableRun, type TextRun, type TextStyleOptions, type WriteOptions, createDefaultBorderFill, createDefaultCharProperty, createDefaultDocument, createDefaultFontFaces, createDefaultHead, createDefaultMeta, createDefaultParaProperty, createDefaultSectionDef, createDefaultStyles, read, index as utils, write };
456
+ export { type BinDataItem, type BorderFill, type BorderLine, type BreakRun, type BulletProperty, type CellMerge, type CellPadding, type CharProperty, type ColumnDef, type ColumnSettings, type DocumentHead, type DocumentMeta, type DrawingObject, type DrawingRun, type FillBrush, type FontFace, HWPXBuilder, type HWPXDocument, HWPXError, HWPXValidationError, type HeaderFooterDef, type HeaderFooterOptions, type ImageOptions, type NumberingLevel, type NumberingProperty, type PageMargin, type PageSettings, type ParaProperty, type Paragraph, type PdfOptions, type Picture, type PictureRun, type ReadOptions, type Run, type Section, type SectionDef, type Style, type StyledSegment, type SubDocument, type Table, type TableCell, type TableOptions, type TableRow, type TableRun, type TextRun, type TextStyleOptions, type WriteOptions, createDefaultBorderFill, createDefaultCharProperty, createDefaultDocument, createDefaultFontFaces, createDefaultHead, createDefaultMeta, createDefaultParaProperty, createDefaultSectionDef, createDefaultStyles, read, index as utils, write, writePdf };
package/dist/index.js CHANGED
@@ -779,14 +779,15 @@ var XmlParser = class {
779
779
  // src/codecs/hwpx/xml/parse-header.ts
780
780
  function parseHeaderXml(xml) {
781
781
  const root = parseXml(xml);
782
+ const refList = findChild(root, "hh:refList") || root;
782
783
  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),
784
+ fontFaces: parseFontFaces(refList),
785
+ borderFills: parseBorderFills(refList),
786
+ charProperties: parseCharProperties(refList),
787
+ paraProperties: parseParaProperties(refList),
788
+ styles: parseStyles(refList),
789
+ bulletProperties: parseBullets(refList),
790
+ numberingProperties: parseNumberings(refList),
790
791
  compatibleDoc: parseCompatibleDoc(root)
791
792
  };
792
793
  }
@@ -860,15 +861,21 @@ function parseCharProperties(root) {
860
861
  if (!propsNode) return props;
861
862
  for (const cpNode of findChildren(propsNode, "hh:charPr")) {
862
863
  const fontRefNode = findChild(cpNode, "hh:fontRef");
864
+ const hasBoldElement = !!findChild(cpNode, "hh:bold");
865
+ const hasItalicElement = !!findChild(cpNode, "hh:italic");
866
+ const underlineNode = findChild(cpNode, "hh:underline");
867
+ const strikeoutNode = findChild(cpNode, "hh:strikeout");
868
+ const underline = underlineNode ? attrStr(underlineNode, "type", "NONE") : attrStr(cpNode, "underline", "NONE");
869
+ const strikeout = strikeoutNode ? attrStr(strikeoutNode, "shape", "NONE") === "NONE" ? "NONE" : "CONTINUOUS" : attrStr(cpNode, "strikeout", "NONE");
863
870
  props.push({
864
871
  id: attrInt(cpNode, "id"),
865
872
  height: attrInt(cpNode, "height", 1e3),
866
873
  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"),
874
+ shadeColor: cpNode.attrs["shadeColor"] && cpNode.attrs["shadeColor"] !== "none" ? parseColor(attrStr(cpNode, "shadeColor")) : void 0,
875
+ bold: hasBoldElement || attrBool(cpNode, "bold"),
876
+ italic: hasItalicElement || attrBool(cpNode, "italic"),
877
+ underline,
878
+ strikeout,
872
879
  useFontSpace: attrBool(cpNode, "useFontSpace"),
873
880
  useKerning: attrBool(cpNode, "useKerning"),
874
881
  spacing: cpNode.attrs["spacing"] !== void 0 ? attrInt(cpNode, "spacing") : void 0,
@@ -892,24 +899,54 @@ function parseParaProperties(root) {
892
899
  const propsNode = findChild(root, "hh:paraProperties");
893
900
  if (!propsNode) return props;
894
901
  for (const ppNode of findChildren(propsNode, "hh:paraPr")) {
895
- const marginNode = findChild(ppNode, "hh:parMargin");
896
- const lineSpacingNode = findChild(ppNode, "hh:lineSpacing");
902
+ const alignNode = findChild(ppNode, "hh:align");
903
+ const alignment = alignNode ? attrStr(alignNode, "horizontal", "JUSTIFY") : attrStr(ppNode, "align", "JUSTIFY");
904
+ let marginNode = findChild(ppNode, "hh:parMargin") || findChild(ppNode, "hh:margin");
905
+ let lineSpacingNode = findChild(ppNode, "hh:lineSpacing");
906
+ const switchNode = findChild(ppNode, "hp:switch");
907
+ if (switchNode) {
908
+ const caseNode = findChild(switchNode, "hp:case");
909
+ if (caseNode) {
910
+ if (!marginNode) marginNode = findChild(caseNode, "hh:margin");
911
+ if (!lineSpacingNode) lineSpacingNode = findChild(caseNode, "hh:lineSpacing");
912
+ }
913
+ }
914
+ let paraMargin = { left: 0, right: 0, indent: 0, prevSpacing: 0, nextSpacing: 0 };
915
+ if (marginNode) {
916
+ const leftChild = findChild(marginNode, "hc:left");
917
+ if (leftChild) {
918
+ const intentChild = findChild(marginNode, "hc:intent");
919
+ const rightChild = findChild(marginNode, "hc:right");
920
+ const prevChild = findChild(marginNode, "hc:prev");
921
+ const nextChild = findChild(marginNode, "hc:next");
922
+ paraMargin = {
923
+ left: leftChild ? attrInt(leftChild, "value") : 0,
924
+ right: rightChild ? attrInt(rightChild, "value") : 0,
925
+ indent: intentChild ? attrInt(intentChild, "value") : 0,
926
+ prevSpacing: prevChild ? attrInt(prevChild, "value") : 0,
927
+ nextSpacing: nextChild ? attrInt(nextChild, "value") : 0
928
+ };
929
+ } else {
930
+ paraMargin = {
931
+ left: attrInt(marginNode, "left"),
932
+ right: attrInt(marginNode, "right"),
933
+ indent: attrInt(marginNode, "indent"),
934
+ prevSpacing: attrInt(marginNode, "prev"),
935
+ nextSpacing: attrInt(marginNode, "next")
936
+ };
937
+ }
938
+ }
939
+ const headingNode = findChild(ppNode, "hh:heading");
897
940
  props.push({
898
941
  id: attrInt(ppNode, "id"),
899
- alignment: attrStr(ppNode, "align", "JUSTIFY"),
900
- heading: ppNode.attrs["heading"] ? ppNode.attrs["heading"] : void 0,
942
+ alignment,
943
+ heading: headingNode ? attrStr(headingNode, "type", "NONE") : ppNode.attrs["heading"] ? ppNode.attrs["heading"] : void 0,
901
944
  breakBefore: ppNode.attrs["breakBefore"] ? ppNode.attrs["breakBefore"] : void 0,
902
945
  lineSpacing: lineSpacingNode ? {
903
946
  type: attrStr(lineSpacingNode, "type", "PERCENT"),
904
947
  value: attrInt(lineSpacingNode, "value", 160)
905
948
  } : { 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 }
949
+ paraMargin
913
950
  });
914
951
  }
915
952
  return props;
@@ -981,12 +1018,24 @@ function parseSectionXml(xml) {
981
1018
  };
982
1019
  }
983
1020
  function parseSectionDef(root) {
984
- const secPr = findChild(root, "hs:secPr");
1021
+ let secPr = findChild(root, "hs:secPr");
1022
+ if (!secPr) {
1023
+ for (const p of findChildren(root, "hp:p")) {
1024
+ for (const run of findChildren(p, "hp:run")) {
1025
+ const found = findChild(run, "hp:secPr");
1026
+ if (found) {
1027
+ secPr = found;
1028
+ break;
1029
+ }
1030
+ }
1031
+ if (secPr) break;
1032
+ }
1033
+ }
985
1034
  if (!secPr) {
986
1035
  return defaultSectionDef();
987
1036
  }
988
- const pagePr = findChild(secPr, "hs:pagePr");
989
- const pageMarginNode = findChild(secPr, "hs:pageMargin");
1037
+ const pagePr = findChild(secPr, "hp:pagePr") || findChild(secPr, "hs:pagePr");
1038
+ const pageMarginNode = findChild(secPr, "hs:pageMargin") || (pagePr ? findChild(pagePr, "hp:margin") || findChild(pagePr, "hs:margin") : void 0);
990
1039
  const colPr = findChild(secPr, "hs:colPr");
991
1040
  let columns;
992
1041
  if (colPr && attrInt(colPr, "count", 1) > 1) {
@@ -1012,7 +1061,7 @@ function parseSectionDef(root) {
1012
1061
  return {
1013
1062
  pageWidth: pagePr ? attrInt(pagePr, "width", 59528) : 59528,
1014
1063
  pageHeight: pagePr ? attrInt(pagePr, "height", 84188) : 84188,
1015
- landscape: pagePr ? attrStr(pagePr, "landscape") === "LANDSCAPE" : false,
1064
+ landscape: pagePr ? attrStr(pagePr, "landscape") === "LANDSCAPE" || attrStr(pagePr, "landscape") === "WIDELY" : false,
1016
1065
  gutterType: pagePr ? attrStr(pagePr, "gutterType", "LEFT_ONLY") : "LEFT_ONLY",
1017
1066
  pageMargin: pageMarginNode ? parsePageMargin(pageMarginNode) : defaultPageMargin(),
1018
1067
  columns,
@@ -1042,11 +1091,11 @@ function parseParagraph(pNode) {
1042
1091
  for (const runNode of findChildren(pNode, "hp:run")) {
1043
1092
  const charPrIDRef = attrInt(runNode, "charPrIDRef");
1044
1093
  const lineBreak = findChild(runNode, "hp:lineBreak");
1045
- const pageBreak = findChild(runNode, "hp:pageBreak");
1094
+ const pageBreak2 = findChild(runNode, "hp:pageBreak");
1046
1095
  const colBreak = findChild(runNode, "hp:colBreak");
1047
1096
  if (lineBreak) {
1048
1097
  runs.push({ t: "break", breakType: "LINE", charPrIDRef });
1049
- } else if (pageBreak) {
1098
+ } else if (pageBreak2) {
1050
1099
  runs.push({ t: "break", breakType: "PAGE", charPrIDRef });
1051
1100
  } else if (colBreak) {
1052
1101
  runs.push({ t: "break", breakType: "COLUMN", charPrIDRef });
@@ -1061,29 +1110,36 @@ function parseParagraph(pNode) {
1061
1110
  runs.push({ t: "picture", picture: parsePicture(picNode), charPrIDRef });
1062
1111
  continue;
1063
1112
  }
1064
- const runTextNode = findChild(runNode, "hp:runText");
1113
+ const runTextNode = findChild(runNode, "hp:runText") || findChild(runNode, "hp:t");
1065
1114
  const text = runTextNode ? getTextContent(runTextNode) : "";
1066
1115
  runs.push({ t: "text", text, charPrIDRef });
1067
1116
  }
1068
1117
  }
1118
+ const pageBreakVal = attrStr(pNode, "pageBreak", "0");
1119
+ const pageBreak = pageBreakVal === "1" || pageBreakVal === "true";
1069
1120
  return {
1070
1121
  paraPrIDRef: attrInt(pNode, "paraPrIDRef"),
1071
1122
  styleIDRef: attrInt(pNode, "styleIDRef"),
1072
- runs
1123
+ runs,
1124
+ ...pageBreak ? { pageBreak: true } : {}
1073
1125
  };
1074
1126
  }
1075
1127
  function parseTable(tblNode) {
1076
1128
  const rowCount = attrInt(tblNode, "rowCnt", 1);
1077
1129
  const colCount = attrInt(tblNode, "colCnt", 1);
1078
- const width = attrInt(tblNode, "width", 0);
1079
1130
  const borderFillIDRef = attrInt(tblNode, "borderFillIDRef", 1);
1080
1131
  const cellSpacing = attrInt(tblNode, "cellSpacing", 0);
1132
+ const szNode = findChild(tblNode, "hp:sz");
1133
+ const width = szNode ? attrInt(szNode, "width", 0) : attrInt(tblNode, "width", 0);
1081
1134
  const colSzNodes = findChildren(tblNode, "hp:colSz");
1082
- const colWidths = colSzNodes.map((n) => attrInt(n, "width", 0));
1135
+ let colWidths = colSzNodes.map((n) => attrInt(n, "width", 0));
1083
1136
  const rows = [];
1084
1137
  for (const trNode of findChildren(tblNode, "hp:tr")) {
1085
1138
  rows.push(parseTableRow(trNode));
1086
1139
  }
1140
+ if (colWidths.length === 0 && rows.length > 0) {
1141
+ colWidths = rows[0].cells.map((c) => c.width);
1142
+ }
1087
1143
  return {
1088
1144
  rowCount,
1089
1145
  colCount,
@@ -1095,21 +1151,31 @@ function parseTable(tblNode) {
1095
1151
  };
1096
1152
  }
1097
1153
  function parseTableRow(trNode) {
1098
- const height = attrInt(trNode, "height", 0);
1099
1154
  const cells = [];
1100
1155
  for (const tcNode of findChildren(trNode, "hp:tc")) {
1101
1156
  cells.push(parseTableCell(tcNode));
1102
1157
  }
1158
+ const height = attrInt(trNode, "height", 0) || (cells.length > 0 ? cells[0].height : 0);
1103
1159
  return { height, cells };
1104
1160
  }
1105
1161
  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);
1162
+ const cellSpanNode = findChild(tcNode, "hp:cellSpan");
1163
+ const colSpan = cellSpanNode ? attrInt(cellSpanNode, "colSpan", 1) : attrInt(tcNode, "colSpan", 1);
1164
+ const rowSpan = cellSpanNode ? attrInt(cellSpanNode, "rowSpan", 1) : attrInt(tcNode, "rowSpan", 1);
1165
+ const cellSzNode = findChild(tcNode, "hp:cellSz");
1166
+ const width = cellSzNode ? attrInt(cellSzNode, "width", 0) : attrInt(tcNode, "width", 0);
1167
+ const height = cellSzNode ? attrInt(cellSzNode, "height", 0) : attrInt(tcNode, "height", 0);
1110
1168
  const borderFillIDRef = attrInt(tcNode, "borderFillIDRef", 1);
1111
1169
  let padding;
1112
- if (tcNode.attrs["paddingLeft"] !== void 0) {
1170
+ const cellMarginNode = findChild(tcNode, "hp:cellMargin");
1171
+ if (cellMarginNode) {
1172
+ padding = {
1173
+ left: attrInt(cellMarginNode, "left"),
1174
+ right: attrInt(cellMarginNode, "right"),
1175
+ top: attrInt(cellMarginNode, "top"),
1176
+ bottom: attrInt(cellMarginNode, "bottom")
1177
+ };
1178
+ } else if (tcNode.attrs["paddingLeft"] !== void 0) {
1113
1179
  padding = {
1114
1180
  left: attrInt(tcNode, "paddingLeft"),
1115
1181
  right: attrInt(tcNode, "paddingRight"),
@@ -2026,6 +2092,303 @@ function detectFormat(data) {
2026
2092
  return "unknown";
2027
2093
  }
2028
2094
 
2095
+ // src/codecs/pdf/writer.ts
2096
+ var PdfBuilder = class {
2097
+ constructor() {
2098
+ this.objects = [];
2099
+ this.offsets = [];
2100
+ this.output = "";
2101
+ }
2102
+ nextId() {
2103
+ return this.objects.length + 1;
2104
+ }
2105
+ addObject(content) {
2106
+ const id = this.nextId();
2107
+ this.objects.push(content);
2108
+ return id;
2109
+ }
2110
+ build() {
2111
+ this.output = "%PDF-1.4\n%\xC0\xC1\xC2\xC3\n";
2112
+ for (let i = 0; i < this.objects.length; i++) {
2113
+ this.offsets.push(this.output.length);
2114
+ this.output += `${i + 1} 0 obj
2115
+ ${this.objects[i]}
2116
+ endobj
2117
+ `;
2118
+ }
2119
+ const xrefOffset = this.output.length;
2120
+ this.output += "xref\n";
2121
+ this.output += `0 ${this.objects.length + 1}
2122
+ `;
2123
+ this.output += "0000000000 65535 f \n";
2124
+ for (const off of this.offsets) {
2125
+ this.output += off.toString().padStart(10, "0") + " 00000 n \n";
2126
+ }
2127
+ this.output += "trailer\n";
2128
+ this.output += `<< /Size ${this.objects.length + 1} /Root 1 0 R >>
2129
+ `;
2130
+ this.output += "startxref\n";
2131
+ this.output += `${xrefOffset}
2132
+ `;
2133
+ this.output += "%%EOF\n";
2134
+ return stringToBytes(this.output);
2135
+ }
2136
+ };
2137
+ function writePdf(doc, opts) {
2138
+ const korFont = opts?.fontName || "Malgun Gothic";
2139
+ const latFont = opts?.latinFontName || "Helvetica";
2140
+ const pdf = new PdfBuilder();
2141
+ const catalogId = pdf.addObject("");
2142
+ const pagesId = pdf.addObject("");
2143
+ const cidFontId = pdf.addObject(
2144
+ `<< /Type /Font /Subtype /CIDFontType2 /BaseFont /${sanitizeName(korFont)} /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /DW 1000 >>`
2145
+ );
2146
+ const korFontId = pdf.addObject(
2147
+ `<< /Type /Font /Subtype /Type0 /BaseFont /${sanitizeName(korFont)} /Encoding /Identity-H /DescendantFonts [${cidFontId} 0 R] /ToUnicode ${pdf.nextId() + 1} 0 R >>`
2148
+ );
2149
+ const cmapStream = buildToUnicodeCMap();
2150
+ const cmapId = pdf.addObject(
2151
+ `<< /Length ${cmapStream.length} >>
2152
+ stream
2153
+ ${cmapStream}
2154
+ endstream`
2155
+ );
2156
+ pdf.objects[korFontId - 1] = `<< /Type /Font /Subtype /Type0 /BaseFont /${sanitizeName(korFont)} /Encoding /Identity-H /DescendantFonts [${cidFontId} 0 R] /ToUnicode ${cmapId} 0 R >>`;
2157
+ const latFontId = pdf.addObject(
2158
+ `<< /Type /Font /Subtype /Type1 /BaseFont /${sanitizeName(latFont)} /Encoding /WinAnsiEncoding >>`
2159
+ );
2160
+ const fontResources = `/F1 ${korFontId} 0 R /F2 ${latFontId} 0 R`;
2161
+ const pageIds = [];
2162
+ for (const section of doc.sections) {
2163
+ const pageW = section.def.pageWidth / 7200 * 72;
2164
+ const pageH = section.def.pageHeight / 7200 * 72;
2165
+ const ml = section.def.pageMargin.left / 7200 * 72;
2166
+ const mr = section.def.pageMargin.right / 7200 * 72;
2167
+ const mt = section.def.pageMargin.top / 7200 * 72;
2168
+ const mb = section.def.pageMargin.bottom / 7200 * 72;
2169
+ const bodyW = pageW - ml - mr;
2170
+ const bodyH = pageH - mt - mb;
2171
+ const ctx = {
2172
+ doc,
2173
+ pdf,
2174
+ pagesId,
2175
+ fontResources,
2176
+ pageW,
2177
+ pageH,
2178
+ ml,
2179
+ mr,
2180
+ mt,
2181
+ mb,
2182
+ bodyW,
2183
+ bodyH,
2184
+ pageIds
2185
+ };
2186
+ renderSection(ctx, section);
2187
+ }
2188
+ pdf.objects[0] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`;
2189
+ const kids = pageIds.map((id) => `${id} 0 R`).join(" ");
2190
+ pdf.objects[1] = `<< /Type /Pages /Kids [${kids}] /Count ${pageIds.length} >>`;
2191
+ return pdf.build();
2192
+ }
2193
+ function renderSection(ctx, section) {
2194
+ let page = newPage(ctx);
2195
+ for (const para of section.paragraphs) {
2196
+ for (const run of para.runs) {
2197
+ if (run.t === "text") {
2198
+ const fontSize = getFontSize(ctx.doc, run.charPrIDRef);
2199
+ const lineHeight = fontSize * 1.6;
2200
+ if (page.y - lineHeight < 0) {
2201
+ flushPage(ctx, page);
2202
+ page = newPage(ctx);
2203
+ }
2204
+ if (run.text) {
2205
+ const color = getTextColor(ctx.doc, run.charPrIDRef);
2206
+ const bold = isBold(ctx.doc, run.charPrIDRef);
2207
+ if (color !== "0 0 0") {
2208
+ page.ops.push(color + " rg");
2209
+ }
2210
+ const lines = wrapText(run.text, fontSize, ctx.bodyW);
2211
+ for (const line of lines) {
2212
+ if (page.y - lineHeight < 0) {
2213
+ flushPage(ctx, page);
2214
+ page = newPage(ctx);
2215
+ }
2216
+ page.ops.push("BT");
2217
+ page.ops.push(`/F1 ${fontSize} Tf`);
2218
+ page.ops.push(`${ctx.ml} ${ctx.mt + page.y - lineHeight} Td`);
2219
+ page.ops.push(`<${toUtf16Hex(line)}> Tj`);
2220
+ page.ops.push("ET");
2221
+ page.y -= lineHeight;
2222
+ }
2223
+ if (color !== "0 0 0") {
2224
+ page.ops.push("0 0 0 rg");
2225
+ }
2226
+ } else {
2227
+ page.y -= fontSize * 1.6;
2228
+ }
2229
+ } else if (run.t === "table") {
2230
+ const tableHeight = estimateTableHeight(run.table, ctx.doc);
2231
+ if (page.y - tableHeight < 0 && page.y < ctx.bodyH - 10) {
2232
+ flushPage(ctx, page);
2233
+ page = newPage(ctx);
2234
+ }
2235
+ renderTable(ctx, page, run.table);
2236
+ }
2237
+ }
2238
+ }
2239
+ flushPage(ctx, page);
2240
+ }
2241
+ function newPage(ctx) {
2242
+ return { ops: [], y: ctx.bodyH };
2243
+ }
2244
+ function flushPage(ctx, page) {
2245
+ if (page.ops.length === 0) return;
2246
+ const stream = page.ops.join("\n");
2247
+ const streamId = ctx.pdf.addObject(
2248
+ `<< /Length ${byteLength(stream)} >>
2249
+ stream
2250
+ ${stream}
2251
+ endstream`
2252
+ );
2253
+ const pageId = ctx.pdf.addObject(
2254
+ `<< /Type /Page /Parent ${ctx.pagesId} 0 R /MediaBox [0 0 ${fmt(ctx.pageW)} ${fmt(ctx.pageH)}] /Resources << /Font << ${ctx.fontResources} >> >> /Contents ${streamId} 0 R >>`
2255
+ );
2256
+ ctx.pageIds.push(pageId);
2257
+ }
2258
+ function renderTable(ctx, page, table) {
2259
+ const tableW = table.width / 7200 * 72;
2260
+ const scale = Math.min(1, ctx.bodyW / tableW);
2261
+ const startX = ctx.ml;
2262
+ const startY = ctx.mt + page.y;
2263
+ for (const row of table.rows) {
2264
+ const rowH = row.height / 7200 * 72 * scale;
2265
+ let cellX = startX;
2266
+ for (const cell of row.cells) {
2267
+ const cellW = cell.width / 7200 * 72 * scale;
2268
+ const cellH = cell.height / 7200 * 72 * scale;
2269
+ const cellY = startY - rowH;
2270
+ page.ops.push("0.5 w");
2271
+ page.ops.push(`${fmt(cellX)} ${fmt(cellY)} ${fmt(cellW)} ${fmt(cellH)} re S`);
2272
+ const text = cell.paragraphs.flatMap((p) => p.runs.filter((r) => r.t === "text").map((r) => r.text)).join(" ");
2273
+ if (text) {
2274
+ const fontSize = 8 * scale;
2275
+ const textX = cellX + 3;
2276
+ const textY = cellY + cellH - fontSize - 2;
2277
+ page.ops.push("BT");
2278
+ page.ops.push(`/F1 ${fmt(fontSize)} Tf`);
2279
+ page.ops.push(`${fmt(textX)} ${fmt(textY)} Td`);
2280
+ page.ops.push(`<${toUtf16Hex(text.substring(0, 100))}> Tj`);
2281
+ page.ops.push("ET");
2282
+ }
2283
+ cellX += cellW;
2284
+ }
2285
+ page.y -= rowH;
2286
+ startY === ctx.mt + page.y + rowH;
2287
+ }
2288
+ }
2289
+ function estimateTableHeight(table, doc) {
2290
+ let h = 0;
2291
+ for (const row of table.rows) {
2292
+ h += row.height / 7200 * 72;
2293
+ }
2294
+ return h;
2295
+ }
2296
+ function getFontSize(doc, charPrIDRef) {
2297
+ const cp = doc.head.charProperties.find((c) => c.id === charPrIDRef);
2298
+ if (!cp) return 10;
2299
+ return cp.height / 100;
2300
+ }
2301
+ function getTextColor(doc, charPrIDRef) {
2302
+ const cp = doc.head.charProperties.find((c) => c.id === charPrIDRef);
2303
+ if (!cp || cp.textColor === 0) return "0 0 0";
2304
+ const r = (cp.textColor & 255) / 255;
2305
+ const g = (cp.textColor >> 8 & 255) / 255;
2306
+ const b = (cp.textColor >> 16 & 255) / 255;
2307
+ return `${fmt(r)} ${fmt(g)} ${fmt(b)}`;
2308
+ }
2309
+ function isBold(doc, charPrIDRef) {
2310
+ const cp = doc.head.charProperties.find((c) => c.id === charPrIDRef);
2311
+ return cp?.bold || false;
2312
+ }
2313
+ function wrapText(text, fontSize, maxWidth) {
2314
+ const lines = [];
2315
+ let current = "";
2316
+ let currentWidth = 0;
2317
+ for (const ch of text) {
2318
+ const w = ch.charCodeAt(0) > 127 ? fontSize : fontSize * 0.5;
2319
+ if (currentWidth + w > maxWidth && current) {
2320
+ lines.push(current);
2321
+ current = "";
2322
+ currentWidth = 0;
2323
+ }
2324
+ current += ch;
2325
+ currentWidth += w;
2326
+ }
2327
+ if (current) lines.push(current);
2328
+ return lines.length > 0 ? lines : [""];
2329
+ }
2330
+ function buildToUnicodeCMap() {
2331
+ return [
2332
+ "/CIDInit /ProcSet findresource begin",
2333
+ "12 dict begin",
2334
+ "begincmap",
2335
+ "/CIDSystemInfo",
2336
+ "<< /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def",
2337
+ "/CMapName /Adobe-Identity-UCS def",
2338
+ "/CMapType 2 def",
2339
+ "1 begincodespacerange",
2340
+ "<0000> <FFFF>",
2341
+ "endcodespacerange",
2342
+ "1 beginbfrange",
2343
+ "<0000> <FFFF> <0000>",
2344
+ "endbfrange",
2345
+ "endcmap",
2346
+ "CMapName currentdict /CMap defineresource pop",
2347
+ "end",
2348
+ "end"
2349
+ ].join("\n");
2350
+ }
2351
+ function toUtf16Hex(str) {
2352
+ let hex = "";
2353
+ for (let i = 0; i < str.length; i++) {
2354
+ const code = str.charCodeAt(i);
2355
+ hex += code.toString(16).padStart(4, "0");
2356
+ }
2357
+ return hex.toUpperCase();
2358
+ }
2359
+ function sanitizeName(name) {
2360
+ return name.replace(/[^a-zA-Z0-9]/g, "");
2361
+ }
2362
+ function fmt(n) {
2363
+ return Number(n.toFixed(2)).toString();
2364
+ }
2365
+ function byteLength(str) {
2366
+ let len = 0;
2367
+ for (let i = 0; i < str.length; i++) {
2368
+ const code = str.charCodeAt(i);
2369
+ if (code <= 127) len++;
2370
+ else if (code <= 2047) len += 2;
2371
+ else len += 3;
2372
+ }
2373
+ return len;
2374
+ }
2375
+ function stringToBytes(str) {
2376
+ const bytes = [];
2377
+ for (let i = 0; i < str.length; i++) {
2378
+ const code = str.charCodeAt(i);
2379
+ if (code <= 255) {
2380
+ bytes.push(code);
2381
+ } else {
2382
+ if (code <= 2047) {
2383
+ bytes.push(192 | code >> 6, 128 | code & 63);
2384
+ } else {
2385
+ bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
2386
+ }
2387
+ }
2388
+ }
2389
+ return new Uint8Array(bytes);
2390
+ }
2391
+
2029
2392
  // src/utils/id.ts
2030
2393
  var IdCounter = class {
2031
2394
  constructor(start = 0) {
@@ -2358,6 +2721,9 @@ function read(data, opts) {
2358
2721
  }
2359
2722
  return readHwpx(data, opts);
2360
2723
  }
2724
+ function writePdf2(doc, opts) {
2725
+ return writePdf(doc, opts);
2726
+ }
2361
2727
  export {
2362
2728
  HWPXBuilder,
2363
2729
  HWPXError,
@@ -2373,6 +2739,7 @@ export {
2373
2739
  createDefaultStyles,
2374
2740
  read,
2375
2741
  utils_exports as utils,
2376
- write
2742
+ write,
2743
+ writePdf2 as writePdf
2377
2744
  };
2378
2745
  //# sourceMappingURL=index.js.map