HwpForge

mcp
Security Audit
Warn
Health Warn
  • License รขโ‚ฌโ€ License: Apache-2.0
  • Description รขโ‚ฌโ€ Repository has a description
  • Active repo รขโ‚ฌโ€ Last push 0 days ago
  • Low visibility รขโ‚ฌโ€ Only 7 GitHub stars
Code Pass
  • Code scan รขโ‚ฌโ€ Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
  • Permissions รขโ‚ฌโ€ No dangerous permissions requested
Purpose
This tool is a Rust library and MCP server designed for the programmatic creation, reading, and conversion of Korean Hancom (HWP/HWPX) documents. It enables AI agents and developers to seamlessly convert Markdown to HWPX and perform JSON round-trip editing on these files.

Security Assessment
The overall risk is rated as Low. The automated code scan reviewed 12 files and found no dangerous patterns. It does not request any dangerous system permissions, shows no evidence of making unauthorized network requests, and contains no hardcoded secrets or shell execution commands. Because it is written in Rust, it benefits from strong memory safety guarantees, further reinforced by the project's strict "unsafe-forbidden" policy. The primary concern is that as a document parser, it could theoretically be exposed to malformed or malicious files, but the underlying code itself appears safe and non-invasive.

Quality Assessment
The project is in excellent health and demonstrates strong engineering practices. It is actively maintained, with repository activity as recent as today. It is well-documented, thoroughly tested (claiming over 2,200 passed tests and 92% code coverage), and properly licensed under the permissive Apache-2.0 and MIT licenses. The only notable drawback is its low current visibility, boasting only 7 GitHub stars. Consequently, the community trust level is high from a code-quality standpoint, but low in terms of widespread real-world battle-testing.

Verdict
Safe to use, though developers should remain aware that the project is still early in its community adoption lifecycle.
SUMMARY

Rust library for programmatic control of Korean HWPX documents โ€” Markdownโ†’HWPX conversion, JSON round-trip editing, MCP server for AI agents ๐Ÿ”ฅ

README.md

HwpForge ๐Ÿ”ฅ

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

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

CI
codecov
Tests
unsafe forbidden
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 Banner

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.5"

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

CLI ๋„๊ตฌ hwpforge(Hammer)๋ฅผ ์„ค์น˜ํ•˜๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ๋ฐ”๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

cargo install hwpforge-bindings-cli
# Markdown โ†’ HWPX ๋ณ€ํ™˜
hwpforge convert report.md -o report.hwpx

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

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

# HWPX โ†’ JSON ์ถ”์ถœ (AI ํŽธ์ง‘์šฉ)
hwpforge to-json report.hwpx --section 0 > section0.json

# JSON โ†’ HWPX ์ง์ ‘ ์ƒ์„ฑ
hwpforge from-json section0.json -o new.hwpx

# JSON์œผ๋กœ ์„น์…˜ ๊ต์ฒด
hwpforge patch report.hwpx --section 0 < modified.json -o updated.hwpx

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

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๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

Claude Code (ํ„ฐ๋ฏธ๋„)
# 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 CLI (ํ„ฐ๋ฏธ๋„)

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

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

๋˜๋Š” CLI๋กœ:

codex mcp add hwpforge -- npx -y @hwpforge/mcp
Claude Desktop (์•ฑ)

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

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

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

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

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

{
  "mcpServers": {
    "hwpforge": {
      "command": "npx",
      "args": ["-y", "@hwpforge/mcp"]
    }
  }
}
Cursor (์—๋””ํ„ฐ)

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

{
  "mcpServers": {
    "hwpforge": {
      "command": "npx",
      "args": ["-y", "@hwpforge/mcp"]
    }
  }
}
Antigravity (์—๋””ํ„ฐ)

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

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

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

๋„๊ตฌ ํ•˜๋Š” ์ผ ํ•œ๋งˆ๋””
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์œผ๋กœ ๊บผ๋‚ด์ค˜"

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

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์œผ๋กœ ์‘๋‹ต โ€” ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค.

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

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๊ฐ€ ํ•œ๊ธ€ ๋ฌธ์„œ ์ฝ๊ธฐ)

๐Ÿ“„ ํ•œ๊ธ€ ์›๋ณธ (.hwpx) ๐Ÿ“ Markdown ๋ณ€ํ™˜ ๊ฒฐ๊ณผ
ํ•œ๊ธ€ ์›๋ณธ ๋ฌธ์„œ Markdown ๋ณ€ํ™˜ ๊ฒฐ๊ณผ
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.5", 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

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

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

  • foundation: ๊ณตํ†ต primitive, unit, index, error
  • core: ํฌ๋งท ๋…๋ฆฝ ๋ฌธ์„œ ๋ชจ๋ธ๊ณผ shared semantics
  • blueprint: ์Šคํƒ€์ผ ์ •์˜์™€ ํ…œํ”Œ๋ฆฟ ๊ณ„์ธต
  • smithy-*: ํฌ๋งท๋ณ„ codec๊ณผ bridge
  • bindings-*: 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

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

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

    classDef file fill:#FFFDE7,stroke:#F9A825,color:#5D4037
    classDef smithy fill:#FFF3E0,stroke:#FB8C00,color:#E65100
    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

    CLI["CLI"]:::binding --> 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 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 ~83,962
ํ…Œ์ŠคํŠธ 2,207๊ฐœ (cargo-nextest)
์†Œ์Šค ํŒŒ์ผ 137 .rs
Crate ์ˆ˜ 10๊ฐœ
์ปค๋ฒ„๋ฆฌ์ง€ 92.65%
Clippy ๊ฒฝ๊ณ  0
Unsafe ์ฝ”๋“œ 0

๊ฐœ๋ฐœ

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

  • Rust 1.88+ (MSRV)
  • (๊ถŒ์žฅ) cargo-nextest โ€” ๋ณ‘๋ ฌ ํ…Œ์ŠคํŠธ ์‹คํ–‰
  • (์„ ํƒ) pre-commit โ€” git hook ์ž๋™ํ™”

MSRV ์ •์ฑ…

  • ํ˜„์žฌ MSRV๋Š” Rust 1.88์ž…๋‹ˆ๋‹ค.
  • HwpForge๋Š” stable์—์„œ 4 ๋ฆด๋ฆฌ์Šค ๋’ค์ฒ˜์ง„ ๋ฒ„์ „์„ ๊ธฐ๋ณธ MSRV ์ •์ฑ…์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • Cargo.toml์˜ rust-version์ด ๋‹จ์ผ ์ง„์‹ค์›์ด๋ฉฐ, CI์˜ Verify โ€บ MSRV job์ด ์ด ๊ณ„์•ฝ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  • MSRV ์ƒํ–ฅ์ด ํ•„์š”ํ•˜๋ฉด PR์—์„œ ์ด์œ ๋ฅผ ๋ช…์‹œํ•˜๊ณ , Cargo.toml, CI, CHANGELOG๋ฅผ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์šฉ ๊ธฐ๋ณธ ํˆด์ฒด์ธ์€ ๋” ์ตœ์‹ ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜ธํ™˜์„ฑ ํŒ๋‹จ ๊ธฐ์ค€์€ ์ตœ์‹  stable์ด ์•„๋‹ˆ๋ผ MSRV + CI ํ†ต๊ณผ ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค.

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

make ci          # fmt + clippy + test + deny + lint (CI์™€ ๋™์ผ)
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)

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

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/convert helpers
โ”‚   โ”œโ”€โ”€ hwpforge-bindings-py/     # Python bindings (stub)
โ”‚   โ”œโ”€โ”€ 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 ์ƒ์„ฑยท๊ฒ€์ฆยทํŽธ์ง‘ (8๊ฐœ ๋„๊ตฌ + 4 ๋ฆฌ์†Œ์Šค + 3 ํ”„๋กฌํ”„ํŠธ)
  • CLI ๋„๊ตฌ โ€” hwpforge convert doc.md doc.hwpx ํ•œ ์ค„ ๋ณ€ํ™˜ (7๊ฐœ ๋ช…๋ น์–ด)
  • HWPX ์™„์ „ ์ง€์› โ€” ์–‘์‹ ์ปจํŠธ๋กค, ๋ณ€๊ฒฝ ์ถ”์ , OLE ๊ฐ์ฒด
  • Python ๋ฐ”์ธ๋”ฉ โ€” pip install hwpforge๋กœ ์„ค์น˜, PyPI ๋ฐฐํฌ

๋ผ์ด์„ ์Šค

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

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)

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

Buy Me a Coffee

Reviews (0)

No results found