# rhwp: a Rust and WebAssembly HWP viewer and editor for Korean documents

> rhwp is an MIT-licensed HWP 5.0, HWPX and HML viewer and editor written in Rust and compiled to WebAssembly. It targets anyone who has to open a Korean Hangul document without installing Hangul Office, and the README is candid about which features are still restricted.

**edwardkim/rhwp** — 아래한글 hwp viewer and editor by rust and wasm

- Repository: https://github.com/edwardkim/rhwp
- Stars: 3,872 · Forks: 743
- Language: Rust
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/edwardkim-rhwp

## The problem rhwp addresses: HWP files without Hangul Office

HWP is the binary document format used by Hangul (한글), the Korean word processor from Hancom. HWP 5.0 files are OLE2 compound documents, and HWPX is the Open XML-based successor. Both are awkward to open outside the vendor's own software, which is the gap rhwp is built to fill. The README states the goal plainly: open HWP/HWPX files and supported HML documents anywhere, free, without installation.

The audience is narrow and specific. A developer who receives a .hwp attachment and wants to extract its text in a script. A team that needs to render a Korean document inside a web application. A VS Code user who wants to preview a document without leaving the editor. rhwp ships as a Rust library first (the Cargo.toml comment says the rlib is the primary crate type and the browser cdylib comes second), with a CLI, a WASM build, browser extensions for Chrome, Edge and Firefox, and a VS Code extension. That is four distribution surfaces for one parsing and rendering core.

## How the parsing and rendering pipeline is structured

The README describes a layered pipeline rather than a single monolithic renderer. Parsing handles HWP 5.0 binary, HWPX, and HML (HWPML 2.9/2.91). The parsed document then goes through pagination, which covers multi-column splitting, table row-level page splitting, and vpos-based paragraph position correction. From there the output stage produces SVG, PNG, PDF, or Canvas output.

The interesting architectural choice is the shared paint IR. Both the Rust side (DocumentCore::build_page_layer_tree) and the WASM side (getPageLayerTree) emit a PageRenderTree that is converted into a PageLayerTree, described in the README as schemaVersion: 1, with the rule that compatible changes stay additive. Four backends consume that tree: legacy and layered SVG, Canvas2D, direct CanvasKit replay, and native Skia for PNG and direct PDF behind the native-skia feature flag.

Renderer selection is not free-form. The README says Studio, browser extensions, embeds and the VS Code viewer default to Canvas2D, which is the compatibility path. CanvasKit is only pinned when a caller asks for ?renderer=auto and a bounded document preflight is complete and eligible and the required document fonts can be prepared. Certain constructs force the whole document revision back to Canvas2D: complex shaping characters without direction and cluster advance authority, old Hangul or boxed-PUA combined with width scaling or paint effects, vertical or rotated special visual operations, and structural control codes such as [표] and [그림] when showControlCodes is on. The renderer is a per-document decision, not a per-element one, and that is a deliberate safety trade-off: one unsupported glyph can cost the whole document the faster path.

## Installing rhwp and opening a document from the CLI

The primary distribution channel is the Rust crate itself. The Cargo.toml declares the package as rhwp, version 0.8.6, MIT licensed, with two binaries: rhwp and font-metric-gen. The README also links an npm package at @rhwp/core for the WASM build, plus the Chrome Web Store, Edge Add-ons and Firefox Add-ons listings and the VS Code Marketplace extension. The repository has no Homepage field in its metadata, so the README's demo link on GitHub Pages is the reference web build.

For a local build the repository ships a Dockerfile and a docker-compose.yml. The Dockerfile pins wasm-pack at 0.15.0 and adds the wasm32-unknown-unknown target, and the compose file defines three services: dev, test and wasm. The dev service runs cargo build in the mounted working directory, the test service runs cargo test, and the wasm service sets CARGO_TARGET_DIR to /build-target and runs scripts/wasm-pack-locked.sh with --target web, then chowns the pkg directory back to the host UID and GID.

```yaml
services:
  dev:
    build:
      context: .
      args:
        UID: ${UID:-1000}
        GID: ${GID:-1000}
    env_file: .env.docker
    working_dir: /app
    command: cargo build
```

That is the dev service as it appears in docker-compose.yml: it builds from the repository root, reads .env.docker, and its default command is cargo build. The test and wasm services repeat the same build context and env_file, with cargo test and the wasm-pack invocation as their commands. After the wasm service finishes, the pkg directory on the host holds the WASM output, with the -opt.wasm files removed before the pack step. The compose file notes that Docker Desktop bind mounts on Windows do not guarantee the hard links Cargo uses, which is why the build cache lives in a named volume and only pkg is exposed to the host.

For export, the README lists SVG export through the CLI, PNG export with the native-skia feature, and PDF export with two paths: an SVG compatibility mode that supports --text-as-paths and byte reproducibility, and a native Skia direct mode selected with --features native-skia and --backend direct. The direct PDF path uses a print profile and a CSS px to PDF point conversion of 72/96; the README states that gradients, patterns, shadows, connectors and image adjustments are lossy there and that the tool fails and points you at the SVG backend instead. Only Raw SVG falls back to bounded rasterization via --raster-dpi.

## Where rhwp will not do what you expect

HML support is explicitly partial. The README says only HWPML 2.9/2.91 structures confirmed against a real corpus are supported. Equations within that supported range can be imported and edited, and an HML source with no unpreservable elements can be saved back to HML after a preflight check. Unsupported elements such as pictures and embedded or external resources produce a warning, and lossy saves are blocked rather than silently performed. If your workflow depends on round-tripping HML documents that contain images, rhwp is the wrong tool today.

The direct PDF backend is the second sharp edge. Because gradients, patterns, shadows, connectors and image adjustments are not representable in that path, the tool refuses and tells you to use SVG. That is a good failure mode, but it means a pipeline that assumes one PDF command works for every document will break on exactly the documents with the richest formatting.

The renderer selection logic is the third constraint worth understanding before adoption. A document that contains complex shaping, old Hangul with width scaling or paint effects, or structural control codes under showControlCodes will be pinned to Canvas2D for its entire revision. You cannot opt individual elements into the faster CanvasKit path. The README also notes that the WASM crate type is deliberately second in the Cargo.toml list, because Dioxus CLI classifies the first crate type when handling hot-patch arguments; reversing the order silently breaks --hot-patch. That is a build-tooling coupling that will surprise anyone who edits the manifest without reading the comment.

## How rhwp differs from converting HWP through LibreOffice or Hancom

The obvious alternative is Hancom Office itself, which owns the format and will render anything rhwp might miss. The difference is deployment: Hancom Office is desktop software, while rhwp compiles to WebAssembly and runs in a browser tab, a browser extension, a VS Code panel, or a Rust program. If your requirement is opening a document inside a web application or extracting text programmatically, Hancom Office is not a component you can embed.

LibreOffice with the Hangul filter is the other common route. It is a general office suite that reads HWP as one of many import formats, and its output is a converted document rather than a rendered one. rhwp instead keeps the document model and exposes it: the README describes a hwpctl-compatible API layer with 30 Actions (TableCreate, InsertText, CharShape, Para and others) plus a Field API, aimed at compatibility with Hancom's web-based document editing interface. That is a different product category from a batch converter. If you need to script document edits against an API that mirrors what Hancom's web editor exposes, rhwp is targeting that surface; a suite-level importer is not.

The trade-off is maturity. rhwp is at v0.8.6 with a stated v1.0 goal of systematizing the typesetting engine, and the README describes v2.0 as a collaboration foundation with more than 40 external contributors and two co-maintainers. A general office suite has years of accumulated HWP edge cases behind it. rhwp's answer is a large test surface (the README claims 3,400+ Rust tests plus studio unit, e2e and visual regression CI), which is a different kind of assurance than field exposure.

## Maintenance, licensing and the cost of upgrading

The repository is not archived, and the last push was on 2026-09-23. Recent releases run from v0.8.3 on 2026-08-11 through v0.8.4 on 2026-08-12 to v0.8.6 on 2026-09-02, with release notes naming compatibility, typesetting, agent and distribution reliability work. That cadence means pinning a version is worthwhile: the CHANGELOG.md is the place the project records per-cycle changes including contributor lists, and the README points there rather than duplicating detail.

The licence is MIT, which is permissive and imposes no copyleft obligation on your own code. The repository also carries a THIRD_PARTY_LICENSES.md, which is where you should look for the terms of bundled dependencies and any font or corpus material, since MIT covers rhwp itself and not necessarily everything it links or ships. CONTRIBUTING.md would be the file to check before sending patches.

The upgrade cost is concentrated in two places. First, the renderer contract: the README states that compatible changes to the layer tree schema are additive, which suggests you can rely on schemaVersion: 1 fields not disappearing, but you should still diff the schema section of the README between versions. Second, the pinned toolchain: the Dockerfile fixes wasm-pack at 0.15.0 and the README badge lists Rust 1.93.1, so a CI pipeline that floats either will drift from what the project tests against. The Cargo.toml comment about crate-type ordering is a third trap for anyone who forks the manifest.

## Conclusion

Adopt rhwp if you need to read HWP or HWPX files in a browser, in VS Code, or from a Rust program, and if you can accept the HML import restrictions and a v0.8.x API surface. Do not adopt it as a drop-in replacement for Hangul Office document production, and do not assume every HWP feature round-trips. Verify first that your specific corpus opens correctly: check the preflight behaviour on HML sources that contain pictures or external resources, confirm which renderer path your deployment uses, and read the CHANGELOG entry for the version you pin.

## FAQ

### How can I read an HWP file with rhwp?

rhwp opens HWP 5.0 binary files (OLE2 compound documents), HWPX, and supported HML documents. The README lists an online demo on GitHub Pages, a VS Code extension, and browser extensions for Chrome, Edge and Firefox, plus a Rust CLI and an npm package at @rhwp/core for the WASM build.

### Does rhwp support editing HWP documents, or only viewing them?

The README describes both. The web editor supports text editing with insert, delete and undo/redo, character and paragraph formatting dialogs, table creation and row/column insert and delete, and cell formulas. Saving covers HWP edit save, HWPX and HML semantic-preserving save, and an HWPX to HWP conversion path.

### Can rhwp convert an HWP file to PDF?

The README lists PDF export in two modes: an SVG compatibility path that supports --text-as-paths and byte reproducibility, and a native Skia direct path selected with --features native-skia and --backend direct. The direct path refuses documents containing gradients, patterns, shadows, connectors or image adjustments and directs you to the SVG backend.

### Which renderer does rhwp use by default in the browser?

The README states that Studio, browser extensions, embeds and the VS Code viewer default to Canvas2D, which is the compatibility path. CanvasKit is only pinned when ?renderer=auto is requested and a bounded document preflight is complete and eligible and the required fonts can be prepared.

## Sources

- [edwardkim/rhwp on GitHub](https://github.com/edwardkim/rhwp)
- [Issues](https://github.com/edwardkim/rhwp/issues)
- [License: MIT](https://github.com/edwardkim/rhwp/blob/main/LICENSE)
- [README](https://github.com/edwardkim/rhwp/blob/main/README.md)
- [Releases](https://github.com/edwardkim/rhwp/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/edwardkim-rhwp
