npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hwp-convert

v1.13.0

Published

HWP/HWPX ↔ Markdown/HTML 변환 + 한컴(한글)에서 실제로 열리는 HWPX 생성. 순수 TypeScript, 브라우저/Node. 표·이미지·폰트·스타일 보존.

Readme

hwp-convert

HWP 5.0(바이너리/CFB)·HWPX(OWPML/ZIP+XML) ↔ Markdown/HTML 변환 라이브러리. Node.js 와 브라우저(ESM) 양쪽에서 동작하며, 별도의 한글(HWP) 클라이언트 의존성 없이 순수 TypeScript 만으로 처리합니다.

핵심: 생성한 HWPX 가 한컴오피스(한글)에서 실제로 열립니다. Markdown·HTML 을 변환해도 표·이미지·서식이 보존된 채 정상 표시됩니다.

  • HWPX 는 ZIP 패키지 내부에 OWPML XML 로 본문을 저장하는 개방형 포맷입니다. (참고: HWPX 포맷 구조 살펴보기)
  • HWP 바이너리(CFB/OLE2) 파서는 rhwp (Rust, MIT, Copyright (c) 2025-2026 Edward Kim) 의 구조를 TypeScript 로 포팅한 것입니다. 각 포팅 파일 헤더에 출처를 명시합니다.
  • 이 프로젝트는 hwpxjs (MIT, Copyright (c) 2025 ssabro) 에서 fork·확장한 것입니다. 한컴 호환 HWPX 생성(OWPML 컨벤션 정합) 과 로컬 파일 이미지 임베드를 더했습니다. 출처·라이선스는 LICENSENOTICE 참조. 본 저장소도 동일한 MIT 라이선스를 따릅니다.

주요 기능

  • HWPX 읽기: 패키지 inspect, 평문/HTML/Markdown 추출 (이미지 인라인/리졸버 지원)
  • HWPX 쓰기: OWPML 패키지 규칙(첫 STORED mimetype, META-INF/container.xml/manifest.xml, OPF spine) 호환 .hwpx 생성
  • HWP 5.0 파싱: CFB(OLE2) + raw deflate 컨테이너 → FileHeader / DocInfo / BodyText 풀 파서
  • HWP → HWPX 변환: 표(셀 병합 포함), 이미지(BinData 패키징), 폰트/문단/스타일 정의 보존, Preview/PrvText.txt 자동 생성
  • Markdown ↔ HWPX 양방향: MD lexer → IR / IR → MD writer
  • HTML → HWPX: htmlparser2 기반 DOM → IR
  • 표·이미지·스타일 보존: 글자 모양(굵게/기울임/밑줄/색/크기), 문단 모양(정렬/들여쓰기/줄간격), 7개 언어별 폰트 그룹, 번호/글머리표 형식 문자열까지 OWPML refList 로 옮겨 라운드트립 가능
  • 배경 채우기 박스: HTML 블록(div/p/h1~6)·표 셀의 background-color 를 글자 음영이 아니라 문단/셀 전체 너비 채우기(OWPML borderFill fillBrush + paraPr border)로 변환 — 한컴에서 제목 띠·헤더 배경이 박스로 표시. 인라인(span 등) 배경은 글자 음영 유지
  • 다단 레이아웃 → 표: HTML display:grid(grid-template-columns)·display:flex(row) 가로 다단 컨테이너(블록 자식 2개 이상)를 N열 표로 합성해 좌우 배치 보존 — 발주처|공급처 2칼럼 등이 한글에서 세로로 무너지지 않음. 컬럼 너비는 grid-template-columns px 비율 반영. 인라인 자식 flex(라벨:값)·flex-direction:column 은 변환 안 함
  • CSS 테두리 보존: div 박스·표 셀의 border(border-width/style/color, 면별 개별 포함)를 OWPML borderFill 4면 테두리로 반영(width px→HWP 너비 인덱스, solid/dashed/dotted/double). border-width: 0 인 레이아웃 래퍼·무테두리 div 엔 선이 생기지 않음(투명 유지). 발주처/공급처 박스 외곽선이 실제 출력으로 표시
  • 셀 마감: padding(셀 안쪽 여백)·grid gap(칼럼 간격→표 cellSpacing)·vertical-align(셀 세로 정렬, 레이아웃 셀 기본 상단) 반영. px→HWPUNIT(px×75)
  • 용지/여백 보존 + 명시 제어: 루트 컨테이너의 padding(→페이지 여백)·max-width/width(→본문폭)를 페이지 설정으로 자동 반영하고, htmlToHwpx(html, { page }) 옵션으로 용지 종류·방향·여백을 명시 지정할 수 있다.
    • page.size: 'A4'|'A3'|'A5'|'B4'|'B5'|'Letter'|'Legal' 또는 커스텀 { width, height, unit:'mm'|'hwpunit' }
    • page.orientation: 'auto'(기본, 본문폭이 세로 가용폭을 넘으면 가로) / 'portrait' / 'landscape'(명시 고정)
    • page.margins: { left, right, top, bottom, header, footer, gutter }(mm)
    • 한글 네이티브 모델: 용지 치수는 물리값(세로 기준) 고정, 방향은 landscape 플래그(WIDELY=세로/NARROWLY=가로)로 표현. 우선순위는 옵션 > 컨테이너 CSS > 기본값(용지 A4 / 세로 / 한글 기본여백)으로 자동 귀결. px 단위만 인식(%·auto 무시)
  • 인라인 테두리(도장박스): 인라인 <span>border 를 글자 테두리(charPr borderFillIDRef)로 반영. 빈 박스(서명 인감란 등)는 placeholder 로 보존
  • 이미지 크기/비율: PNG/JPEG/GIF/BMP 헤더에서 원본 px 를 읽어 비율 보존(찌그러짐 방지) + <img>width/height(px·%) 반영. 페이지 본문 폭 기준 % 지원
  • CLI: inspect / txt / html / md / hwp:txt / hwp:md / html:tpl / batch / batch:tpl / write:txt / md:hwpx / html:hwpx / convert:hwp
  • 템플릿 처리: {{key}} 텍스트 치환 (CLI / 라이브러리)
  • 브라우저 ESM 번들: dist/browser/hwp-convert.browser.mjs (esbuild, 약 830KB, 모든 의존성 인라인)

변환 매트릭스

| 출발 \ 도착 | text | HTML | Markdown | HWPX | HWP | | --- | --- | --- | --- | --- | --- | | HWP | ✅ | ⚠️ HWPX 거쳐서 | ✅ hwpToMarkdown | ✅ hwpToHwpx | ─ | | HWPX | ✅ extractText | ✅ extractHtml | ✅ extractMarkdown | ─ | ❌ | | Markdown | ─ | ─ | ─ | ✅ markdownToHwpx | ❌ | | HTML | ─ | ─ | ─ | ✅ htmlToHwpx | ❌ | | plain text | ─ | ─ | ─ | ✅ HwpxWriter | ❌ | | PDF | ❌ | ❌ | ❌ | ❌ | ❌ |

PDF 와 HWPX → HWP 역변환은 별도 도메인이라 미지원. PDF 가 필요하면 변환된 HWPX 를 LibreOffice (libreoffice --headless --convert-to pdf) 또는 헤드리스 Chrome 인쇄로 변환하시면 됩니다.

변환 스펙

변환 계약(옵션·기본값·단위·우선순위)입니다. 사용 예제는 아래 라이브러리 사용을 참고하세요.

옵션

| 함수 | 옵션 | 기본값 | | --- | --- | --- | | htmlToHwpx(html, options) | title, creator, imageResolver, page | 모두 선택 | | markdownToHwpx(md, options) | title, creator, imageResolver | 모두 선택 (page 없음 — HTML 경로 전용) | | hwpToHwpx(bytes, options) | title, creator | 모두 선택 | | page (PageSetupOption) | size, orientation, margins | A4 / 'auto' / 한글 기본 여백 | | HwpxReader.extractMarkdown(options) | embedImages, imageSrcResolver | embedImages: false | | HwpxReader.extractHtml(options) | embedImages, imageSrcResolver | embedImages: false | | hwpToMarkdown(bytes, options) | embedImages, imageSrcResolver | embedImages: false |

라이브러리 기본은 모두 경로 참조(embedImages: false)입니다. CLI 는 html 명령만 인라인을 기본으로 하며, 자세한 내용은 CLI를 참고하세요.

  • page.size: 'A4' | 'A3' | 'A5' | 'B4' | 'B5' | 'Letter' | 'Legal' 또는 커스텀 { width, height, unit?: 'mm' | 'hwpunit' }
  • page.orientation: 'auto'(본문폭이 세로 가용폭을 넘으면 가로) | 'portrait' | 'landscape'
  • page.margins(mm): left, right, top, bottom, header, footer, gutter — 지정한 면만 반영

페이지 설정 우선순위

API 옵션 > @page CSS > 컨테이너 CSS(padding / max-width) > 기본값(A4 · 세로 · 한글 기본 여백)

필드 단위로 병합됩니다. 예를 들어 API 로 margins.left 만 주면 나머지 면은 @page → 컨테이너 → 기본값 순으로 채워집니다.

  • @page 는 셀렉터 없는 블록만 인식하며, 뒤 블록이 앞 블록을 덮습니다(CSS cascade). @page :first 와 named page 는 미지원입니다.
  • 용지 방향은 치수를 바꾸지 않고 플래그로 표현합니다. 한글 네이티브 모델과 동일하게 용지 치수는 항상 세로 기준 물리값이고, 방향은 landscape 속성(WIDELY=세로 / NARROWLY=가로)이 결정합니다.

단위

| 항목 | 값 | | --- | --- | | HWPUNIT | 1/7200 inch | | CSS px → HWPUNIT | px × 75 | | mm → HWPUNIT | mm × 7200 / 25.4 | | 글자 크기 | HWPUNIT (10pt = 1000) |

%auto 는 길이 값으로 인식하지 않습니다(무시하고 다음 우선순위로 넘어감). 단 <img>width/height% 를 본문 폭 기준으로 해석합니다.

이미지

  • 입력: data: URI 는 그대로 처리하고, file:// · 로컬/상대 경로는 imageResolver 를 주입했을 때만 해석합니다(미주입 시 해당 이미지는 건너뜁니다). 코어는 Node API 에 의존하지 않으므로 브라우저에서도 동작합니다.
  • 크기: PNG/JPEG/GIF/BMP 헤더에서 원본 px 를 읽어 비율을 보존합니다. width/height 를 주면 그 값이 우선하고, 한쪽만 주면 나머지는 비율로 채웁니다.
  • 출력(MD/HTML): embedImages: true → base64 data URI 인라인 / imageSrcResolver 지정 → 변환된 경로 / 기본 → BinData/imageN.ext 경로 참조.

오류

미지원 입력은 조용히 실패하지 않고 전용 에러를 던집니다 — HwpEncryptedError(암호화), HwpUnsupportedError(배포용 ViewText · HWP 3.0), HwpInvalidFormatError(형식 불일치). 자세한 사용법은 오류 처리를 참고하세요.

설치

pnpm add hwp-convert
# or
npm i hwp-convert

런타임 의존성: cfb, fast-xml-parser, jszip, pako. Node.js 18+ 권장.

라이브러리 사용

HWPX 파일 읽기

import HwpxReader from "hwp-convert";
import { readFile } from "node:fs/promises";

const buf = await readFile("./document.hwpx");
const reader = new HwpxReader();
await reader.loadFromArrayBuffer(
  buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
);

// 패키지 메타/매니페스트 정보
const info = await reader.getDocumentInfo();
// { metadata: { title?, creator?, created?, modified?, version?, caretPosition? },
//   summary:  { hasEncryptionInfo, contentsFiles, manifest?, spine? } }

// 텍스트 추출 (표 셀까지 재귀 탐색)
const text = await reader.extractText();

// 이미지 경로 목록 (BinData/ 내 파일 경로)
const images = await reader.listImages();
// → ["BinData/image1.png", "BinData/image2.jpg", ...]

HTML 변환

const html = await reader.extractHtml({
  paragraphTag: "p",            // default "p"
  tableClassName: "hwpx-table", // default "hwpx-table"
  renderImages: true,           // default true
  renderTables: true,           // default true (rowSpan/colSpan 보존)
  renderStyles: true,           // default true (charProperties 기반 굵게/기울임/색/크기)
  embedImages: false,           // default false. true 시 data: URL 인라인
  tableHeaderFirstRow: false,   // default false. true 시 첫 행을 <th>
  imageSrcResolver: (binPath) => `/static/images/${binPath}`,
});

HWP 바이너리 파싱 / 변환

import {
  parseHwp,
  hwpToText,
  hwpToHwpx,
  detectFormat,
  HwpEncryptedError,
  HwpUnsupportedError,
  HwpInvalidFormatError,
} from "hwp-convert";
import { readFile, writeFile } from "node:fs/promises";

const bytes = new Uint8Array(await readFile("./input.hwp"));

// 포맷 자동 감지: "hwp" | "hwpx" | "hwp3" | "unknown"
const fmt = detectFormat(bytes);

// 평문 추출
const text = await hwpToText(bytes);

// HwpDocument IR (header / docInfo / sections / binData) 직접 접근
const doc = parseHwp(bytes);
console.log(doc.docInfo.styles, doc.sections.length);

// HWPX 로 변환 (표·이미지·스타일·미리보기 모두 포함)
const hwpxBytes = await hwpToHwpx(bytes, { title: "변환본", creator: "hwp-convert" });
await writeFile("./output.hwpx", hwpxBytes);

hwpToHwpx 가 만든 HWPX 는 다음을 보존합니다:

  • 표(<hp:tbl>/<hp:tr>/<hp:tc>/<hp:subList>) — colSpan/rowSpan 그대로
  • 이미지 — BinData/imageN.{png|jpg|...} 패키징 + 매니페스트 등록 + <hp:pic>/<hc:img binaryItemIDRef> 인라인
  • 폰트 — 7개 언어 그룹별(HANGUL/LATIN/HANJA/JAPANESE/OTHER/SYMBOL/USER) 실제 사용 폰트
  • 글자 모양 — <hh:charPr>fontRef/textColor/shadeColor/height + <hh:bold>/<hh:italic>/<hh:underline>/<hh:strikeout> 요소
  • 문단 모양 — 정렬(LEFT|RIGHT|CENTER|JUSTIFY|DISTRIBUTE), 좌/우 여백, 들여쓰기, 줄간격
  • 스타일 정의 — <hh:style> + paraPrIDRef/charPrIDRef
  • 번호 매기기 — 수준별 형식 문자열(예: ^1., ^1))
  • 글머리표 — 글머리 문자(●/○/■ 등)
  • 미리보기 — Preview/PrvText.txt 자동 생성 (한컴 호환 형식, 다른 뷰어/탐색기에서 썸네일/미리보기 지원)

한계: BorderFill 의 그라데이션/이미지 채우기와 도형의 사각형/타원/호/다각형/곡선 좌표·스타일은 종류만 표시합니다(직선만 좌표 보존). 차트·OLE·글맵시는 미지원이고, 머리말/꼬리말/각주는 본문 흐름에 평탄 출력되며 별도 master page 매핑은 향후 지원 예정입니다.

Markdown / HTML ↔ HWPX

import {
  hwpToMarkdown,
  markdownToHwpx,
  htmlToHwpx,
} from "hwp-convert";
import { readFile, writeFile } from "node:fs/promises";

// HWP → Markdown
const md = await hwpToMarkdown(new Uint8Array(await readFile("./input.hwp")));

// HWPX → Markdown
import HwpxReader from "hwp-convert";
const reader = new HwpxReader();
await reader.loadFromArrayBuffer(/* ArrayBuffer */);
const md2 = await reader.extractMarkdown({ embedImages: true });

// Markdown → HWPX (heading/bold/italic/list/table/image-data-URI)
const mdSrc = `# 제목\n\n**굵게** 그리고 *기울임*\n\n| A | B |\n| --- | --- |\n| 1 | 2 |`;
const hwpxFromMd = await markdownToHwpx(mdSrc, { title: "from-md", creator: "me" });
await writeFile("./from-md.hwpx", hwpxFromMd);

// HTML → HWPX (p/h1-6/strong/em/ul/ol/li/table/blockquote/pre/img-data-URI)
const html = `<h1>제목</h1><p>본문 <strong>강조</strong></p>`;
const hwpxFromHtml = await htmlToHwpx(html);
await writeFile("./from-html.hwpx", hwpxFromHtml);

이미지는 data: URI 인 경우만 BinData 로 임베드됩니다. 외부 URL/상대경로 이미지는 스킵 (런타임 fetch 가 필요해서 1차 포팅 범위 밖).

평문 → HWPX 작성

import { HwpxWriter } from "hwp-convert";
import { writeFile } from "node:fs/promises";

const writer = new HwpxWriter();
const bytes = await writer.createFromPlainText("첫 문단\n두 번째 문단", {
  title: "예시",
  creator: "홍길동",
});
await writeFile("./output.hwpx", bytes);

HwpxWriter 는 OWPML 패키지 규칙(첫 STORED mimetype, META-INF/container.xml/manifest.xml, OPF 매니페스트+spine, 최소 충실도의 header.xml 1세트)을 따르는 spec 호환 .hwpx 를 생성합니다. 한컴오피스 / LibreOffice 에서 그대로 열립니다.

오류 처리

import HwpxReader, {
  HwpxNotLoadedError,
  HwpxEncryptedDocumentError,
  InvalidHwpxFormatError,
  HwpEncryptedError,
  HwpUnsupportedError,
  HwpInvalidFormatError,
} from "hwp-convert";

try {
  const reader = new HwpxReader();
  await reader.loadFromArrayBuffer(buffer);
  await reader.extractText();
} catch (e) {
  if (e instanceof HwpxEncryptedDocumentError) {
    // 암호화 HWPX — 미지원
  } else if (e instanceof InvalidHwpxFormatError) {
    // 유효하지 않은 HWPX
  } else if (e instanceof HwpxNotLoadedError) {
    // loadFromArrayBuffer 미호출
  } else if (e instanceof HwpEncryptedError) {
    // 암호화 HWP — 미지원
  } else if (e instanceof HwpUnsupportedError) {
    // 배포용 ViewText / HWP 3.0 등
  } else if (e instanceof HwpInvalidFormatError) {
    // CFB 시그니처 아님
  } else {
    throw e;
  }
}

브라우저(ESM) 사용

dist/browser/hwp-convert.browser.mjs 는 모든 의존성을 인라인한 단일 ESM 번들입니다. package.jsonexports.browser 조건과 ./browser 서브패스로 매핑되어 있습니다.

번들러(Vite/webpack) 사용 시:

// 번들러가 browser 조건을 자동 선택
import { hwpToText, parseHwp } from "hwp-convert";

// 명시적으로 브라우저 빌드 지정
import { hwpToText } from "hwp-convert/browser";

<script type="module"> 직접 로드:

<script type="module">
  import { hwpToText, hwpToHwpx } from "https://cdn.jsdelivr.net/npm/hwp-convert/dist/browser/hwp-convert.browser.mjs";

  document.querySelector("#file").addEventListener("change", async (e) => {
    const file = e.target.files[0];
    const bytes = new Uint8Array(await file.arrayBuffer());
    const text = await hwpToText(bytes);
    document.querySelector("#out").textContent = text;
  });
</script>

CLI

설치 후 hwpconvert 또는 hwp-convert 명령으로 사용할 수 있습니다.

# HWPX 검사 / 추출
hwp-convert inspect document.hwpx
hwp-convert txt document.hwpx
hwp-convert html document.hwpx > out.html
hwp-convert md document.hwpx > out.md

# 그림 처리 (기본값이 명령마다 다릅니다: md=경로 참조, html=인라인)
hwp-convert md document.hwpx --embed-images > out.md      # base64 인라인으로 켜기
hwp-convert html document.hwpx --no-embed-images > out.html # 인라인 끄기(BinData 경로 참조)

# HWP 바이너리 (자동 라우팅: txt/md 명령은 .hwp 확장자 인식)
hwp-convert txt document.hwp
hwp-convert md document.hwp
hwp-convert hwp:txt document.hwp
hwp-convert hwp:md document.hwp > out.md
hwp-convert convert:hwp document.hwp converted.hwpx

# 작성 / 변환
hwp-convert write:txt notes.txt out.hwpx           # 평문 → HWPX
hwp-convert md:hwpx notes.md out.hwpx              # Markdown → HWPX
hwp-convert html:hwpx page.html out.hwpx           # HTML → HWPX

# 일괄 처리: HWPX → HTML
hwp-convert batch ./input ./output

# 템플릿 ({{key}} 치환)
hwp-convert html:tpl template.hwpx data.json > result.html
hwp-convert batch:tpl ./templates ./data ./output

아키텍처 개요

src/lib/ 아래 모듈 구성:

| 모듈 | 역할 | | --- | --- | | hwpxReader.ts | HWPX(ZIP+XML) 읽기 — 매니페스트/spine 으로 섹션 순서 결정, extractText/extractHtml/listImages | | writer.ts | 평문 → HWPX (HwpxWriter.createFromPlainText) | | errors.ts | HWPX 측 에러 클래스 (HwpxNotLoadedError 등) | | types.ts | HWPX 측 공개 타입 | | hwp/index.ts | HWP 바이너리 진입점 — detectFormat/parseHwp/hwpToText/hwpToHwpx | | hwp/cfbReader.ts | CFB(OLE2) 컨테이너 + raw deflate 압축 해제 (cfb + pako) | | hwp/fileHeader.ts | FileHeader 256B — 시그니처/버전/11종 플래그 | | hwp/record.ts | DocInfo/BodyText 공통 4바이트 레코드 헤더(tag/level/size + 확장 size) | | hwp/docInfo.ts | DocInfo 스트림 — FACE_NAME/CHAR_SHAPE/PARA_SHAPE/STYLE/BORDER_FILL/NUMBERING/BULLET/TAB_DEF/BIN_DATA | | hwp/bodyText.ts | BodyText 섹션 — PARA_HEADER/PARA_TEXT/PARA_CHAR_SHAPE 와 컨트롤 문자, 계층적 nested 문단 | | hwp/control.ts | 인라인 컨트롤 — 표(rowSpan/colSpan) / 그림(GSO+SHAPE_PICTURE) / 머리말·꼬리말·각주 / 필드 / 도형 / 수식(EQEDIT) | | hwp/binData.ts | 임베디드 바이너리(이미지/OLE) 추출 — OLE storage prefix 보정 | | hwp/converter.ts | HwpDocument IR → 텍스트 / HWPX 라우팅 | | hwp/hwpxBuilder.ts | HwpDocument → HWPX 패키지 합성 (header.xml refList 풀 출력, section.xml 실 ID 참조, BinData 패키징, PrvText 생성) | | hwp/types.ts | HWP IR 타입 (HwpDocument, HwpSection, HwpParagraph, HwpControl …) | | hwp/tags.ts | HWPTAG_* 상수 | | hwp/byteReader.ts | LE u8/u16/u32, signed, UTF-16, HWP 문자열, 바운드 체크 |

src/cli.ts 는 위 라이브러리를 얇게 감싼 CLI 진입점이고, scripts/build-browser.mjs 는 esbuild 로 브라우저 번들을 만듭니다.

개발

# 빌드 (tsc → dist/, esbuild → dist/browser/hwp-convert.browser.mjs)
npm run build

# 테스트 (vitest, 316 tests / 30 files: 단위 + 통합 + e2e + 견고성)
npm test
npm run test:watch

테스트 픽스처는 test/fixtures/ 또는 사용자의 ~/Documents/ 에서 자동 탐지합니다. 다음 파일들이 있으면 통합 테스트가 활성화됩니다:

  • 1.hwp, 1.hwpx — 폼 양식 (다중 셀 표)
  • 여름휴가 안내문.hwp — 이미지 임베드 (PNG + JPG)
  • 공고문(안)(26.4.24.).hwp — 표 셀 병합 (rowSpan 2/3/5)

알려진 제한

  • 한글 2014 VP for Mac: 생성된 HWPX 에서 그림 위치가 어긋납니다. 해당 편집기의 결함으로 확인했습니다 — 한컴오피스가 저장한 정품 HWPX 도 같은 편집기에서 동일하게 어긋나고, 같은 파일이 Mac 의 한글 뷰어에서는 정상 렌더됩니다. 그 이후 버전을 대상으로 합니다.
  • 암호화 HWP / 배포용 ViewText / HWP 3.0 미지원 — 명시적 에러 발생
  • 머리말/꼬리말/각주: 본문 흐름에 평탄 paragraph 로 출력 (별도 master page 매핑은 향후)
  • BorderFill 그라데이션/이미지 채우기 미보존 (단색 채우기·4면 테두리·대각선은 보존)
  • 번호 매기기 수준별 시작번호: 기본 1
  • 차트(CHART_DATA) / OLE / 글맵시: 미지원
  • 도형: line 의 좌표만 보존, 사각형/타원/호/다각형/곡선은 종류만 보존

구현 노트

  • HwpxReaderContents/content.hpf 의 manifest+spine 으로 섹션 순서를 결정하며, 실패 시 Contents/section*.xml 알파벳 순서로 폴백합니다.
  • 암호화 감지는 META-INF/manifest.xmlencrypt|cipher 마커에 의존합니다(휴리스틱). 복호화는 미지원입니다.
  • HWP 5.0 바이너리 파서는 rhwp (MIT) 의 구조를 TypeScript 로 포팅 중이며, 각 포팅 파일 헤더에 출처가 명시되어 있습니다.

라이센스

MIT. HWP 바이너리 파서 부분은 rhwp (MIT, Copyright (c) 2025-2026 Edward Kim) 의 코드 구조를 TypeScript 로 포팅한 것이며 동일 MIT 라이센스에서 재배포됩니다. 자세한 내용은 LICENSE 와 각 포팅 파일의 헤더 주석을 참조해 주세요.