hwpforge-foundation 0.1.0

Foundation types for HwpForge (HwpUnit, Color, Interned IDs, Branded Index)
Documentation
# HwpForge ๐Ÿ”ฅ

> **Rust๋กœ ํ•œ๊ธ€(HWP/HWPX) ๋ฌธ์„œ๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ œ์–ด**
>
> [Hancom]https://www.hancom.com/ ํ•œ๊ธ€ ํŒŒ์ผ ์ฝ๊ธฐ, ์“ฐ๊ธฐ, ๋ณ€ํ™˜

[![CI](https://img.shields.io/github/actions/workflow/status/ai-screams/HwpForge/ci.yml?branch=main&label=CI&logo=github)](https://github.com/ai-screams/HwpForge/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/hwpforge.svg?logo=rust)](https://crates.io/crates/hwpforge)
[![docs.rs](https://img.shields.io/docsrs/hwpforge?logo=docs.rs)](https://docs.rs/hwpforge)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE-MIT)
[![MSRV](https://img.shields.io/badge/MSRV-1.88+-orange.svg?logo=rust)](Cargo.toml)
[![codecov](https://img.shields.io/badge/coverage-92.65%25-brightgreen.svg?logo=codecov)](https://github.com/ai-screams/HwpForge)
[![Lines of Code](https://img.shields.io/badge/LOC-~49%2C200-informational.svg)](https://github.com/ai-screams/HwpForge)
[![Tests](https://img.shields.io/badge/tests-1%2C510_passed-success.svg?logo=checkmarx)](https://github.com/ai-screams/HwpForge)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg?logo=rust)](https://github.com/ai-screams/HwpForge)

<div align="center">
<img src="assets/banner-main.png" alt="HwpForge Banner" width="600">
</div>

---

## HwpForge๋ž€?

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

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

- **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`์— ์ถ”๊ฐ€:

```toml
[dependencies]
hwpforge = "0.1"
```

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

```rust
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๋กœ ์ธ์ฝ”๋”ฉ

```rust
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 ๋””์ฝ”๋”ฉ

```rust
use hwpforge::hwpx::HwpxDecoder;

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

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

```rust
use hwpforge::md::MdDecoder;
use hwpforge::hwpx::{HwpxEncoder, HwpxStyleStore};

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

## Feature Flags

| Feature | ๊ธฐ๋ณธ๊ฐ’ | ์„ค๋ช…                 |
| ------- | ------ | -------------------- |
| `hwpx`  | Yes    | HWPX encoder/decoder |
| `md`    | โ€”      | Markdown โ†” Core ๋ณ€ํ™˜ |
| `full`  | โ€”      | ๋ชจ๋“  ๊ธฐ๋Šฅ ํฌํ•จ       |

```toml
# Markdown ์ง€์› ํฌํ•จ
hwpforge = { version = "0.1", 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                |

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

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '14px', 'lineColor': '#BDBDBD'}}}%%
flowchart TD
    subgraph formats["ํฌ๋งท"]
        HF(["๐Ÿ“„ .hwpx<br/>ํ•œ๊ธ€ ํŒŒ์ผ"]):::file
        MF(["๐Ÿ“ .md<br/>Markdown"]):::file
    end

    subgraph smithy["โš’๏ธ Smithy โ€” ํฌ๋งท ๋ณ€ํ™˜๊ธฐ"]
        SH["hwpforge-smithy-hwpx"]:::smithy
        SM["hwpforge-smithy-md"]:::smithy
    end

    C["๐Ÿ”จ hwpforge-core<br/>ํฌ๋งท ๋…๋ฆฝ ๋ฌธ์„œ ๋ชจ๋ธ (IR)"]:::core
    BP["๐Ÿ“ hwpforge-blueprint<br/>YAML ์Šคํƒ€์ผ ยท ํฐํŠธ ยท ์ƒ‰์ƒ"]:::blueprint
    F["๐Ÿ”ฉ hwpforge-foundation<br/>HwpUnit ยท Color ยท Index"]:::foundation

    HF <--> SH
    MF <--> SM
    SH & SM <--> C
    BP --> SH & SM
    F --> C & BP

    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
```

**ํ•ต์‹ฌ ์›์น™**: ๊ตฌ์กฐ(Structure)์™€ ์Šคํƒ€์ผ(Style)์˜ ๋ถ„๋ฆฌ โ€” HTML + CSS์™€ ๊ฐ™์€ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.
Core๋Š” ์Šคํƒ€์ผ _์ฐธ์กฐ_(index)๋งŒ ๊ฐ€์ง€๊ณ , Blueprint๋Š” ์Šคํƒ€์ผ _์ •์˜_(ํฐํŠธ, ํฌ๊ธฐ, ์ƒ‰์ƒ)๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
Smithy compiler๊ฐ€ Core + Blueprint๋ฅผ ํ•ฉ์ณ ์ตœ์ข… ํฌ๋งท์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

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

| ์ง€ํ‘œ        | ๊ฐ’                      |
| ----------- | ----------------------- |
| ์ด LOC      | ~49,200                 |
| ํ…Œ์ŠคํŠธ      | 1,510๊ฐœ (cargo-nextest) |
| ์†Œ์Šค ํŒŒ์ผ   | 92 .rs                  |
| Crate ์ˆ˜    | 9๊ฐœ (6๊ฐœ ๋ฐฐํฌ)          |
| ์ปค๋ฒ„๋ฆฌ์ง€    | 92.65%                  |
| Clippy ๊ฒฝ๊ณ  | 0                       |
| Unsafe ์ฝ”๋“œ | 0                       |

## ๊ฐœ๋ฐœ

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

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

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

```bash
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 decoder (์˜ˆ์ •)
โ”‚   โ”œโ”€โ”€ hwpforge-bindings-py/     # Python bindings (์˜ˆ์ •)
โ”‚   โ””โ”€โ”€ hwpforge-bindings-cli/    # CLI ๋„๊ตฌ (์˜ˆ์ •)
โ”œโ”€โ”€ tests/                        # ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ + golden fixture
โ””โ”€โ”€ examples/                     # ๐Ÿ“œ ์‚ฌ์šฉ ์˜ˆ์ œ + ์ƒ์„ฑ๋œ HWPX ํŒŒ์ผ
```

## ๋กœ๋“œ๋งต

### ์ถœ์‹œ ์˜ˆ์ •

- [ ] HWP5 ์ฝ๊ธฐ โ€” ๊ตฌํ˜• ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท(`.hwp`) ๋””์ฝ”๋”
- [ ] MCP ์„œ๋ฒ„ โ€” Claude, GPT ๋“ฑ LLM์ด tool๋กœ ์ง์ ‘ HWPX ์ƒ์„ฑ
- [ ] CLI ๋„๊ตฌ โ€” `hwpforge convert doc.md doc.hwpx` ํ•œ ์ค„ ๋ณ€ํ™˜

- [ ] HWPX ์™„์ „ ์ง€์› โ€” ์–‘์‹ ์ปจํŠธ๋กค, ๋ณ€๊ฒฝ ์ถ”์ , OLE ๊ฐ์ฒด
- [ ] Python ๋ฐ”์ธ๋”ฉ โ€” `pip install hwpforge`๋กœ ์„ค์น˜, PyPI ๋ฐฐํฌ

## ๋ผ์ด์„ ์Šค

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

- Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE)
- MIT license ([LICENSE-MIT]LICENSE-MIT)

## Acknowledgements

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

- **[Hancom]https://www.hancom.com** โ€” HWPX ํฌ๋งท์˜ ๊ณต๊ฐœ ๋ฌธ์„œ์™€ [KS X 6101 (OWPML)]https://www.kssn.net/ ๊ตญ๊ฐ€ ํ‘œ์ค€์ด ์—†์—ˆ๋‹ค๋ฉด ์ด ํ”„๋กœ์ ํŠธ๋Š” ์‹œ์ž‘์กฐ์ฐจ ํ•  ์ˆ˜ ์—†์—ˆ์Šต๋‹ˆ๋‹ค. ํฌ๋งท์„ ๊ณต๊ฐœํ•ด ์ฃผ์‹  Hancom์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

- **[openhwp]https://github.com/openhwp/openhwp** โ€” Rust๋กœ HWP/HWPX๋ฅผ ๋‹ค๋ฃจ๋Š” IR ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„์—์„œ ํฐ ์˜๊ฐ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. HwpForge์˜ Core ๋ ˆ์ด์–ด๊ฐ€ ์กด์žฌํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ๊ฒƒ์€ openhwp์ด ๋จผ์ € ๊ทธ ๊ธธ์„ ๊ฑธ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

- **[hwpxlib]https://github.com/neolord0/hwpxlib** โ€” Java๋กœ ์ž‘์„ฑ๋œ ๊ฐ€์žฅ ์„ฑ์ˆ™ํ•œ HWPX ๊ตฌํ˜„์ฒด์ž…๋‹ˆ๋‹ค. ์ŠคํŽ™๊ณผ ์‹ค์ œ ๋™์ž‘์˜ ์ฐจ์ด๋ฅผ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ๊ฒฐ์ •์ ์ธ ์ฐธ๊ณ ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

- **[hwp.js]https://github.com/hahnlee/hwp.js** โ€” HWP5 ํฌ๋งท์˜ quirks์™€ edge case๋ฅผ ๊ผผ๊ผผํžˆ ๋ฌธ์„œํ™”ํ•œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท์˜ ์–ด๋‘์šด ๊ตฌ์„์„ ๋ฐํ˜€ ์ค€ ๋•๋ถ„์— ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ํฌ๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

- **[hwpx-owpml-model]https://github.com/hancom-io/hwpx-owpml-model** โ€” Hancom์ด ์ง์ ‘ ๊ณต๊ฐœํ•œ C++ OWPML ๋ชจ๋ธ ๊ตฌํ˜„์ฒด๋กœ, ์Šคํ‚ค๋งˆ ํ•ด์„์˜ ์ตœ์ข… ๊ธฐ์ค€์œผ๋กœ ์‚ผ์•˜์Šต๋‹ˆ๋‹ค.

- **Rust ์ƒํƒœ๊ณ„** โ€” [serde]https://serde.rs, [quick-xml]https://github.com/tafia/quick-xml, [pulldown-cmark]https://github.com/pulldown-cmark/pulldown-cmark, [zip]https://github.com/zip-rs/zip2 ๋“ฑ ๋›ฐ์–ด๋‚œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค ๋•๋ถ„์— HwpForge ์ „์ฒด๋ฅผ zero unsafe ์ˆœ์ˆ˜ Rust๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. Rust ์ปค๋ฎค๋‹ˆํ‹ฐ์™€ Ferris ๐Ÿฆ€์—๊ฒŒ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

- **[Claude]https://claude.ai by [Anthropic]https://www.anthropic.com** โ€” HwpForge์˜ ์„ค๊ณ„, ๊ตฌํ˜„, ํ…Œ์ŠคํŠธ, ๋ฌธ์„œํ™” ์ „ ๊ณผ์ •์—์„œ Claude Code๊ฐ€ ๊ฐœ๋ฐœ ํŒŒํŠธ๋„ˆ๋กœ ํ•จ๊ป˜ํ–ˆ์Šต๋‹ˆ๋‹ค. LLM-first๋ฅผ ํ‘œ๋ฐฉํ•˜๋Š” ํ”„๋กœ์ ํŠธ๋‹ต๊ฒŒ, AI์™€ ์‚ฌ๋žŒ์ด ํ˜‘์—…ํ•˜์—ฌ ๋งŒ๋“ค์–ด๋‚ธ ๊ฒฐ๊ณผ๋ฌผ์ž…๋‹ˆ๋‹ค.

---

<div align="center">
<img src="assets/mascot-main.png" width="260" alt="์‡ ๋ถ€๋ฆฌ (SoeBuri)">

<br/><br/>

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

</div>