Python bindings for native rhwp HWP to PDF conversion
Project description
hwp-ingest
Rust-backed Python package for Korean HWP/HWPX ingestion.
Convert HWP documents to PDF or page-level SVG artifacts through a typed Python facade.
hwp-ingestuses a vendoredrhwpsubset as its current HWP parsing, layout, and rendering backend. The vendored backend can carry hwp-ingest-local compatibility patches, while product-facing conversion policy stays in the hwp-ingest Rust/Python layers.
Features
- Convert HWP files to PDF bytes or PDF files.
- Render page-level SVG artifacts for inspection and downstream pipelines.
- Read basic document metadata such as renderable page count.
- Keep conversion orchestration in Rust while Python handles paths, overwrite policy, and typed facade ergonomics.
Install from PyPI:
uv pip install hwp-ingest
# or
python -m pip install hwp-ingest
Runtime PDF dependency
PDF conversion requires the rsvg-convert executable from librsvg at runtime.
The Python wheel does not bundle this executable.
SVG output does not require rsvg-convert; that executable is only needed for PDF conversion.
Check availability:
rsvg-convert --version
Install examples:
# Debian/Ubuntu
sudo apt-get install librsvg2-bin
# Fedora/RHEL
sudo dnf install librsvg2-tools
# Arch Linux
sudo pacman -S librsvg
# macOS
brew install librsvg
On Windows, install librsvg/rsvg-convert and ensure rsvg-convert.exe is on
PATH. If rsvg-convert is missing, PDF conversion raises
hwp_ingest.HwpIngestError with a clear message. svg2pdf is not used as an
automatic fallback because it is known to drop clipped table text in some HWP
documents.
Font fallback and PUA glyphs
hwp-ingest does not bundle Hancom or Microsoft fonts. PDF conversion asks
rsvg-convert/fontconfig to resolve the SVG font-family chain from fonts
installed on the system. If a document contains visible Private Use Area (PUA)
characters, such as Hancom symbol slots, a generic Unicode font is not enough:
PUA codepoints do not define a standard glyph shape. They render correctly only
when the owning document font is installed, or when the project adds a
source-backed codepoint mapping for that specific symbol set.
The current renderer preserves the document font name first, then appends Korean-compatible fallback families:
| Text family in the document | Fallback order |
|---|---|
| Batang/Myeongjo/serif text | Document font → Batang/바탕 → Nanum Myeongjo → AppleMyungjo → Noto Serif KR / Noto Serif CJK KR → HCR Batang Ext-B / 함초롬바탕 확장B → HCR Batang Ext / 함초롬바탕 확장 → HCR Batang / 함초롬바탕 → Source Han Serif K Old Hangul → serif |
| Dotum/Gothic/Gulim/sans-serif text | Document font → Malgun Gothic / 맑은 고딕 → Apple SD Gothic Neo → Noto Sans KR ExtraLight → Noto Sans KR → Pretendard → HCR Batang Ext-B / 함초롬바탕 확장B → HCR Batang Ext / 함초롬바탕 확장 → HCR Batang / 함초롬바탕 → Source Han Serif K Old Hangul → sans-serif |
For Hancom-origin documents with PUA symbols, install the matching licensed font on the machine running conversion:
| Document/font origin | Families to check first |
|---|---|
| Hancom/HCR body or PUA symbols | HCR Batang / 함초롬바탕, HCR Dotum / 함초롬돋움 |
| Windows-origin documents | Batang, Dotum, Gulim, Gungsuh, Malgun Gothic |
| Older HY/Hanyang documents | HY울릉도L, HY헤드라인M, HY견고딕, HY그래픽, HY견명조, HY신명조 |
Linux/fontconfig check:
fc-match "HCR Batang"
fc-match "함초롬바탕"
fc-match "HCR Dotum"
If these commands resolve to a fallback like Noto or DejaVu instead of the expected Hancom/HCR family, install the licensed TTF/OTF files into a system or user font directory and refresh fontconfig:
# user-local example
mkdir -p ~/.local/share/fonts/hancom
cp HCRBatang.ttf HCRBatang-Bold.ttf ~/.local/share/fonts/hancom/
fc-cache -f ~/.local/share/fonts/hancom
Do not commit restricted font files into this repository.
Quick start
Convert an HWP file to a PDF next to the input file:
from pathlib import Path
import hwp_ingest
input_path = Path("document.hwp")
output_path = hwp_ingest.to_pdf_file(input_path, overwrite=True)
print(output_path)
Convert one zero-based page to bytes:
from pathlib import Path
import hwp_ingest
pdf_bytes = hwp_ingest.to_pdf_bytes(
Path("document.hwp").read_bytes(),
page_index=0,
)
Path("page-1.pdf").write_bytes(pdf_bytes)
Render SVG page artifacts without the PDF backend:
from pathlib import Path
import hwp_ingest
svg_pages = hwp_ingest.to_svg_pages(Path("document.hwp").read_bytes())
Path("page-1.svg").write_bytes(svg_pages[0])
Read basic document metadata:
from pathlib import Path
import hwp_ingest
info = hwp_ingest.analyze_bytes(Path("document.hwp").read_bytes())
print(info.page_count)
Python API
The package is typed and ships py.typed plus checked-in .pyi stubs.
hwp_ingest.analyze_bytes(data: bytes) -> hwp_ingest.DocumentInfo
Returns a DocumentInfo object. DocumentInfo.page_count is the number of
renderable pages discovered in the HWP bytes.
hwp_ingest.to_pdf_bytes(
data: bytes,
*,
page_index: int | None = None,
) -> bytes
Returns PDF bytes. page_index is zero-based. Pass None to convert the full
document.
hwp_ingest.to_svg_pages(
data: bytes,
*,
page_index: int | None = None,
) -> list[bytes]
Returns one SVG document per selected page as bytes. page_index is
zero-based. Pass None to render all pages in document order. SVG output does
not require rsvg-convert.
hwp_ingest.to_pdf_file(
input_path: str | os.PathLike[str],
output_path: str | os.PathLike[str] | None = None,
*,
page_index: int | None = None,
overwrite: bool = False,
) -> pathlib.Path
Writes a PDF and returns the output path. If output_path is None, the output
uses the input path with a .pdf suffix. Existing output files are protected
unless overwrite=True.
hwp_ingest.to_svg_files(
input_path: str | os.PathLike[str],
output_dir: str | os.PathLike[str] | None = None,
*,
page_index: int | None = None,
overwrite: bool = False,
) -> list[pathlib.Path]
Writes one SVG file per selected page and returns paths in page order. If
output_dir is None, SVG files are written beside the input file. Generated
filenames are stable and use one-based visible page numbers:
<stem>.page-0001.svg, <stem>.page-0002.svg, and so on. Existing output
files are protected unless overwrite=True.
Errors
ValueError: negativepage_index, empty documents, or out-of-range pages.FileExistsError: output exists andoverwrite=False.FileNotFoundError: input file path does not exist.hwp_ingest.HwpIngestError: parse, render, PDF backend, or output write failures.
Local development
cargo test --workspace
uv run maturin develop
uv run pytest
Build distributable artifacts locally:
uv run maturin sdist --out dist
uv run --with ziglang maturin build --release --locked --out dist --compatibility pypi --zig
Notices
This project includes vendored portions of rhwp.
본 제품은 한글과컴퓨터의 HWP 문서 파일(.hwp) 공개 문서를 참고하여 개발하였습니다.
"한글", "한컴", "HWP", and "HWPX" are registered trademarks of Hancom Inc.
hwp-ingest is an independent open-source project and is not affiliated with,
sponsored by, or endorsed by Hancom Inc.
See NOTICE, THIRD_PARTY_LICENSES.md, and vendor/rhwp-subset/NOTICE for
vendored source attribution and license details.
Project direction
See docs/rhwp-python-binding-plan.md for batch engine, semantic output, vendor/upstream sync, and production pipeline direction.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hwp_ingest-0.1.0.tar.gz.
File metadata
- Download URL: hwp_ingest-0.1.0.tar.gz
- Upload date:
- Size: 2.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45bc42693315ea26bea38c2333affe42436a4a25ce6e97ea4bdba6cb8073e472
|
|
| MD5 |
f704b89d4971216d99dd274ebe2ae868
|
|
| BLAKE2b-256 |
2e20936b0db9c8fe953fc12c3ad6ea2a765775e95dd9927c384e2a57bddabeee
|
Provenance
The following attestation bundles were made for hwp_ingest-0.1.0.tar.gz:
Publisher:
publish.yml on iosif2/hwp-ingest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hwp_ingest-0.1.0.tar.gz -
Subject digest:
45bc42693315ea26bea38c2333affe42436a4a25ce6e97ea4bdba6cb8073e472 - Sigstore transparency entry: 2084849116
- Sigstore integration time:
-
Permalink:
iosif2/hwp-ingest@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/iosif2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Trigger Event:
push
-
Statement type:
File details
Details for the file hwp_ingest-0.1.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: hwp_ingest-0.1.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 2.8 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e22cc6972ff0bc2e43b154ca5c7ad631c6708f549525ba1ec13629cb752aad47
|
|
| MD5 |
c3084d3958bbc6a1fc64a460284456d0
|
|
| BLAKE2b-256 |
0bb5b40dc8201ae95ef06dbf03862ccf35d4c4b801a26da7e4694dbb4cf6192d
|
Provenance
The following attestation bundles were made for hwp_ingest-0.1.0-cp310-abi3-win_amd64.whl:
Publisher:
publish.yml on iosif2/hwp-ingest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hwp_ingest-0.1.0-cp310-abi3-win_amd64.whl -
Subject digest:
e22cc6972ff0bc2e43b154ca5c7ad631c6708f549525ba1ec13629cb752aad47 - Sigstore transparency entry: 2084849152
- Sigstore integration time:
-
Permalink:
iosif2/hwp-ingest@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/iosif2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Trigger Event:
push
-
Statement type:
File details
Details for the file hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 3.0 MB
- Tags: CPython 3.10+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17a34e3edc5c9e3222f90b1bd62a352545b3cfada00682de2ae1870bb1f955ca
|
|
| MD5 |
5eacb1f91c665f636330d30e409e4b42
|
|
| BLAKE2b-256 |
64f90d94135281ae533e29129fd2a65766183890f4ab3bb194eaee48d4c8de77
|
Provenance
The following attestation bundles were made for hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl:
Publisher:
publish.yml on iosif2/hwp-ingest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_x86_64.whl -
Subject digest:
17a34e3edc5c9e3222f90b1bd62a352545b3cfada00682de2ae1870bb1f955ca - Sigstore transparency entry: 2084849132
- Sigstore integration time:
-
Permalink:
iosif2/hwp-ingest@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/iosif2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Trigger Event:
push
-
Statement type:
File details
Details for the file hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 2.7 MB
- Tags: CPython 3.10+, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5cb41d6b94e6b8505be14c69e3ff90cb2f256c8e7abde3affef6b9e56247f970
|
|
| MD5 |
1942c5d565ad505a04d68c83f5578861
|
|
| BLAKE2b-256 |
8bc44394c880f5b53ec71c3e00b62698765df31615e5fd467b6f25ef6ed04914
|
Provenance
The following attestation bundles were made for hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl:
Publisher:
publish.yml on iosif2/hwp-ingest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hwp_ingest-0.1.0-cp310-abi3-manylinux_2_28_aarch64.whl -
Subject digest:
5cb41d6b94e6b8505be14c69e3ff90cb2f256c8e7abde3affef6b9e56247f970 - Sigstore transparency entry: 2084849124
- Sigstore integration time:
-
Permalink:
iosif2/hwp-ingest@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/iosif2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Trigger Event:
push
-
Statement type:
File details
Details for the file hwp_ingest-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: hwp_ingest-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.6 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
808acd7adf335063124ba6ddf05d46a10fe3dc2d08388548650b39a8b218247f
|
|
| MD5 |
282b03324c71ff8d636afee6a8e3c884
|
|
| BLAKE2b-256 |
187e1bea56196fbe32f38ff04694434fda315d02e3fd4c35b5ec04448282a635
|
Provenance
The following attestation bundles were made for hwp_ingest-0.1.0-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
publish.yml on iosif2/hwp-ingest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hwp_ingest-0.1.0-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
808acd7adf335063124ba6ddf05d46a10fe3dc2d08388548650b39a8b218247f - Sigstore transparency entry: 2084849159
- Sigstore integration time:
-
Permalink:
iosif2/hwp-ingest@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/iosif2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Trigger Event:
push
-
Statement type:
File details
Details for the file hwp_ingest-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: hwp_ingest-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.7 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a87df9f6656962600a4ea4bbc3613a3d9b715c51e70bd9af29e5ae33fc816a6e
|
|
| MD5 |
495a15df28fb892fde397282a2853311
|
|
| BLAKE2b-256 |
6183930cef2ada9e775de611d398b2a5968ddcce93ca385b5d2824691fd53e78
|
Provenance
The following attestation bundles were made for hwp_ingest-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl:
Publisher:
publish.yml on iosif2/hwp-ingest
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hwp_ingest-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl -
Subject digest:
a87df9f6656962600a4ea4bbc3613a3d9b715c51e70bd9af29e5ae33fc816a6e - Sigstore transparency entry: 2084849140
- Sigstore integration time:
-
Permalink:
iosif2/hwp-ingest@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/iosif2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4b85cb252bc814645e124f6eae814e2f5ef1753c -
Trigger Event:
push
-
Statement type: