hwpkit-dev 0.0.3 → 0.0.5
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/ .npmignore +1 -0
- package/README.md +8 -8
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +573 -230
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +573 -230
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/decoders/hwp/HwpScanner.ts +174 -57
- package/src/decoders/hwpx/HwpxDecoder.ts +23 -12
- package/src/encoders/docx/DocxEncoder.ts +49 -4
- package/src/encoders/hwp/HwpEncoder.ts +309 -163
- package/src/encoders/hwpx/HwpxEncoder.ts +249 -103
- package/src/model/doc-props.ts +5 -5
- package/src/model/doc-tree.ts +2 -2
- package/test-styling.ts +0 -210
|
@@ -165,10 +165,11 @@ class BufWriter {
|
|
|
165
165
|
|
|
166
166
|
function mkRec(tag: number, level: number, data: Uint8Array): Uint8Array {
|
|
167
167
|
const sz = data.length;
|
|
168
|
-
const
|
|
169
|
-
const
|
|
168
|
+
const isLarge = sz >= 0xfff;
|
|
169
|
+
const enc = isLarge ? 0xfff : sz;
|
|
170
|
+
const hdr = (((enc & 0xfff) * 0x100000) | ((level & 0x3ff) << 10) | (tag & 0x3ff)) >>> 0;
|
|
170
171
|
const w = new BufWriter().u32(hdr);
|
|
171
|
-
if (
|
|
172
|
+
if (isLarge) w.u32(sz);
|
|
172
173
|
w.bytes(data);
|
|
173
174
|
return w.build();
|
|
174
175
|
}
|
|
@@ -674,7 +675,9 @@ function mkPageDef(dims: PageDims): Uint8Array {
|
|
|
674
675
|
.u32(Metric.ptToHwp(dims.mr))
|
|
675
676
|
.u32(Metric.ptToHwp(dims.mt))
|
|
676
677
|
.u32(Metric.ptToHwp(dims.mb))
|
|
677
|
-
.
|
|
678
|
+
.u32(dims.headerPt ? Metric.ptToHwp(dims.headerPt) : 0)
|
|
679
|
+
.u32(dims.footerPt ? Metric.ptToHwp(dims.footerPt) : 0)
|
|
680
|
+
.u32(0) // gutter
|
|
678
681
|
.u32(dims.orient === "landscape" ? 1 : 0)
|
|
679
682
|
.build(); // 40 bytes
|
|
680
683
|
}
|
|
@@ -1085,7 +1088,7 @@ function mkTableCtrl(
|
|
|
1085
1088
|
): Uint8Array {
|
|
1086
1089
|
// 표 정렬 속성 플래그 (HWP 표 제어 문자)
|
|
1087
1090
|
// offset 20-21: 속성 플래그 (align: left=0, center=1, right=2, justify=3)
|
|
1088
|
-
const alignFlags = { left: 0, center: 1, right: 2, justify: 3 }[align] ?? 0;
|
|
1091
|
+
const alignFlags = { left: 0, center: 1, right: 2, justify: 3, distribute: 0, distribute_space: 0 }[align] ?? 0;
|
|
1089
1092
|
return new BufWriter()
|
|
1090
1093
|
.u32(CTRL_TABLE)
|
|
1091
1094
|
.u32(0x082a2211)
|
|
@@ -1256,7 +1259,7 @@ function encodeGrid(
|
|
|
1256
1259
|
const cellWidthHwp = Metric.ptToHwp(cwPt[c] ?? defColPt);
|
|
1257
1260
|
for (const para of paras) {
|
|
1258
1261
|
records.push(
|
|
1259
|
-
...encodePara(para as ParaNode, bank, lv +
|
|
1262
|
+
...encodePara(para as ParaNode, bank, lv + 1, idGen(), cellWidthHwp),
|
|
1260
1263
|
);
|
|
1261
1264
|
}
|
|
1262
1265
|
}
|
|
@@ -1594,7 +1597,8 @@ function buildHwpOle2(
|
|
|
1594
1597
|
section0Data: Uint8Array,
|
|
1595
1598
|
binImages: BinImage[] = [],
|
|
1596
1599
|
): Uint8Array {
|
|
1597
|
-
const SS = 512;
|
|
1600
|
+
const SS = 512; // 정규 섹터 크기
|
|
1601
|
+
const MSS = 64; // 미니 섹터 크기
|
|
1598
1602
|
const ENDOFCHAIN = 0xfffffffe;
|
|
1599
1603
|
const FREESECT = 0xffffffff;
|
|
1600
1604
|
const FATSECT = 0xfffffffd;
|
|
@@ -1606,73 +1610,193 @@ function buildHwpOle2(
|
|
|
1606
1610
|
);
|
|
1607
1611
|
}
|
|
1608
1612
|
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1613
|
+
// 스트림 정의 및 4096바이트 미만(isMini) 여부 분류
|
|
1614
|
+
interface OleStream {
|
|
1615
|
+
name: string;
|
|
1616
|
+
data: Uint8Array;
|
|
1617
|
+
dirIdx: number;
|
|
1618
|
+
isMini: boolean;
|
|
1619
|
+
startSec?: number; // 미니 스트림은 미니 FAT 내 시작 인덱스, 정규 스트림은 regular FAT 내 시작 섹터
|
|
1615
1620
|
}
|
|
1616
1621
|
|
|
1617
|
-
const
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1622
|
+
const streams: OleStream[] = [];
|
|
1623
|
+
streams.push({
|
|
1624
|
+
name: "FileHeader",
|
|
1625
|
+
data: fileHeaderData,
|
|
1626
|
+
dirIdx: 1,
|
|
1627
|
+
isMini: fileHeaderData.length < 4096,
|
|
1628
|
+
});
|
|
1629
|
+
streams.push({
|
|
1630
|
+
name: "DocInfo",
|
|
1631
|
+
data: docInfoData,
|
|
1632
|
+
dirIdx: 2,
|
|
1633
|
+
isMini: docInfoData.length < 4096,
|
|
1634
|
+
});
|
|
1635
|
+
streams.push({
|
|
1636
|
+
name: "Section0",
|
|
1637
|
+
data: section0Data,
|
|
1638
|
+
dirIdx: 4,
|
|
1639
|
+
isMini: section0Data.length < 4096,
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
for (let i = 0; i < binImages.length; i++) {
|
|
1643
|
+
const img = binImages[i];
|
|
1644
|
+
const name = `BIN${String(img.id).padStart(4, "0")}.${img.ext}`;
|
|
1645
|
+
streams.push({
|
|
1646
|
+
name,
|
|
1647
|
+
data: img.data,
|
|
1648
|
+
dirIdx: 6 + i,
|
|
1649
|
+
isMini: img.data.length < 4096,
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// 미니 스트림 패킹 및 Mini FAT 체인 생성
|
|
1654
|
+
const miniStreams = streams.filter((s) => s.isMini);
|
|
1655
|
+
const miniSectorList: number[] = [];
|
|
1656
|
+
let miniStreamDataLength = 0;
|
|
1657
|
+
|
|
1658
|
+
for (const s of miniStreams) {
|
|
1659
|
+
const startSec = miniStreamDataLength / MSS;
|
|
1660
|
+
s.startSec = startSec;
|
|
1661
|
+
|
|
1662
|
+
const len = s.data.length;
|
|
1663
|
+
const numMiniSecs = Math.ceil(len / MSS);
|
|
1664
|
+
|
|
1665
|
+
for (let i = 0; i < numMiniSecs; i++) {
|
|
1666
|
+
const curSec = startSec + i;
|
|
1667
|
+
const nextSec = i === numMiniSecs - 1 ? ENDOFCHAIN : curSec + 1;
|
|
1621
1668
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1669
|
+
while (miniSectorList.length <= curSec) {
|
|
1670
|
+
miniSectorList.push(FREESECT);
|
|
1671
|
+
}
|
|
1672
|
+
miniSectorList[curSec] = nextSec;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
miniStreamDataLength += numMiniSecs * MSS;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// 미니 스트림 데이터 버퍼 구성
|
|
1679
|
+
const miniStreamData = new Uint8Array(miniStreamDataLength);
|
|
1680
|
+
let miniStreamOffset = 0;
|
|
1681
|
+
for (const s of miniStreams) {
|
|
1682
|
+
miniStreamData.set(s.data, miniStreamOffset);
|
|
1683
|
+
miniStreamOffset += Math.ceil(s.data.length / MSS) * MSS;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// 정규 스트림 패딩 및 섹터 개수 계산
|
|
1687
|
+
const regularStreams = streams.filter((s) => !s.isMini);
|
|
1688
|
+
const regPads = regularStreams.map((s) => {
|
|
1689
|
+
const len = s.data.length;
|
|
1690
|
+
const n = Math.ceil(Math.max(len, 1) / SS) * SS;
|
|
1691
|
+
const out = new Uint8Array(n);
|
|
1692
|
+
out.set(s.data);
|
|
1693
|
+
return out;
|
|
1694
|
+
});
|
|
1695
|
+
const regNs = regPads.map((p) => p.length / SS);
|
|
1627
1696
|
|
|
1697
|
+
// 디렉토리 섹터 크기 결정 (엔트리 개수 비례)
|
|
1628
1698
|
const numDirEntries = 5 + (binImages.length > 0 ? 1 + binImages.length : 0);
|
|
1629
|
-
const dirN = Math.max(
|
|
1699
|
+
const dirN = Math.max(1, Math.ceil((numDirEntries * 128) / SS));
|
|
1700
|
+
|
|
1701
|
+
// Mini FAT 섹터 크기 결정 (1 섹터당 128개 FAT 엔트리 수용)
|
|
1702
|
+
const miniFatN = Math.ceil(miniSectorList.length / 128);
|
|
1703
|
+
|
|
1704
|
+
// Mini Stream을 위한 정규 섹터 크기 결정
|
|
1705
|
+
const miniStreamN = Math.ceil(miniStreamData.length / SS);
|
|
1706
|
+
|
|
1707
|
+
// 정규 FAT 섹터 수 (fatN) 계산 루프
|
|
1708
|
+
const totalRegStreamN = regNs.reduce((a, b) => a + b, 0);
|
|
1709
|
+
const neededDataSec = dirN + miniFatN + miniStreamN + totalRegStreamN;
|
|
1630
1710
|
|
|
1631
1711
|
let fatN = 1;
|
|
1632
1712
|
for (let iter = 0; iter < 10; iter++) {
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
if (
|
|
1636
|
-
fatN =
|
|
1713
|
+
const totalSec = fatN + neededDataSec;
|
|
1714
|
+
const neededFat = Math.ceil(totalSec / 128);
|
|
1715
|
+
if (neededFat <= fatN) break;
|
|
1716
|
+
fatN = neededFat;
|
|
1637
1717
|
}
|
|
1638
1718
|
|
|
1639
|
-
const
|
|
1640
|
-
const fhSec = dir1Sec + dirN;
|
|
1641
|
-
const diSec = fhSec + fhN;
|
|
1642
|
-
const s0Sec = diSec + diN;
|
|
1719
|
+
const totalSec = fatN + neededDataSec;
|
|
1643
1720
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1721
|
+
// 각 정규 섹터 영역의 시작 위치 할당
|
|
1722
|
+
const dirStartSec = fatN;
|
|
1723
|
+
const miniFatStartSec = dirStartSec + dirN;
|
|
1724
|
+
const miniStreamStartSec = miniFatStartSec + miniFatN;
|
|
1725
|
+
|
|
1726
|
+
let curSec = miniStreamStartSec + miniStreamN;
|
|
1727
|
+
for (let i = 0; i < regularStreams.length; i++) {
|
|
1728
|
+
regularStreams[i].startSec = curSec;
|
|
1729
|
+
curSec += regNs[i];
|
|
1649
1730
|
}
|
|
1650
|
-
const totalSec = curSec;
|
|
1651
1731
|
|
|
1732
|
+
// 정규 FAT 버퍼 구성
|
|
1652
1733
|
const fatBuf = new Uint8Array(fatN * SS).fill(0xff);
|
|
1653
1734
|
const setFat = (i: number, v: number) => {
|
|
1654
|
-
|
|
1655
|
-
fatBuf[
|
|
1656
|
-
fatBuf[
|
|
1657
|
-
fatBuf[
|
|
1735
|
+
const off = i * 4;
|
|
1736
|
+
fatBuf[off] = v & 0xff;
|
|
1737
|
+
fatBuf[off + 1] = (v >>> 8) & 0xff;
|
|
1738
|
+
fatBuf[off + 2] = (v >>> 16) & 0xff;
|
|
1739
|
+
fatBuf[off + 3] = (v >>> 24) & 0xff;
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
// FAT 섹터 자신 표시
|
|
1743
|
+
for (let i = 0; i < fatN; i++) {
|
|
1744
|
+
setFat(i, FATSECT);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// Directory 섹터 체인 연결
|
|
1748
|
+
for (let i = 0; i < dirN; i++) {
|
|
1749
|
+
setFat(
|
|
1750
|
+
dirStartSec + i,
|
|
1751
|
+
i + 1 < dirN ? dirStartSec + i + 1 : ENDOFCHAIN,
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// Mini FAT 섹터 체인 연결
|
|
1756
|
+
if (miniFatN > 0) {
|
|
1757
|
+
for (let i = 0; i < miniFatN; i++) {
|
|
1758
|
+
setFat(
|
|
1759
|
+
miniFatStartSec + i,
|
|
1760
|
+
i + 1 < miniFatN ? miniFatStartSec + i + 1 : ENDOFCHAIN,
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// Mini Stream 섹터 체인 연결
|
|
1766
|
+
if (miniStreamN > 0) {
|
|
1767
|
+
for (let i = 0; i < miniStreamN; i++) {
|
|
1768
|
+
setFat(
|
|
1769
|
+
miniStreamStartSec + i,
|
|
1770
|
+
i + 1 < miniStreamN ? miniStreamStartSec + i + 1 : ENDOFCHAIN,
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// 정규 스트림 섹터 체인 연결
|
|
1776
|
+
for (let i = 0; i < regularStreams.length; i++) {
|
|
1777
|
+
const s = regularStreams[i];
|
|
1778
|
+
const n = regNs[i];
|
|
1779
|
+
const start = s.startSec!;
|
|
1780
|
+
for (let j = 0; j < n; j++) {
|
|
1781
|
+
setFat(start + j, j + 1 < n ? start + j + 1 : ENDOFCHAIN);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Mini FAT 버퍼 생성
|
|
1786
|
+
const miniFatBuf = new Uint8Array(miniFatN * SS).fill(0xff);
|
|
1787
|
+
const setMiniFat = (i: number, v: number) => {
|
|
1788
|
+
const off = i * 4;
|
|
1789
|
+
miniFatBuf[off] = v & 0xff;
|
|
1790
|
+
miniFatBuf[off + 1] = (v >>> 8) & 0xff;
|
|
1791
|
+
miniFatBuf[off + 2] = (v >>> 16) & 0xff;
|
|
1792
|
+
miniFatBuf[off + 3] = (v >>> 24) & 0xff;
|
|
1658
1793
|
};
|
|
1659
1794
|
|
|
1660
|
-
for (let i = 0; i <
|
|
1661
|
-
|
|
1662
|
-
setFat(dir1Sec + i, i + 1 < dirN ? dir1Sec + i + 1 : ENDOFCHAIN);
|
|
1663
|
-
for (let i = 0; i < fhN; i++)
|
|
1664
|
-
setFat(fhSec + i, i + 1 < fhN ? fhSec + i + 1 : ENDOFCHAIN);
|
|
1665
|
-
for (let i = 0; i < diN; i++)
|
|
1666
|
-
setFat(diSec + i, i + 1 < diN ? diSec + i + 1 : ENDOFCHAIN);
|
|
1667
|
-
for (let i = 0; i < s0N; i++)
|
|
1668
|
-
setFat(s0Sec + i, i + 1 < s0N ? s0Sec + i + 1 : ENDOFCHAIN);
|
|
1669
|
-
for (let ii = 0; ii < imgNs.length; ii++) {
|
|
1670
|
-
const start = imgSecs[ii];
|
|
1671
|
-
const n = imgNs[ii];
|
|
1672
|
-
for (let i = 0; i < n; i++)
|
|
1673
|
-
setFat(start + i, i + 1 < n ? start + i + 1 : ENDOFCHAIN);
|
|
1795
|
+
for (let i = 0; i < miniSectorList.length; i++) {
|
|
1796
|
+
setMiniFat(i, miniSectorList[i]);
|
|
1674
1797
|
}
|
|
1675
1798
|
|
|
1799
|
+
// 디렉토리 버퍼 생성
|
|
1676
1800
|
const dirBuf = new Uint8Array(dirN * SS);
|
|
1677
1801
|
const dv = new DataView(dirBuf.buffer);
|
|
1678
1802
|
|
|
@@ -1689,11 +1813,12 @@ function buildHwpOle2(
|
|
|
1689
1813
|
const base = idx * 128;
|
|
1690
1814
|
const nl = name.length;
|
|
1691
1815
|
// OLE2: 이름은 UTF-16LE, (글자수+1)*2 바이트가 길이 필드에 기록됨
|
|
1692
|
-
for (let i = 0; i < nl; i++)
|
|
1816
|
+
for (let i = 0; i < nl; i++) {
|
|
1693
1817
|
dv.setUint16(base + i * 2, name.charCodeAt(i), true);
|
|
1818
|
+
}
|
|
1694
1819
|
dv.setUint16(base + 64, (nl + 1) * 2, true);
|
|
1695
1820
|
dirBuf[base + 66] = type;
|
|
1696
|
-
dirBuf[base + 67] = 1; // DE_NODE
|
|
1821
|
+
dirBuf[base + 67] = 1; // DE_NODE (블랙 노드)
|
|
1697
1822
|
dv.setInt32(base + 68, left, true);
|
|
1698
1823
|
dv.setInt32(base + 72, right, true);
|
|
1699
1824
|
dv.setInt32(base + 76, child, true);
|
|
@@ -1701,7 +1826,7 @@ function buildHwpOle2(
|
|
|
1701
1826
|
dv.setUint32(base + 120, size >>> 0, true);
|
|
1702
1827
|
}
|
|
1703
1828
|
|
|
1704
|
-
//
|
|
1829
|
+
// 모든 디렉토리 엔트리 -1 (NOSTREAM)으로 초기화
|
|
1705
1830
|
for (let i = 0; i < dirN * 4; i++) {
|
|
1706
1831
|
const base = i * 128;
|
|
1707
1832
|
dv.setInt32(base + 68, -1, true);
|
|
@@ -1709,142 +1834,163 @@ function buildHwpOle2(
|
|
|
1709
1834
|
dv.setInt32(base + 76, -1, true);
|
|
1710
1835
|
}
|
|
1711
1836
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1837
|
+
// 스트림 인덱스 맵 생성
|
|
1838
|
+
const streamMap = new Map<number, OleStream>();
|
|
1839
|
+
for (const s of streams) {
|
|
1840
|
+
streamMap.set(s.dirIdx, s);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Root Entry (0번): 미니 스트림의 루트 역할을 하며 미니 스트림 데이터를 가리킴
|
|
1844
|
+
writeDirEntry(
|
|
1845
|
+
0,
|
|
1846
|
+
"Root Entry",
|
|
1847
|
+
5,
|
|
1848
|
+
-1,
|
|
1849
|
+
-1,
|
|
1850
|
+
3,
|
|
1851
|
+
miniStreamStartSec,
|
|
1852
|
+
miniStreamData.length,
|
|
1853
|
+
);
|
|
1721
1854
|
|
|
1855
|
+
// HWP Root Entry CLSID 설정
|
|
1856
|
+
const HWP_CLSID = [
|
|
1857
|
+
0x20, 0xe9, 0xe3, 0xc0, 0x46, 0x35, 0xcf, 0x11, 0x8d, 0x81, 0x00, 0xaa,
|
|
1858
|
+
0x00, 0x38, 0x9b, 0x71,
|
|
1859
|
+
];
|
|
1860
|
+
for (let i = 0; i < 16; i++) {
|
|
1861
|
+
dirBuf[0 * 128 + 80 + i] = HWP_CLSID[i];
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// FileHeader (1번)
|
|
1865
|
+
const fhStream = streamMap.get(1)!;
|
|
1866
|
+
writeDirEntry(
|
|
1867
|
+
1,
|
|
1868
|
+
"FileHeader",
|
|
1869
|
+
2,
|
|
1870
|
+
-1,
|
|
1871
|
+
-1,
|
|
1872
|
+
-1,
|
|
1873
|
+
fhStream.startSec!,
|
|
1874
|
+
fhStream.data.length,
|
|
1875
|
+
);
|
|
1876
|
+
|
|
1877
|
+
// DocInfo (2번) - 이미지가 있는 경우 BinData(5번)를 left로 지정
|
|
1878
|
+
const diStream = streamMap.get(2)!;
|
|
1879
|
+
const docInfoLeft = binImages.length > 0 ? 5 : -1;
|
|
1880
|
+
writeDirEntry(
|
|
1881
|
+
2,
|
|
1882
|
+
"DocInfo",
|
|
1883
|
+
2,
|
|
1884
|
+
docInfoLeft,
|
|
1885
|
+
-1,
|
|
1886
|
+
-1,
|
|
1887
|
+
diStream.startSec!,
|
|
1888
|
+
diStream.data.length,
|
|
1889
|
+
);
|
|
1890
|
+
|
|
1891
|
+
// BodyText (3번) - Root Entry 아래의 검색 허브 역할 (left: DocInfo(2), right: FileHeader(1), child: Section0(4))
|
|
1892
|
+
writeDirEntry(3, "BodyText", 1, 2, 1, 4, ENDOFCHAIN, 0);
|
|
1893
|
+
|
|
1894
|
+
// Section0 (4번)
|
|
1895
|
+
const s0Stream = streamMap.get(4)!;
|
|
1896
|
+
writeDirEntry(
|
|
1897
|
+
4,
|
|
1898
|
+
"Section0",
|
|
1899
|
+
2,
|
|
1900
|
+
-1,
|
|
1901
|
+
-1,
|
|
1902
|
+
-1,
|
|
1903
|
+
s0Stream.startSec!,
|
|
1904
|
+
s0Stream.data.length,
|
|
1905
|
+
);
|
|
1906
|
+
|
|
1907
|
+
// BinData (5번)
|
|
1722
1908
|
if (binImages.length > 0) {
|
|
1723
|
-
writeDirEntry(0, "Root Entry", 5, -1, -1, 1, ENDOFCHAIN, 0);
|
|
1724
|
-
writeDirEntry(1, "FileHeader", 2, -1, 2, -1, fhSec, fileHeaderData.length);
|
|
1725
|
-
writeDirEntry(2, "DocInfo", 2, -1, 3, -1, diSec, docInfoData.length);
|
|
1726
|
-
writeDirEntry(3, "BodyText", 1, -1, 5, 4, ENDOFCHAIN, 0);
|
|
1727
|
-
writeDirEntry(4, "Section0", 2, -1, -1, -1, s0Sec, section0Data.length);
|
|
1728
1909
|
writeDirEntry(5, "BinData", 1, -1, -1, 6, ENDOFCHAIN, 0);
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
const
|
|
1732
|
-
const sibling =
|
|
1910
|
+
|
|
1911
|
+
for (let i = 0; i < binImages.length; i++) {
|
|
1912
|
+
const imgStream = streamMap.get(6 + i)!;
|
|
1913
|
+
const sibling = i + 1 < binImages.length ? 7 + i : -1;
|
|
1733
1914
|
writeDirEntry(
|
|
1734
|
-
6 +
|
|
1735
|
-
|
|
1915
|
+
6 + i,
|
|
1916
|
+
imgStream.name,
|
|
1736
1917
|
2,
|
|
1737
1918
|
-1,
|
|
1738
1919
|
sibling,
|
|
1739
1920
|
-1,
|
|
1740
|
-
|
|
1741
|
-
|
|
1921
|
+
imgStream.startSec!,
|
|
1922
|
+
imgStream.data.length,
|
|
1742
1923
|
);
|
|
1743
1924
|
}
|
|
1744
|
-
} else {
|
|
1745
|
-
writeDirEntry(0, "Root Entry", 5, -1, -1, 1, ENDOFCHAIN, 0);
|
|
1746
|
-
writeDirEntry(1, "FileHeader", 2, -1, 2, -1, fhSec, fileHeaderData.length);
|
|
1747
|
-
writeDirEntry(2, "DocInfo", 2, -1, 3, -1, diSec, docInfoData.length);
|
|
1748
|
-
writeDirEntry(3, "BodyText", 1, -1, -1, 4, ENDOFCHAIN, 0);
|
|
1749
|
-
writeDirEntry(4, "Section0", 2, -1, -1, -1, s0Sec, section0Data.length);
|
|
1750
1925
|
}
|
|
1751
1926
|
|
|
1752
|
-
//
|
|
1753
|
-
const HWP_CLSID = [
|
|
1754
|
-
0x20, 0xe9, 0xe3, 0xc0, 0x46, 0x35, 0xcf, 0x11, 0x8d, 0x81, 0x00, 0xaa,
|
|
1755
|
-
0x00, 0x38, 0x9b, 0x71,
|
|
1756
|
-
];
|
|
1757
|
-
for (let i = 0; i < 16; i++) dirBuf[80 + i] = HWP_CLSID[i];
|
|
1758
|
-
|
|
1927
|
+
// 헤더 생성 (512바이트)
|
|
1759
1928
|
const hdr = new Uint8Array(SS);
|
|
1760
1929
|
const hdv = new DataView(hdr.buffer);
|
|
1761
1930
|
|
|
1762
|
-
// OLE2
|
|
1763
|
-
// 0-7: Magic number (D0 CF 11 E0 A1 B1 1A E1)
|
|
1764
|
-
// 8-23: CLSID (16 bytes)
|
|
1765
|
-
// 24-25: Minor version (0x003E = 62)
|
|
1766
|
-
// 26-27: Major version (0x0003 = 3)
|
|
1767
|
-
// 28-29: Byte order (0x00FE = Little-Endian)
|
|
1768
|
-
// 30-31: Sector size exponent (0x0009 = 2^9 = 512)
|
|
1769
|
-
// 32-33: Mini sector size exponent (0x0006 = 2^6 = 64)
|
|
1770
|
-
// 34-39: Reserved (6 bytes)
|
|
1771
|
-
// 40-43: Number of FAT sectors
|
|
1772
|
-
// 44-47: Directory start sector location
|
|
1773
|
-
// 48-51: Transaction signature number
|
|
1774
|
-
// 52-55: Mini stream cutoff size (0x1000 = 4096)
|
|
1775
|
-
// 56-59: Mini FAT start sector location
|
|
1776
|
-
// 60-63: Number of mini FAT sectors
|
|
1777
|
-
// 64-67: FAT start sector location
|
|
1778
|
-
// 68-71: Number of backup FAT sectors
|
|
1779
|
-
// 72-75: Backup FAT start sector location
|
|
1780
|
-
// 76-511: Sector bitmap (109 sectors worth)
|
|
1781
|
-
|
|
1782
|
-
// Magic number
|
|
1931
|
+
// OLE2 시그니처 (Magic)
|
|
1783
1932
|
const MAGIC = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1];
|
|
1784
1933
|
MAGIC.forEach((b, i) => {
|
|
1785
1934
|
hdr[i] = b;
|
|
1786
1935
|
});
|
|
1787
1936
|
|
|
1788
|
-
// CLSID is already set in dirBuf[0][80:96] for Root Entry
|
|
1789
|
-
|
|
1790
|
-
// Version
|
|
1791
1937
|
hdv.setUint16(24, 0x003e, true); // Minor version
|
|
1792
1938
|
hdv.setUint16(26, 0x0003, true); // Major version
|
|
1939
|
+
hdv.setUint16(28, 0xfffe, true); // Byte order (0xFFFE = 리틀 엔디안 BOM)
|
|
1940
|
+
hdv.setUint16(30, 0x0009, true); // Sector size exponent (2^9 = 512)
|
|
1941
|
+
hdv.setUint16(32, 0x0006, true); // Mini sector size exponent (2^6 = 64)
|
|
1942
|
+
|
|
1943
|
+
hdv.setUint32(40, 0, true); // Number of Directory Sectors (Version 3에서는 0으로 채움)
|
|
1944
|
+
hdv.setUint32(44, fatN, true); // Number of FAT Sectors (0x002C)
|
|
1945
|
+
hdv.setUint32(48, dirStartSec, true); // Starting Sector of Directory Stream (0x0030)
|
|
1946
|
+
hdv.setUint32(52, 0, true); // Transaction Signature Number (0x0034)
|
|
1947
|
+
hdv.setUint32(56, 0x1000, true); // Mini Stream Cutoff Size (0x0038)
|
|
1948
|
+
hdv.setUint32(60, miniFatN > 0 ? miniFatStartSec : ENDOFCHAIN, true); // Starting Sector of Mini FAT (0x003C)
|
|
1949
|
+
hdv.setUint32(64, miniFatN, true); // Number of Mini FAT Sectors (0x0040)
|
|
1950
|
+
hdv.setUint32(68, ENDOFCHAIN, true); // Starting Sector of DIFAT (0x0044)
|
|
1951
|
+
hdv.setUint32(72, 0, true); // Number of DIFAT Sectors (0x0048)
|
|
1952
|
+
|
|
1953
|
+
// Sector bitmap (DIFAT) 슬롯 채우기 (처음 109개 FAT 섹터 번호 지정)
|
|
1954
|
+
for (let i = 0; i < 109; i++) {
|
|
1955
|
+
hdv.setUint32(76 + i * 4, i < fatN ? i : FREESECT, true);
|
|
1956
|
+
}
|
|
1793
1957
|
|
|
1794
|
-
//
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
// Sector size exponent (2^9 = 512)
|
|
1798
|
-
hdv.setUint16(30, 0x0009, true);
|
|
1799
|
-
|
|
1800
|
-
// Mini sector size exponent (2^6 = 64)
|
|
1801
|
-
hdv.setUint16(32, 0x0006, true);
|
|
1802
|
-
|
|
1803
|
-
// Reserved (34-39, 6 bytes) - already zero
|
|
1804
|
-
|
|
1805
|
-
// Number of FAT sectors
|
|
1806
|
-
hdv.setUint32(40, fatN, true);
|
|
1807
|
-
|
|
1808
|
-
// Directory start sector location
|
|
1809
|
-
hdv.setUint32(44, dir1Sec, true);
|
|
1810
|
-
|
|
1811
|
-
// Transaction signature number
|
|
1812
|
-
hdv.setUint32(48, 0, true);
|
|
1813
|
-
|
|
1814
|
-
// Mini stream cutoff size
|
|
1815
|
-
hdv.setUint32(52, 0x1000, true);
|
|
1958
|
+
// 최종 바이트 어레이 조립 및 출력 생성
|
|
1959
|
+
const out = new Uint8Array(SS + totalSec * SS);
|
|
1960
|
+
let outOff = 0;
|
|
1816
1961
|
|
|
1817
|
-
//
|
|
1818
|
-
|
|
1962
|
+
// 1. Header (Sector -1)
|
|
1963
|
+
out.set(hdr, outOff);
|
|
1964
|
+
outOff += SS;
|
|
1819
1965
|
|
|
1820
|
-
//
|
|
1821
|
-
|
|
1966
|
+
// 2. FAT sectors
|
|
1967
|
+
out.set(fatBuf, outOff);
|
|
1968
|
+
outOff += fatN * SS;
|
|
1822
1969
|
|
|
1823
|
-
//
|
|
1824
|
-
|
|
1970
|
+
// 3. Directory sectors
|
|
1971
|
+
out.set(dirBuf, outOff);
|
|
1972
|
+
outOff += dirN * SS;
|
|
1825
1973
|
|
|
1826
|
-
//
|
|
1827
|
-
|
|
1974
|
+
// 4. Mini FAT sectors
|
|
1975
|
+
if (miniFatN > 0) {
|
|
1976
|
+
out.set(miniFatBuf, outOff);
|
|
1977
|
+
outOff += miniFatN * SS;
|
|
1978
|
+
}
|
|
1828
1979
|
|
|
1829
|
-
//
|
|
1830
|
-
|
|
1980
|
+
// 5. Mini Stream sectors (Root Entry Data)
|
|
1981
|
+
if (miniStreamN > 0) {
|
|
1982
|
+
const miniStreamPad = new Uint8Array(miniStreamN * SS);
|
|
1983
|
+
miniStreamPad.set(miniStreamData);
|
|
1984
|
+
out.set(miniStreamPad, outOff);
|
|
1985
|
+
outOff += miniStreamN * SS;
|
|
1986
|
+
}
|
|
1831
1987
|
|
|
1832
|
-
//
|
|
1833
|
-
for (let i = 0; i <
|
|
1834
|
-
|
|
1988
|
+
// 6. Regular Streams sectors
|
|
1989
|
+
for (let i = 0; i < regularStreams.length; i++) {
|
|
1990
|
+
out.set(regPads[i], outOff);
|
|
1991
|
+
outOff += regNs[i] * SS;
|
|
1835
1992
|
}
|
|
1836
1993
|
|
|
1837
|
-
const out = new Uint8Array(SS + totalSec * SS);
|
|
1838
|
-
out.set(hdr, 0);
|
|
1839
|
-
for (let i = 0; i < fatN; i++)
|
|
1840
|
-
out.set(fatBuf.subarray(i * SS, (i + 1) * SS), SS + i * SS);
|
|
1841
|
-
for (let i = 0; i < dirN; i++)
|
|
1842
|
-
out.set(dirBuf.subarray(i * SS, (i + 1) * SS), SS + (dir1Sec + i) * SS);
|
|
1843
|
-
out.set(fhPad, SS + fhSec * SS);
|
|
1844
|
-
out.set(diPad, SS + diSec * SS);
|
|
1845
|
-
out.set(s0Pad, SS + s0Sec * SS);
|
|
1846
|
-
for (let ii = 0; ii < imgPads.length; ii++)
|
|
1847
|
-
out.set(imgPads[ii], SS + imgSecs[ii] * SS);
|
|
1848
1994
|
return out;
|
|
1849
1995
|
}
|
|
1850
1996
|
|