hwpforge-foundation 0.16.6

Foundation types for HwpForge (HwpUnit, Color, Interned IDs, Branded Index)
Documentation

HwpForge ๐Ÿ”ฅ

Rust๋กœ ํ•œ๊ธ€(HWP/HWPX) ๋ฌธ์„œ๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ œ์–ด

Hancom ํ•œ๊ธ€ ํŒŒ์ผ ์ฝ๊ธฐ, ์“ฐ๊ธฐ, ๋ณ€ํ™˜

CI codecov Tests unsafe: 0 blocks Lines of Code

crates.io docs.rs crates.io downloads MSRV License: MIT OR Apache-2.0

MCP Ready GitHub release GitHub last commit GitHub stars

Security Policy Contributing PRs Welcome Made in Korea Buy Me a Coffee


HwpForge๋ž€?

HwpForge๋Š” HWPX ๋ฌธ์„œ(ZIP + XML, KS X 6101)๋ฅผ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ์˜คํ”ˆ์†Œ์Šค ์ˆœ์ˆ˜ Rust ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์›Œ๋“œํ”„๋กœ์„ธ์„œ์ธ Hancom ํ•œ๊ธ€์˜ ์ตœ์‹  ํฌ๋งท์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์ง€์› ๋ฒ„์ „

ํ•œ๊ธ€ ๋ฒ„์ „ ํฌ๋งท ์ฝ๊ธฐ ์“ฐ๊ธฐ ์Šคํƒ€์ผ ์„ธํŠธ
ํ•œ๊ธ€ 2014 ~ 2020 HWPX (.hwpx) โœ… โœ… Classic (18 styles)
ํ•œ๊ธ€ 2022 ~ 2024 HWPX (.hwpx) โœ… โœ… Modern (22 styles, ๊ธฐ๋ณธ๊ฐ’)
ํ•œ๊ธ€ 2025+ HWPX (.hwpx) โœ… โœ… Latest (23 styles)
ํ•œ๊ธ€ 97 ~ 2010 HWP5 (.hwp) โœ… โ€” โ€”
  • HWPX: OWPML ๊ตญ๊ฐ€ํ‘œ์ค€ (KS X 6101) ๊ธฐ๋ฐ˜, ZIP + XML ์ปจํ…Œ์ด๋„ˆ
  • HWP5: ๊ตฌํ˜• ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท. ํ˜„์žฌ๋Š” CLI ์ค‘์‹ฌ์˜ ์ฝ๊ธฐ/์ ๊ฒ€/์žฌ์ถœ๋ ฅ ๊ฒฝ๋กœ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค (convert-hwp5, audit-hwp5, census-hwp5)
  • ์Šคํƒ€์ผ ์„ธํŠธ๋Š” HancomStyleSet enum์œผ๋กœ ์„ ํƒ ๊ฐ€๋Šฅ (๊ธฐ๋ณธ: Modern)

LLM-first ์„ค๊ณ„ ๐Ÿ”ฅ โ€” AI ์นœํ™”์ ์ธ Markdown๊ณผ ๊ณต์‹ ํ•œ๊ธ€ ๋ฌธ์„œ ํฌ๋งท(HWPX), ๋‘ ์„ธ๊ณ„๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ž‡์Šต๋‹ˆ๋‹ค. LLM์ด Markdown์œผ๋กœ ์ž‘์„ฑํ•œ ๋‚ด์šฉ์€ ๊ณต๋ฌธ์„œ ๊ทœ๊ฒฉ์˜ HWPX๋กœ ์ปดํŒŒ์ผ๋˜๊ณ  ๐Ÿ“œ, ๋ฐ˜๋Œ€๋กœ ๊ธฐ์กด HWPX ๋ฌธ์„œ๋Š” AI๊ฐ€ ์‰ฝ๊ฒŒ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ๋กœ ๊บผ๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค โš’๏ธ.

  • ๐Ÿ“„ HWPX ์™„์ „ ๊ฐ€์ด๋“œ ๋‹ค์šด๋กœ๋“œ โ€” HwpForge API๋กœ ์ƒ์„ฑํ•œ 4์„น์…˜ ๋ฐ๋ชจ ๋ฌธ์„œ (ํ•œ๊ธ€์—์„œ ์—ด์–ด๋ณด์„ธ์š”)
  • HWPX Reader for AI โ€” ๊ธฐ์กด ํ•œ๊ธ€ ๋ฌธ์„œ(.hwpx)๋ฅผ Markdown์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ LLM์ด ์ฆ‰์‹œ ์ดํ•ด ๊ฐ€๋Šฅ
  • Full HWPX codec โ€” HWPX ํŒŒ์ผ์„ ์†์‹ค ์—†์ด ๋””์ฝ”๋”ฉ/์ธ์ฝ”๋”ฉ (lossless roundtrip)
  • Markdown bridge โ€” GFM Markdown๊ณผ HWPX ๊ฐ„ ์–‘๋ฐฉํ–ฅ ๋ณ€ํ™˜ (์ฝ๊ธฐ + ์“ฐ๊ธฐ)
  • YAML style template โ€” Figma Design Token์ฒ˜๋Ÿผ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์Šคํƒ€์ผ ์ •์˜ (ํฐํŠธ, ํฌ๊ธฐ, ์ƒ‰์ƒ)
  • Type-safe API โ€” branded index, typestate validation, zero unsafe code

๋น ๋ฅธ ์‹œ์ž‘

์„ค์น˜

# Cargo.toml์— ์ถ”๊ฐ€
cargo add hwpforge

# Markdown ์ง€์› ํฌํ•จ
cargo add hwpforge --features full

๋˜๋Š” Cargo.toml์— ์ง์ ‘ ์ถ”๊ฐ€:

[dependencies]
hwpforge = "0.16"

๐Ÿ”จ Hammer โ€” CLI๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

CLI ๋„๊ตฌ hwpforge(Hammer)๋ฅผ ์„ค์น˜ํ•˜๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ๋ฐ”๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. hwpforge-bindings-cli๋Š” crates.io์— ๋ฐฐํฌ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ(publish = false), git ๋˜๋Š” ๋กœ์ปฌ ๊ฒฝ๋กœ์—์„œ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. hwpforge-smithy-pdf(krilla) ์˜์กด์œผ๋กœ ์›Œํฌ์ŠคํŽ˜์ด์Šค MSRV(1.88)๋ณด๋‹ค ๋†’์€ Rust 1.92+๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

cargo install --git https://github.com/ai-screams/HwpForge hwpforge-bindings-cli

# ๋˜๋Š” clone ํ›„ ๋กœ์ปฌ ๊ฒฝ๋กœ์—์„œ
git clone https://github.com/ai-screams/HwpForge && cd HwpForge
cargo install --path crates/hwpforge-bindings-cli
# Markdown โ†’ HWPX ๋ณ€ํ™˜
hwpforge convert report.md -o report.hwpx

# HWPX ๊ตฌ์กฐ ํ™•์ธ
hwpforge inspect report.hwpx

# ๋ฌธ์„œ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋งต ํ™•์ธ (์ œ๋ชฉยทํ‘œยทํ•„๋“œยท์ฑ…๊ฐˆํ”ผ)
hwpforge outline report.hwpx

# HWPX โ†’ Markdown ๋ณ€ํ™˜ (AI๊ฐ€ ํ•œ๊ธ€ ๋ฌธ์„œ ์ฝ๊ธฐ)
hwpforge to-md report.hwpx -o report.out.md

# HWPX โ†’ JSON ์ถ”์ถœ (AI ํŽธ์ง‘์šฉ) โ€” -o ๋Š” ํ•„์ˆ˜, stdout ๋‚ด๋ณด๋‚ด๊ธฐ๋Š” ์—†์Šต๋‹ˆ๋‹ค
hwpforge to-json report.hwpx --section 0 -o section0.json   # ์„น์…˜ ํ•˜๋‚˜ (patch ์šฉ)
hwpforge to-json report.hwpx -o full.json                   # ๋ฌธ์„œ ์ „์ฒด (from-json ์šฉ)

# ์„น์…˜ JSON์„ ์›๋ณธ์— ๋˜์“ฐ๊ธฐ (ํ…์ŠคํŠธ๋งŒ ๋ฐ”๋€œ, ์›๋ณธ์ด base)
hwpforge patch report.hwpx --section 0 section0.json -o updated.hwpx

# ๋ฌธ์„œ ์ „์ฒด JSON์œผ๋กœ ์ƒˆ ๋ฌธ์„œ ๋งŒ๋“ค๊ธฐ (๊ตฌ์กฐ ๋ณ€๊ฒฝ๊นŒ์ง€ ๊ฐ€๋Šฅ)
hwpforge from-json full.json -o new.hwpx

# ๋ฌธ์„œ ๊ตฌ์กฐ ๊ฒ€์ฆ (Core ๋ถˆ๋ณ€์กฐ๊ฑด ํ†ต๊ณผ ์—ฌ๋ถ€)
hwpforge validate report.hwpx

# JSON Schema ์ถœ๋ ฅ (AI agent์šฉ)
hwpforge schema document

๋‘ JSON์€ ์„œ๋กœ ๋‹ค๋ฅธ ํ˜•์‹์ž…๋‹ˆ๋‹ค. --section ์—†์ด ๋ฝ‘์€ ๋ฌธ์„œ ์ „์ฒด JSON์€ from-json์ด ์ฝ๊ณ , --section N์œผ๋กœ ๋ฝ‘์€ ์„น์…˜ JSON์€ patch๊ฐ€ ์›๋ณธ ๋ฌธ์„œ๋ฅผ base๋กœ ์‚ผ์•„ ๋˜์”๋‹ˆ๋‹ค. ์„œ๋กœ ๋ฐ”๊ฟ” ๋„ฃ์œผ๋ฉด ์Šคํ‚ค๋งˆ ๋ถˆ์ผ์น˜๋กœ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค.

๋ˆ„๋ฆ„ํ‹€ ์ฑ„์šฐ๊ธฐ๋Š” ๋ˆ„๋ฆ„ํ‹€(click-here field)์ด ๋“ค์–ด ์žˆ๋Š” ์„œ์‹ ๋ฌธ์„œ์—์„œ๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๋ฐฉ๊ธˆ convert๋กœ ๋งŒ๋“  ๋ฌธ์„œ์—๋Š” ๋ˆ„๋ฆ„ํ‹€์ด ์—†์–ด FIELD_NOT_FOUND๊ฐ€ ๋‚ฉ๋‹ˆ๋‹ค โ€” fields๋กœ ์ด๋ฆ„์„ ๋จผ์ € ํ™•์ธํ•˜์„ธ์š”.

hwpforge fields form.hwpx                                    # ์ฑ„์šธ ์ˆ˜ ์žˆ๋Š” ํ•„๋“œ ์ด๋ฆ„
hwpforge fill form.hwpx --set ํšŒ์‚ฌ๋ช…=HwpForge -o filled.hwpx   # ๋‚˜๋จธ์ง€ ํŒจํ‚ค์ง€๋Š” ๋ฐ”์ดํŠธ ๊ทธ๋Œ€๋กœ

PDF ๋‚ด๋ณด๋‚ด๊ธฐ๋Š” ๋ฌธ์„œ์— ๋“ค์–ด ์žˆ๋Š” ์กฐํŒ ์บ์‹œ๋ฅผ ์žฌ์ƒํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ, ์บ์‹œ๊ฐ€ ์žˆ๋Š” ๋ฌธ์„œ๋งŒ ๋ Œ๋”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์บ์‹œ์˜ ์ถœ์ฒ˜๋Š” ๋‘˜์ž…๋‹ˆ๋‹ค โ€” ํ•œ์ปด์ด ์ €์žฅํ•œ HWPX, ๊ทธ๋ฆฌ๊ณ  HWP5์—์„œ ์บ์‹œ๋ฅผ ์‹ค์–ด ๋ณ€ํ™˜ํ•œ HWPX(convert-hwp5 --carry-layout-cache). to-pdf๋Š” .hwp๋ฅผ ์ง์ ‘ ๋ฐ›์•„ ๊ทธ ๋ณ€ํ™˜์„ ๋Œ€์‹ ํ•ด ์ฃผ๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด convert๋‚˜ from-json์ด ์ƒˆ๋กœ ๋งŒ๋“  ๋ฌธ์„œ์—๋Š” ์บ์‹œ๊ฐ€ ์—†์–ด PDF_RENDER_FAILED๋กœ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๊ฐ€ ์“ฐ๋Š” ํฐํŠธ๋„ ํ˜ธ์ŠคํŠธ์— ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(--font-dirยท--discovery๋กœ ์ง€์ •, ์—†๋Š” ํฐํŠธ๋ฅผ ๋Œ€์ฒด ๊ธ€๊ผด๋กœ ๋ Œ๋”ํ•˜๋ ค๋ฉด --degraded).

hwpforge to-pdf hancom-saved.hwpx -o report.pdf     # ํ•œ์ปด์ด ์ €์žฅํ•œ ๋ฌธ์„œ
hwpforge to-pdf legacy.hwp -o legacy.pdf            # HWP5 โ€” ์บ์‹œ๋ฅผ ์‹ค์–ด ๋ณ€ํ™˜ํ•œ ๋’ค ๋ Œ๋”

HWP5์—์„œ ์‹ค์–ด ์˜จ ์บ์‹œ๋Š” PDF ์žฌ์ƒยท๋Œ€์กฐ ์ „์šฉ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋งŒ๋“  .hwpx๋ฅผ ํ•œ์ปด์—์„œ ๋‹ค์‹œ ์—ด ์šฉ๋„๋กœ ์“ฐ์ง€ ๋งˆ์„ธ์š”.

AI-first ์„ค๊ณ„: CLI๋Š” AI agent(Claude Code ๋“ฑ)๊ฐ€ ์ฃผ ์‚ฌ์šฉ์ž์ž…๋‹ˆ๋‹ค. Markdown์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค, JSON round-trip์œผ๋กœ ๊ธฐ์กด ์Šคํƒ€์ผ์„ ๋ณด์กดํ•˜๋ฉด์„œ section ๋‹จ์œ„๋กœ ์ •๋ฐ€ํ•˜๊ฒŒ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. --json ํ”Œ๋ž˜๊ทธ๋กœ ๋ชจ๋“  ๋ช…๋ น์–ด๊ฐ€ machine-readable ์ถœ๋ ฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

โš™๏ธ Anvil โ€” MCP Server (Beta)๋กœ AI๊ฐ€ ์ง์ ‘ ํ•œ๊ธ€ ๋ฌธ์„œ๋ฅผ ๋‹ค๋ฃจ๋‹ค

Claude Code, Codex CLI, Claude, ChatGPT, Cursor, Antigravity ๋“ฑ MCP ์ง€์› AI ๋„๊ตฌ์—์„œ ํ•œ๊ธ€ ๋ฌธ์„œ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜๊ณ  ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ MCP surface๋Š” ๋ฒ ํƒ€์ด๋ฉฐ, HWP5 ๊ฒฝ๋กœ๋Š” MCP๊ฐ€ ์•„๋‹ˆ๋ผ CLI workflow๋ฅผ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค. "๋ณด๊ณ ์„œ ๋งŒ๋“ค์–ด์ค˜"๋ผ๊ณ  ๋งํ•˜๋ฉด, AI๊ฐ€ ์•Œ์•„์„œ .hwpx ํŒŒ์ผ์„ ๋š๋”ฑ ๋งŒ๋“ค์–ด๋ƒ…๋‹ˆ๋‹ค.

AI ๋„๊ตฌ์— ๋“ฑ๋ก

ํ•œ ์ค„์ด๋ฉด ์„ค์น˜ + ๋“ฑ๋ก์ด ๋๋‚ฉ๋‹ˆ๋‹ค. npm์€ npx -y๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

# npm (๊ถŒ์žฅ โ€” Rust ํˆด์ฒด์ธ ๋ถˆํ•„์š”)
claude mcp add hwpforge -- npx -y @hwpforge/mcp

# Cargo (Rust ๊ฐœ๋ฐœ์ž์šฉ)
cargo install hwpforge-bindings-mcp && claude mcp add hwpforge hwpforge-mcp

# ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ (๊ธ€๋กœ๋ฒŒ)
claude mcp add --global hwpforge -- npx -y @hwpforge/mcp

~/.codex/config.toml์— ์ถ”๊ฐ€:

[mcp_servers.hwpforge]
command = "npx"
args = ["-y", "@hwpforge/mcp"]

๋˜๋Š” CLI๋กœ:

codex mcp add hwpforge -- npx -y @hwpforge/mcp

์„ค์ • ํŒŒ์ผ์„ ํŽธ์ง‘ํ•ฉ๋‹ˆ๋‹ค:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "hwpforge": {
      "command": "npx",
      "args": ["-y", "@hwpforge/mcp"]
    }
  }
}

Settings โ†’ Tools โ†’ Add MCP Server์—์„œ:

  • Name: hwpforge
  • Command: npx -y @hwpforge/mcp

๋˜๋Š” ์„ค์ • ํŒŒ์ผ์„ ์ง์ ‘ ํŽธ์ง‘:

{
  "mcpServers": {
    "hwpforge": {
      "command": "npx",
      "args": ["-y", "@hwpforge/mcp"]
    }
  }
}

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— .cursor/mcp.json ์ƒ์„ฑ:

{
  "mcpServers": {
    "hwpforge": {
      "command": "npx",
      "args": ["-y", "@hwpforge/mcp"]
    }
  }
}

... ๋“œ๋กญ๋‹ค์šด โ†’ MCP Store โ†’ Manage MCP Servers โ†’ View raw config (mcp_config.json)์— ์ถ”๊ฐ€:

{
  "mcpServers": {
    "hwpforge": {
      "command": "npx",
      "args": ["-y", "@hwpforge/mcp"]
    }
  }
}

๋“ฑ๋กํ•˜๋ฉด 19๊ฐœ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

๋„๊ตฌ ํ•˜๋Š” ์ผ ํ•œ๋งˆ๋””
hwpforge_convert Markdown โ†’ HWPX ๋ณ€ํ™˜ "์ด ๋งˆํฌ๋‹ค์šด์„ ํ•œ๊ธ€ ํŒŒ์ผ๋กœ!"
hwpforge_inspect HWPX ๊ตฌ์กฐ ํ™•์ธ "์ด ๋ฌธ์„œ ๋ญ๊ฐ€ ๋“ค์–ด์žˆ์–ด?"
hwpforge_to_json HWPX โ†’ JSON ์ถ”์ถœ "์ด ์„น์…˜ ๋‚ด์šฉ ์ข€ ๊บผ๋‚ด๋ด"
hwpforge_patch JSON์œผ๋กœ ์„น์…˜ ๊ต์ฒด "์ด ๋ถ€๋ถ„๋งŒ ๋ฐ”๊ฟ”์„œ ๋‹ค์‹œ ์ €์žฅํ•ด"
hwpforge_templates ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์กฐํšŒ "์–ด๋–ค ํ…œํ”Œ๋ฆฟ ์“ธ ์ˆ˜ ์žˆ์–ด?"
hwpforge_validate HWPX ๊ตฌ์กฐ/๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ "์ด ํŒŒ์ผ ๋ฌธ์ œ ์—†๋Š”์ง€ ํ™•์ธํ•ด"
hwpforge_restyle ์Šคํƒ€์ผ ํ”„๋ฆฌ์…‹ ์ผ๊ด„ ์ ์šฉ "์ด ๋ฌธ์„œ ํฐํŠธ ๋ฐ”๊ฟ”์ค˜"
hwpforge_from_json JSON โ†’ HWPX ์ง์ ‘ ์ƒ์„ฑ "์ด JSON์œผ๋กœ ํ•œ๊ธ€ ํŒŒ์ผ ๋งŒ๋“ค์–ด"
hwpforge_to_md HWPX โ†’ Markdown ๋ณ€ํ™˜ "์ด ํ•œ๊ธ€ ๋ฌธ์„œ๋ฅผ Markdown์œผ๋กœ ๊บผ๋‚ด์ค˜"
hwpforge_outline ๋ฌธ์„œ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋งต(์ œ๋ชฉยทํ‘œยทํ•„๋“œยท์ฑ…๊ฐˆํ”ผ) ์กฐํšŒ "์ด ๋ฌธ์„œ ๊ตฌ์กฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ์–ด?"
hwpforge_diff ๋‘ HWPX ํŒŒ์ผ์„ semantic/package ์ฑ„๋„๋กœ ๋น„๊ต "์ด ํŽธ์ง‘์ด ๋ญ˜ ๋ฐ”๊ฟจ๋Š”์ง€ ํ™•์ธํ•ด์ค˜"
hwpforge_delete_para ์ตœ์ƒ์œ„ ๋ฌธ๋‹จ ์‚ญ์ œ (๊ตฌ์กฐ ํŽธ์ง‘) "์ด ๋ฌธ๋‹จ ์ง€์›Œ์ค˜"
hwpforge_insert_para ์•ต์ปค ๋ฌธ๋‹จ ๊ธฐ์ค€ ์ƒˆ ๋ฌธ๋‹จ ์‚ฝ์ž… "์ด ๋ฌธ๋‹จ ๋‹ค์Œ์— ๋‚ด์šฉ ์ถ”๊ฐ€ํ•ด์ค˜"
hwpforge_read ๋ฌธ๋‹จ/ํ‘œ/ํ•„๋“œ ์ค‘ ํ•˜๋‚˜๋ฅผ ํƒ€๊ฒŸ ์ฝ๊ธฐ "์ด ๋ถ€๋ถ„๋งŒ ๋”ฑ ์ฝ์–ด์ค˜"
hwpforge_fields ๋ˆ„๋ฆ„ํ‹€ ํ•„๋“œ ๋ชฉ๋ก ์กฐํšŒ "์ฑ„์šธ ์ˆ˜ ์žˆ๋Š” ์นธ์ด ๋ญ๊ฐ€ ์žˆ์–ด?"
hwpforge_fill ๋ˆ„๋ฆ„ํ‹€ ํ•„๋“œ์— ๊ฐ’ ์ฑ„์šฐ๊ธฐ "์ด ํ•„๋“œ๋“ค ์ฑ„์›Œ์ค˜"
hwpforge_stamp_plan ๋ฌธ์žฅ ์† ๋นˆ์นธ ์Šคํƒฌํ•‘ ํ›„๋ณด ํƒ์ƒ‰ "์ฑ„์šธ ์ˆ˜ ์žˆ๋Š” ๋นˆ์นธ ํ›„๋ณด ์ฐพ์•„์ค˜"
hwpforge_stamp ํ›„๋ณด๋ฅผ ๋ˆ„๋ฆ„ํ‹€ ํ•„๋“œ๋กœ ์Šน๊ฒฉ "์ด ๋นˆ์นธ๋“ค์„ ํ•„๋“œ๋กœ ๋งŒ๋“ค์–ด์ค˜"
hwpforge_set_cell ํ‘œ ์…€์„ ๊ทธ๋ฆฌ๋“œ ์ฃผ์†Œ๋กœ ํŽธ์ง‘ "์ด ํ‘œ ์นธ ๊ฐ’ ๋ฐ”๊ฟ”์ค˜"

์—…๋ฐ์ดํŠธ / ์‚ญ์ œ

npm์€ npx -y๊ฐ€ ํ•ญ์ƒ ์ตœ์‹  ๋ฒ„์ „์„ ๊ฐ€์ ธ์˜ค๋ฏ€๋กœ ๋ณ„๋„ ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

# Cargo ์‚ฌ์šฉ์ž๋งŒ ํ•ด๋‹น
cargo install hwpforge-bindings-mcp --force   # ์—…๋ฐ์ดํŠธ
cargo uninstall hwpforge-bindings-mcp          # ์‚ญ์ œ

์™œ MCP? CLI(Hammer)๋Š” AI๊ฐ€ bash ๋ช…๋ น์„ ์‹คํ–‰ํ•ด์•ผ ํ•˜์ง€๋งŒ, MCP(Anvil)๋Š” AI๊ฐ€ ๋„ค์ดํ‹ฐ๋ธŒ ๋„๊ตฌ๋กœ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ํŒŒ์ผ ๊ฒฝ๋กœ ํŒŒ์‹ฑ๋„, stdout ํ•ด์„๋„ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค. JSON-RPC๋กœ ์š”์ฒญํ•˜๋ฉด ๊ตฌ์กฐํ™”๋œ JSON์œผ๋กœ ์‘๋‹ต โ€” ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ Python์œผ๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

pip install hwpforge

CPython 3.9+ ์ „์šฉ abi3 wheel์„ Linux(manylinux_2_28 x86_64/aarch64)ยทmacOS(11+ arm64, 10.12+ x86_64)ยทWindows(x64)์— ๋ฐฐํฌํ•˜๋ฉฐ, ๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑ์€ 0๊ฐœ์ž…๋‹ˆ๋‹ค. pyproject.toml์— ์ง์ ‘ ํ•€ํ•  ๋•Œ๋Š” ์ •ํ™• ํ•€(==)๋ณด๋‹ค ~=๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค โ€” Python ์ „์šฉ ํŒจ์น˜(X.Y.Z.N)๋Š” ์ •ํ™• ํ•€์œผ๋กœ๋Š” ๋ฐ›์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

import hwpforge

doc = hwpforge.Document.open("proposal.hwpx")
print(doc.outline()["outline"]["title"])
doc = doc.fill({"applicant": "ํ™๊ธธ๋™", "date": "2026-09-17"}).document
doc.save("proposal-filled.hwpx")

Document๋Š” ๋ถˆ๋ณ€ ๊ฐ’์ž…๋‹ˆ๋‹ค โ€” ๋ชจ๋“  ํŽธ์ง‘ ๋ฉ”์„œ๋“œ๋Š” ์›๋ณธ์„ ๊ทธ๋Œ€๋กœ ๋‘” ์ฑ„ ์ƒˆ ๋ฌธ์„œ์™€ ๋ณด๊ณ ์„œ๋ฅผ ๋‹ด์€ DocumentResult๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ณต์œ  ์—ฐ์‚ฐ ๊ณ„์ธต์„ ์“ฐ๋Š” 18๊ฐœ document ๋ฉ”์„œ๋“œ + 5๊ฐœ ๋ชจ๋“ˆ ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ํ˜ธ์ถœ ์ด๋ฆ„ยท์˜ต์…˜ ํ‘œ๊ธฐยทํ˜ธํ™˜ ์—๋Ÿฌ ์ฝ”๋“œ๋Š” ๋ฐ”์ธ๋”ฉ๋งˆ๋‹ค ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ hwpforge ํŒจํ‚ค์ง€ README๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๐Ÿ”จ ๋ฌธ์„œ ์ƒ์„ฑ

use hwpforge::core::{Document, Draft, Paragraph, Run, Section, PageSettings};
use hwpforge::foundation::{CharShapeIndex, ParaShapeIndex};

let mut doc = Document::<Draft>::new();
doc.add_section(Section::with_paragraphs(
    vec![Paragraph::with_runs(
        vec![Run::text("Hello, ํ•œ๊ธ€!", CharShapeIndex::new(0))],
        ParaShapeIndex::new(0),
    )],
    PageSettings::a4(),
));

โš’๏ธ HWPX๋กœ ์ธ์ฝ”๋”ฉ

use hwpforge::hwpx::{HwpxEncoder, HwpxStyleStore};
use hwpforge::core::ImageStore;

let validated = doc.validate().unwrap();
let style_store = HwpxStyleStore::with_default_fonts("ํ•จ์ดˆ๋กฌ๋ฐ”ํƒ•");
let image_store = ImageStore::new();
let bytes = HwpxEncoder::encode(&validated, &style_store, &image_store).unwrap();
std::fs::write("output.hwpx", &bytes).unwrap();

โš’๏ธ HWPX ๋””์ฝ”๋”ฉ

use hwpforge::hwpx::HwpxDecoder;

let result = HwpxDecoder::decode_file("input.hwpx").unwrap();
println!("์„น์…˜ ์ˆ˜: {}", result.document.sections().len());

โš’๏ธ HWPX โ†’ Markdown ๋ณ€ํ™˜ (AI๊ฐ€ ํ•œ๊ธ€ ๋ฌธ์„œ ์ฝ๊ธฐ)

use hwpforge::hwpx::HwpxDecoder;
use hwpforge::md::MdEncoder;

let decoded = HwpxDecoder::decode_file("government_report.hwpx").unwrap();
let validated = decoded.document.validate().unwrap();
let markdown = MdEncoder::encode_lossy(&validated).unwrap();
println!("{}", markdown); // LLM์ด ๋ฐ”๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” Markdown

๊ธฐ์กด .hwpx ํŒŒ์ผ์„ Markdown์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด Claude, GPT ๋“ฑ ์–ด๋–ค LLM์ด๋“  ํ•œ๊ธ€ ๊ณต๋ฌธ์„œ๋ฅผ ์ฆ‰์‹œ ์ฝ๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โš’๏ธ Markdown โ†’ HWPX ๋ณ€ํ™˜

use hwpforge::md::MdDecoder;
use hwpforge::hwpx::{HwpxEncoder, HwpxRegistryBridge};

let md_doc = MdDecoder::decode_with_default("# ์ œ๋ชฉ\n\nMarkdown์—์„œ ๋ณ€ํ™˜!").unwrap();
let bridge = HwpxRegistryBridge::from_registry(&md_doc.style_registry).unwrap();
let rebound = bridge.rebind_draft_document(md_doc.document).unwrap();
let validated = rebound.validate().unwrap();
let image_store = hwpforge::core::ImageStore::new();
let bytes = HwpxEncoder::encode(&validated, bridge.style_store(), &image_store).unwrap();

Feature Flags

Feature ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
hwpx Yes HWPX encoder/decoder
md โ€” Markdown โ†” Core ๋ณ€ํ™˜
full โ€” ๋ชจ๋“  ๊ธฐ๋Šฅ ํฌํ•จ
# Markdown ์ง€์› ํฌํ•จ
hwpforge = { version = "0.16", features = ["full"] }

๐Ÿ“œ ์ง€์› ์ฝ˜ํ…์ธ 

์นดํ…Œ๊ณ ๋ฆฌ ์š”์†Œ
ํ…์ŠคํŠธ Run, character shape, paragraph shape, style (22๊ฐœ ํ•œ์ปด ๊ธฐ๋ณธ ์Šคํƒ€์ผ)
๊ตฌ์กฐ Table (์ค‘์ฒฉ), Image (๋ฐ”์ด๋„ˆ๋ฆฌ + ๊ฒฝ๋กœ), TextBox, Caption
๋ ˆ์ด์•„์›ƒ ๋‹ค๋‹จ, ํŽ˜์ด์ง€ ์„ค์ •, ๊ฐ€๋กœ/์„ธ๋กœ ๋ฐฉํ–ฅ, ์ œ๋ณธ ์—ฌ๋ฐฑ, master page
๋จธ๋ฆฌ๊ธ€/๋ฐ”๋‹ฅ๊ธ€ Header, Footer, ์ชฝ๋ฒˆํ˜ธ (autoNum)
๊ฐ์ฃผ/๋ฏธ์ฃผ ๊ฐ์ฃผ, ๋ฏธ์ฃผ
๋„ํ˜• ์„ , ํƒ€์›, ๋‹ค๊ฐํ˜•, ํ˜ธ, ๊ณก์„ , ์—ฐ๊ฒฐ์„  (์ฑ„์›€, ํšŒ์ „, ํ™”์‚ดํ‘œ ์ง€์›)
์ˆ˜์‹ HancomEQN script ํ˜•์‹
์ฐจํŠธ 18์ข… chart type (OOXML ํ˜ธํ™˜)
์ฐธ์กฐ ์ฑ…๊ฐˆํ”ผ, ์ƒํ˜ธ ์ฐธ์กฐ, ํ•„๋“œ (๋‚ ์งœ/์‹œ๊ฐ„/์š”์•ฝ), ๋ฉ”๋ชจ, ์ƒ‰์ธ
๋ง๋ง/๊ฒน์นจ ๋ง๋ง (dutmal), ๊ธ€์ž ๊ฒน์นจ
Markdown GFM decode, lossy + lossless encode, YAML frontmatter
PDF ๋‚ด๋ณด๋‚ด๊ธฐ ํ•œ์ปด ์กฐํŒ ์บ์‹œ ์žฌ์ƒ(๊ณ„์‚ฐ ์•„๋‹˜) ๋ Œ๋” โ€” ํ‘œยท๋จธ๋ฆฌ๊ธ€/๋ฐ”๋‹ฅ๊ธ€ยท์ชฝ๋ฒˆํ˜ธยทํฐํŠธ ํŒŒ์ดํ”„๋ผ์ธ

์•„ํ‚คํ…์ฒ˜

HwpForge๋Š” ๋ ˆ์ด์–ด๋ฅผ ๋‚˜๋ˆ ์„œ ์ƒ๊ฐํ•˜๋Š” ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค.

  • foundation: ๊ณตํ†ต primitive, unit, index, error
  • core: ํฌ๋งท ๋…๋ฆฝ ๋ฌธ์„œ ๋ชจ๋ธ๊ณผ shared semantics
  • blueprint: ์Šคํƒ€์ผ ์ •์˜์™€ ํ…œํ”Œ๋ฆฟ ๊ณ„์ธต
  • smithy-*: ํฌ๋งท๋ณ„ codec๊ณผ bridge (๊ฐ ํฌ๋ ˆ์ดํŠธ๋Š” ๋‹จ์ผ ํฌ๋งท๋งŒ ๋‹ด๋‹น)
  • convert: ํฌ๋งท ๊ฐ„ ๋ณ€ํ™˜ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ (HWP5 โ†’ HWPX, smithy ์œ„์—์„œ ๋‘ ํฌ๋งท์„ ์—ฎ์Œ)
  • bindings-*: CLI / MCP / Python ์ง„์ž…์ 

hwpforge(umbrella crate)์˜ ops ๋ชจ๋“ˆ๊ณผ hwpforge-convert์˜ ops ๋ชจ๋“ˆ์€ ์„ธ ๋ฐ”์ธ๋”ฉ์ด ๊ณต์œ ํ•˜๋Š” ์—ฐ์‚ฐ ๊ณ„์ธต์ž…๋‹ˆ๋‹ค โ€” CLIยทMCPยทPython ๋ชจ๋‘ ์ด ๊ณ„์ธต์„ ๊ฑฐ์ณ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

๋ ˆ์ด์–ด ๊ตฌ์กฐ

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '14px', 'lineColor': '#BDBDBD'}}}%%
flowchart TB
    F["foundation<br/>primitive types<br/>units / indices / errors"]:::foundation
    C["core<br/>shared document model<br/>list / tab / paragraph semantics"]:::core
    B["blueprint<br/>style registry<br/>template authoring"]:::blueprint

    SHX["smithy-hwpx<br/>HWPX read/write"]:::smithy
    SH5["smithy-hwp5<br/>HWP5 read / audit / re-emission path"]:::smithy
    SMD["smithy-md<br/>Markdown bridge"]:::smithy
    SPDF["smithy-pdf<br/>layout-cache replay renderer"]:::smithy

    CONV["convert<br/>HWP5 โ†’ HWPX orchestrator"]:::convert

    CLI["bindings-cli<br/>Hammer"]:::binding
    MCP["bindings-mcp<br/>Anvil MCP Server"]:::binding
    PY["bindings-py<br/>Python"]:::binding

    F --> C
    C --> B
    C --> SHX
    C --> SH5
    C --> SMD
    F --> SPDF
    C --> SPDF
    B --> SHX
    B --> SMD
    C --> CONV
    SHX --> CONV
    SH5 --> CONV
    SHX --> CLI
    SH5 --> CLI
    SMD --> CLI
    SPDF --> CLI
    CONV --> CLI
    SHX --> MCP
    SMD --> MCP
    SHX --> PY

    classDef file fill:#FFFDE7,stroke:#F9A825,color:#5D4037
    classDef smithy fill:#FFF3E0,stroke:#FB8C00,color:#E65100
    classDef convert fill:#E0F2F1,stroke:#00897B,color:#004D40
    classDef core fill:#E3F2FD,stroke:#42A5F5,color:#0D47A1
    classDef blueprint fill:#F3E5F5,stroke:#AB47BC,color:#4A148C
    classDef foundation fill:#FAFAFA,stroke:#BDBDBD,color:#424242
    classDef binding fill:#E8F5E9,stroke:#43A047,color:#1B5E20

๋ฐ์ดํ„ฐ ํ๋ฆ„

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '14px', 'lineColor': '#BDBDBD'}}}%%
flowchart LR
    HWP5[".hwp"]:::file --> SH5["smithy-hwp5"]:::smithy
    MD[".md"]:::file --> SMD["smithy-md"]:::smithy
    HWPX[".hwpx"]:::file <--> SHX["smithy-hwpx"]:::smithy

    SH5 --> CORE["core document"]:::core
    SHX <--> CORE
    SMD <--> CORE
    CORE --> SHX

    CONV["convert<br/>HWP5โ†’HWPX ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜"]:::convert --> SH5
    CONV --> SHX

    CLI["CLI"]:::binding --> CONV
    CLI --> SH5
    CLI --> SHX
    CLI --> SMD
    MCP["MCP"]:::binding --> SHX
    MCP --> SMD

    classDef file fill:#FFFDE7,stroke:#F9A825,color:#5D4037
    classDef smithy fill:#FFF3E0,stroke:#FB8C00,color:#E65100
    classDef convert fill:#E0F2F1,stroke:#00897B,color:#004D40
    classDef core fill:#E3F2FD,stroke:#42A5F5,color:#0D47A1
    classDef binding fill:#E8F5E9,stroke:#43A047,color:#1B5E20

ํ•ต์‹ฌ ์›์น™: ๊ตฌ์กฐ(Structure)์™€ ์Šคํƒ€์ผ(Style)์„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค. core๋Š” shared semantics์™€ ์Šคํƒ€์ผ ์ฐธ์กฐ๋ฅผ ๋“ค๊ณ , blueprint๋Š” ์Šคํƒ€์ผ ์ •์˜๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. smithy-* ๊ณ„์ธต์ด ํฌ๋งท surface๋ฅผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ „์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ์“ฐ๋Š” ๊ฒฝ๋กœ๋Š” ๋‹ค์Œ ์…‹์ž…๋‹ˆ๋‹ค.

  1. Markdown โ†’ HWPX
  2. HWPX โ†’ Markdown / JSON
  3. HWP5 โ†’ HWPX โ†’ audit / inspect

ํ”„๋กœ์ ํŠธ ํ˜„ํ™ฉ

์ง€ํ‘œ ๊ฐ’
Tracked Rust src LOC ~114,421
ํ…Œ์ŠคํŠธ ~3,476 passed + 14 skipped (cargo-nextest, 2026-08-28 make ci ๊ธฐ์ค€)
์†Œ์Šค ํŒŒ์ผ 228 .rs
Crate ์ˆ˜ 12๊ฐœ
์ปค๋ฒ„๋ฆฌ์ง€ 90%+
Clippy ๊ฒฝ๊ณ  0
Unsafe ์ฝ”๋“œ 0

๊ฐœ๋ฐœ

ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ

  • Rust 1.88+ (์›Œํฌ์ŠคํŽ˜์ด์Šค MSRV) โ€” hwpforge-bindings-cli CLI๋ฅผ ์ง์ ‘ ๋นŒ๋“œ/์„ค์น˜ํ•˜๋ ค๋ฉด krilla ์˜์กด์œผ๋กœ 1.92+ ํ•„์š”
  • (๊ถŒ์žฅ) cargo-nextest โ€” ๋ณ‘๋ ฌ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  • (์„ ํƒ) pre-commit โ€” git hook ์ž๋™ํ™”

MSRV ์ •์ฑ…

  • ์›Œํฌ์ŠคํŽ˜์ด์Šค ๊ธฐ๋ณธ MSRV๋Š” Rust 1.88์ด๋ฉฐ, stable์—์„œ 4 ๋ฆด๋ฆฌ์Šค ๋’ค์ฒ˜์ง„ ๋ฒ„์ „์„ ๊ธฐ๋ณธ ์ •์ฑ…์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • krilla ์˜์กด ๊ฒฝ๋กœ์— ์žˆ๋Š” ๋„ค ํฌ๋ ˆ์ดํŠธ(hwpforge-smithy-pdfยทhwpforge-convertยทhwpforge-bindings-cliยทhwpforge-bindings-py)๋Š” Rust 1.92+๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค(rust-version์„ ํฌ๋ ˆ์ดํŠธ๋ณ„๋กœ ์ƒํ–ฅ ์ง€์ •). CI์˜ Verify โ€บ MSRV (1.88) job์€ ์ด ๋„ท์„ 1.88 ๊ฒ€์ฆ ํŒจ์Šค์—์„œ๋Š” ์ œ์™ธํ•˜์ง€๋งŒ, ๊ฐ™์€ job ์•ˆ์—์„œ cargo +1.92 check๋กœ ๋”ฐ๋กœ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค โ€” ๊ฒ€์ฆ ๋Œ€์ƒ์—์„œ ๋น ์ง€๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค.
  • ๊ฐ ํฌ๋ ˆ์ดํŠธ์˜ Cargo.toml์˜ rust-version์ด ๊ทธ ํฌ๋ ˆ์ดํŠธ์˜ ์‹ค์ œ MSRV์ด๋ฉฐ, CI์˜ Verify โ€บ MSRV job์ด ์›Œํฌ์ŠคํŽ˜์ด์Šค ๊ธฐ๋ณธ๊ฐ’(1.88)์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • MSRV ์ƒํ–ฅ์ด ํ•„์š”ํ•˜๋ฉด PR์—์„œ ์ด์œ ๋ฅผ ๋ช…์‹œํ•˜๊ณ , Cargo.toml, CI, CHANGELOG๋ฅผ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์šฉ ๊ธฐ๋ณธ ํˆด์ฒด์ธ์€ ๋” ์ตœ์‹ ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜ธํ™˜์„ฑ ํŒ๋‹จ ๊ธฐ์ค€์€ ์ตœ์‹  stable์ด ์•„๋‹ˆ๋ผ MSRV + CI ํ†ต๊ณผ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค.

โš’๏ธ ๋ช…๋ น์–ด

make ci          # ๋น ๋ฅธ ๋กœ์ปฌ ๊ฒ€์ฆ โ€” fmt + clippy + test + deny + lint-md (ci-fast ๋ณ„์นญ)
make ci-full     # ์œ„์— coverage + MSRV ์ถ”๊ฐ€ (๋ฆด๋ฆฌ์Šคยทํฐ ๋ณ€๊ฒฝ ์ „)
make py-all      # Python ๋ฐ”์ธ๋”ฉ ๊ฒ€์‚ฌ (lint + ํƒ€์ž… + Rust/Python ํ…Œ์ŠคํŠธ + coverage)
make test        # cargo nextest run
make clippy      # cargo clippy (๋ชจ๋“  target, ๋ชจ๋“  feature, -D warnings)
make fmt-fix     # rustfmt ์ž๋™ ํฌ๋งท
make doc         # rustdoc ์ƒ์„ฑ (๋ธŒ๋ผ์šฐ์ €์—์„œ ์—ด๋ฆผ)
make cov         # coverage ๋ฆฌํฌํŠธ (90% gate)

make ci๋Š” CI ์ „์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๊ทธ์ค‘ ๋น ๋ฅธ ๋‹ค์„ฏ ๋ ˆ์ธ์ž…๋‹ˆ๋‹ค. CI๋Š” ์—ฌ๊ธฐ์— CoverageยทMSRVยทHWP5 Audit GateยทDocs BuildยทPythonยทWorkflow Lint๋ฅผ ๋” ๋Œ๋ฆฝ๋‹ˆ๋‹ค. ๋ฆด๋ฆฌ์Šค๋‚˜ ํฐ ๋ณ€๊ฒฝ ์ „์—๋Š” make ci-full๊ณผ make py-all์„, ๋ฌธ์„œ๋ฅผ ๊ณ ์ณค๋‹ค๋ฉด mdbook build๋ฅผ ํ•จ๊ป˜ ๋Œ๋ฆฌ์„ธ์š”.

๋นŒ๋“œ ๊ฐ€์† (์„ ํƒ): sccache๊ฐ€ PATH์— ์žˆ์œผ๋ฉด make ํƒ€๊นƒ์ด ์ž๋™์œผ๋กœ ์ปดํŒŒ์ผ ์บ์‹œ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์—†์œผ๋ฉด ๊ทธ๋Œ€๋กœ ๋™์ž‘ โ€” ์•„๋ฌด๊ฒƒ๋„ ๊นจ์ง€์ง€ ์•Š์Œ). ๋ฐ˜๋ณต make ci๊ฐ€ ํฌ๊ฒŒ ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค. ์„ค์น˜: cargo install sccache ๋˜๋Š” brew install sccache. (๋ฆด๋ฆฌ์Šค ํŒŒ์ดํ”„๋ผ์ธ release-plz ๋Š” ์˜ํ–ฅ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค.)

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

HwpForge/
โ”œโ”€โ”€ crates/
โ”‚   โ”œโ”€โ”€ hwpforge/                 # Umbrella crate (re-exports)
โ”‚   โ”œโ”€โ”€ hwpforge-foundation/      # ๊ธฐ๋ณธ ํƒ€์ž… (HwpUnit, Color, Index<T>)
โ”‚   โ”œโ”€โ”€ hwpforge-core/            # ๋ฌธ์„œ ๋ชจ๋ธ (์Šคํƒ€์ผ ์ฐธ์กฐ๋งŒ)
โ”‚   โ”œโ”€โ”€ hwpforge-blueprint/       # YAML ํ…œํ”Œ๋ฆฟ (Figma ํŒจํ„ด)
โ”‚   โ”œโ”€โ”€ hwpforge-smithy-hwpx/     # HWPX codec (ZIP+XML โ†” Core)
โ”‚   โ”œโ”€โ”€ hwpforge-smithy-md/       # Markdown codec (MD โ†” Core)
โ”‚   โ”œโ”€โ”€ hwpforge-smithy-hwp5/     # HWP5 decode/projection + inspect helpers
โ”‚   โ”œโ”€โ”€ hwpforge-smithy-pdf/      # PDF ๋ Œ๋”๋Ÿฌ (์กฐํŒ ์บ์‹œ ์žฌ์ƒ ๋ Œ๋”๋Ÿฌ, to-pdf ๊ฐ€ ์‚ฌ์šฉ)
โ”‚   โ”œโ”€โ”€ hwpforge-convert/         # ํฌ๋งท ๊ฐ„ ๋ณ€ํ™˜ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ (HWP5 โ†’ HWPX)
โ”‚   โ”œโ”€โ”€ hwpforge-bindings-py/     # Python bindings (shipped, PyPI)
โ”‚   โ”œโ”€โ”€ hwpforge-bindings-cli/    # CLI ๋„๊ตฌ (hwpforge, shipped)
โ”‚   โ””โ”€โ”€ hwpforge-bindings-mcp/    # MCP Server (hwpforge-mcp)
โ”œโ”€โ”€ tests/                        # ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ + golden fixture
โ””โ”€โ”€ examples/                     # curated showcase + interop artifacts
    โ”œโ”€โ”€ showcase/
    โ””โ”€โ”€ interop/

๊ธฐ์—ฌ

๋ฒ„๊ทธ ์ˆ˜์ •, ํฌ๋งท ๋ฆฌ์„œ์น˜, ํ…Œ์ŠคํŠธ ๋ณด๊ฐ•, ๋ฌธ์„œ ๊ฐœ์„  ๋ชจ๋‘ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.

  • ์‹œ์ž‘ ์ „ ๊ฐ€์ด๋“œ: CONTRIBUTING.md
  • ํŠนํžˆ ํ™•์ธํ•  ๊ฒƒ: release-plz๊ฐ€ ์“ฐ๋Š” ์ปค๋ฐ‹ prefix (feat, fix, perf, refactor)
  • ํŠนํžˆ ํ™•์ธํ•  ๊ฒƒ: MSRV ์ •์ฑ…๊ณผ dependency/MSRV ์ƒ์Šน ๊ธฐ์ค€
  • ํŠนํžˆ ํ™•์ธํ•  ๊ฒƒ: ๋ฌธ์„œ ๋ณ€๊ฒฝ ์‹œ mdbook build์™€ markdown lint ๊ฒ€์ฆ
  • ํŠนํžˆ ํ™•์ธํ•  ๊ฒƒ: ๋กœ์ปฌ docs toolchain์€ CI์™€ ๊ฐ™์€ pinned ๋ฒ„์ „(mdbook 0.4.52, mdbook-admonish 1.20.0, mdbook-mermaid 0.16.2)์„ ์“ฐ๋Š” ํŽธ์ด ๋‚ซ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์€ make install-tools
  • ํŠนํžˆ ํ™•์ธํ•  ๊ฒƒ: CI required checks๋ฅผ ๊นจ์ง€ ์•Š๋Š” ๋ฒ”์œ„์—์„œ์˜ ๋ณ€๊ฒฝ ๋ถ„๋ฆฌ

๋กœ๋“œ๋งต

ํ˜„์žฌ ์ƒํƒœ์™€ ๋‹ค์Œ ๋‹จ๊ณ„

  • HWP5 ์ฝ๊ธฐ/์ ๊ฒ€/์žฌ์ถœ๋ ฅ ๊ฒฝ๋กœ โ€” convert-hwp5, audit-hwp5, census-hwp5
  • HWP5 public API ํ™•๋Œ€ โ€” umbrella crate surface์™€ broader parity ์ •๋ฆฌ
  • MCP ์„œ๋ฒ„ โ€” Claude, Cursor ๋“ฑ AI ๋„๊ตฌ๊ฐ€ tool๋กœ ์ง์ ‘ HWPX ์ƒ์„ฑยท๊ฒ€์ฆยทํŽธ์ง‘ (19๊ฐœ ๋„๊ตฌ + 4 ๋ฆฌ์†Œ์Šค + 3 ํ”„๋กฌํ”„ํŠธ)
  • CLI ๋„๊ตฌ โ€” hwpforge convert doc.md -o doc.hwpx ํ•œ ์ค„ ๋ณ€ํ™˜ (23๊ฐœ ๋ช…๋ น์–ด: 20 core + 3 HWP5)
  • HWPX ์™„์ „ ์ง€์› โ€” ์–‘์‹ ์ปจํŠธ๋กค, ๋ณ€๊ฒฝ ์ถ”์ , OLE ๊ฐ์ฒด
  • Python ๋ฐ”์ธ๋”ฉ โ€” pip install hwpforge๋กœ ์„ค์น˜ (PyPI, 0.16.5๋ถ€ํ„ฐ wheel ๋ฐฐํฌ)

๋ผ์ด์„ ์Šค

๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Acknowledgements

HwpForge๋Š” ๊ฑฐ์ธ๋“ค์˜ ์–ด๊นจ ์œ„์— ์„œ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Hancom โ€” HWPX ํฌ๋งท์˜ ๊ณต๊ฐœ ๋ฌธ์„œ์™€ KS X 6101 (OWPML) ๊ตญ๊ฐ€ ํ‘œ์ค€์ด ์—†์—ˆ๋‹ค๋ฉด ์ด ํ”„๋กœ์ ํŠธ๋Š” ์‹œ์ž‘์กฐ์ฐจ ํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ํฌ๋งท์„ ๊ณต๊ฐœํ•ด ์ฃผ์‹  Hancom์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
  • openhwp โ€” Rust๋กœ HWP/HWPX๋ฅผ ๋‹ค๋ฃจ๋Š” IR ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„์—์„œ ํฐ ์˜๊ฐ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. HwpForge์˜ Core ๋ ˆ์ด์–ด๊ฐ€ ์กด์žฌํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒƒ์€ openhwp์ด ๋จผ์ € ๊ทธ ๊ธธ์„ ๊ฑธ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
  • hwpxlib โ€” Java๋กœ ์ž‘์„ฑ๋œ ๊ฐ€์žฅ ์„ฑ์ˆ™ํ•œ HWPX ๊ตฌํ˜„์ฒด์ž…๋‹ˆ๋‹ค. ์ŠคํŽ™๊ณผ ์‹ค์ œ ๋™์ž‘์˜ ์ฐจ์ด๋ฅผ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ๊ฒฐ์ •์ ์ธ ์ฐธ๊ณ ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  • hwp.js โ€” HWP5 ํฌ๋งท์˜ quirks์™€ edge case๋ฅผ ๊ผผ๊ผผํžˆ ๋ฌธ์„œํ™”ํ•œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท์˜ ์–ด๋‘์šด ๊ตฌ์„์„ ๋ฐํ˜€ ์ค€ ๋•๋ถ„์— ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  • hwpx-owpml-model โ€” Hancom์ด ์ง์ ‘ ๊ณต๊ฐœํ•œ C++ OWPML ๋ชจ๋ธ ๊ตฌํ˜„์ฒด๋กœ, ์Šคํ‚ค๋งˆ ํ•ด์„์˜ ์ตœ์ข… ๊ธฐ์ค€์œผ๋กœ ์‚ผ์•˜์Šต๋‹ˆ๋‹ค.
  • Rust ์ƒํƒœ๊ณ„ โ€” serde, quick-xml, pulldown-cmark, zip ๋“ฑ ๋›ฐ์–ด๋‚œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค ๋•๋ถ„์— HwpForge ์ „์ฒด๋ฅผ zero unsafe ์ˆœ์ˆ˜ Rust๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. Rust ์ปค๋ฎค๋‹ˆํ‹ฐ์™€ Ferris ๐Ÿฆ€์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.
  • Claude by Anthropic โ€” HwpForge์˜ ์„ค๊ณ„, ๊ตฌํ˜„, ํ…Œ์ŠคํŠธ, ๋ฌธ์„œํ™” ์ „ ๊ณผ์ •์—์„œ Claude Code๊ฐ€ ๊ฐœ๋ฐœ ํŒŒํŠธ๋„ˆ๋กœ ํ•จ๊ป˜ํ–ˆ์Šต๋‹ˆ๋‹ค. LLM-first๋ฅผ ํ‘œ๋ฐฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋‹ต๊ฒŒ, AI์™€ ์‚ฌ๋žŒ์ด ํ˜‘์—…ํ•˜์—ฌ ๋งŒ๋“ค์–ด๋‚ธ ๊ฒฐ๊ณผ๋ฌผ์ž…๋‹ˆ๋‹ค.
  • Codex by OpenAI โ€” HwpForge์˜ ๊ตฌํ˜„ ๊ฒ€ํ† , ๋ฆฌํŒฉํ„ฐ๋ง, ํšŒ๊ท€ ์ ๊ฒ€, ์ž‘์—… ํ๋ฆ„ ์ •๋ฆฌ ๊ณผ์ •์—์„œ ์‹ค์งˆ์ ์ธ ๊ฐœ๋ฐœ ํŒŒํŠธ๋„ˆ๋กœ ๊ธฐ์—ฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์‡ ๋ถ€๋ฆฌ Anvilscribe (SoeBuri Anvilscribe) ํ•œ์ปด ๋ฌธ์„œ๋ฅผ ๋ถˆ์— ๋‹ฌ๊ตฌ์–ด ๋‹จ๋‹จํ•˜๊ฒŒ ๋ฒผ๋ ค๋‚ด๋Š” ๋Œ€์žฅ์žฅ์ด ์˜ค๋ฆฌ๋„ˆ๊ตฌ๋ฆฌ ๐Ÿ”ฅ