Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Changesets

This repository uses [Changesets](https://github.com/changesets/changesets) to stage npm version
updates for `@docxly/core-rs`.

Common contributor flow:

```bash
npx @changesets/cli add
```

After the changeset lands on `main`, the `Version Packages` workflow opens or updates a release PR
that:

- bumps `packages/npm-core-rs/package.json`
- syncs `packages/core-rs/Cargo.toml`
- refreshes `packages/npm-core-rs/package-lock.json`

The existing tag-based `Release` workflow still handles the actual npm publish for `v*.*.*` tags.
11 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": false,
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
5 changes: 5 additions & 0 deletions .changeset/fuzzy-cows-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@docxly/core-rs": patch
---

Stabilize HWPX generation and demo packaging, and add automated npm versioning workflow support.
44 changes: 44 additions & 0 deletions .github/workflows/version-packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Version Packages

on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: write
pull-requests: write

concurrency:
group: version-packages
cancel-in-progress: true

jobs:
version:
if: github.actor != 'github-actions[bot]'
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install workspace dependencies
run: npm install

- name: Create or update version PR
uses: changesets/action@v1
with:
version: npm run version:packages
commit: "chore: version packages"
title: "chore: version packages"
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.DS_Store
.codex/
.playwright-cli/
node_modules/
output/
packages/npm-core-rs/dist/
packages/npm-core-rs/node_modules/
packages/npm-core-rs/site-dist/
Expand Down
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 53 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

Language: [English](./README.md) · [한국어](./docs/ko/README.md) · [Docs](./docs/README.md)

`docxly/core-rs` currently measures at an 80 ms cold start and a 2 ms steady median, versus 284 ms cold and 210 ms steady for Pandoc on the summary DOCX benchmark corpus, a 105x steady-state advantage while also exposing browser-local generation and HWPX support from the same Rust core.
`docxly/core-rs` is an embeddable Rust/WASM document generation engine for DOCX and HWPX. Current benchmark details live in the comparison block below so the published README stays aligned with the checked-in benchmark dataset.

The project is developed with a TDD-first workflow. The current milestone implements the DOCX rich slice and an approved HWPX baseline backed by manually validated golden fixtures.

Expand Down Expand Up @@ -58,7 +58,7 @@ fs::write("output.docx", docx)?;
## Why docxly

- Build document generation directly into a product instead of shelling out to a converter.
- On the current summary corpus, generate complex DOCX output in `2 ms` steady median versus Pandoc's `210 ms`, with `80 ms` versus `284 ms` cold start.
- Keep benchmark numbers in the checked-in comparison block below instead of scattering stale point-in-time claims through the README.
- Use the same Rust core across Node, browser, and HWPX workflows.
- Start with DOCX today and expand into HWPX from the same repository.
- Ship deterministic outputs backed by fixture-driven tests and normalized archive checks.
Expand All @@ -69,7 +69,7 @@ Try the browser demo on GitHub Pages:

- https://docxly.github.io/core-rs/

The live page uses the published WASM wrapper and downloads a real `.docx` file directly in the browser.
The live page uses the published WASM wrapper and downloads real `.docx` and `.hwpx` files directly in the browser.

<!-- comparison:start -->

Expand All @@ -81,21 +81,25 @@ Use docxly when document generation must live inside a Node service, browser wor

| Corpus | docxly cold | Pandoc cold | docxly steady | Pandoc steady | Speed ratio |
| --- | --- | --- | --- | --- | --- |
| Small | 66 ms | 539 ms | 1 ms | 249 ms | 369.84x |
| Medium | 80 ms | 284 ms | 2 ms | 126 ms | 64.85x |
| Large | 88 ms | 219 ms | 4 ms | 210 ms | 57.60x |
| Summary | 80 ms | 284 ms | 2 ms | 210 ms | 105.00x |

| Capability | docxly | Pandoc |
| --- | --- | --- |
| Embeddable in app | Yes, library-first for Node and browser bundlers | CLI-first with process invocation |
| Browser-local generation | First-party browser package and WASM path | Possible through pandoc.wasm, not the primary npm workflow |
| npm distribution | Published package | Not a first-party npm package |
| HWPX generation | Supported in the Rust core | Not supported |
| Broad format conversion | Focused on DOCX and HWPX generation | Wide multi-format conversion |
| DOCX reference-template workflow | Not a reference.docx workflow | Supported via reference.docx |

Measured on darwin 25.2.0 / arm64 at 2026-03-10T14:29:12.752Z with Node v23.7.0 and Pandoc 3.9.
| Small | 63 ms | 454 ms | 1 ms | 253 ms | 379.53x |
| Medium | 83 ms | 306 ms | 2 ms | 95 ms | 60.33x |
| Large | 83 ms | 192 ms | 4 ms | 193 ms | 53.92x |
| Summary | 83 ms | 306 ms | 2 ms | 193 ms | 96.50x |

| Capability | DOCX | HWPX strict | HWPX compat |
| --- | --- | --- | --- |
| Headings and paragraphs | Yes | Yes | Yes |
| Inline emphasis, strong, code, links | Yes | Yes | Yes |
| Ordered lists up to depth 2 | Yes | No | Yes, semantic contract |
| Unordered lists up to depth 2 | Yes | Yes | Yes |
| Tables | Yes | Yes | Yes |
| `data:` URI images | Yes | No | Degraded fallback to alt text |
| Unsupported HTML | Strict: error, compat: literal text fallback | Error | Degraded literal text fallback |
| Footnotes, task lists, math | Strict: error, compat: visible text fallback | Error | Degraded visible text fallback |
| Deep nested lists | Strict: error, compat: plain text fallback | Error | Degraded plain text fallback |
| HWPX API in npm | N/A | Experimental API | Experimental API |

Measured on darwin 25.2.0 / arm64 at 2026-03-11T08:19:00.589Z with Node v23.7.0 and Pandoc 3.9.

- This benchmark measures DOCX generation only and does not compare HWPX.
- The numbers above come from an offline Node environment and are not browser runtime timings.
Expand Down Expand Up @@ -126,11 +130,11 @@ Measured on darwin 25.2.0 / arm64 at 2026-03-10T14:29:12.752Z with Node v23.7.0

- Implemented: Markdown parser, internal shared intermediate model, deterministic DOCX packaging
- Implemented: fixture-driven integration tests with normalized hash comparison
- Implemented: strict/fallback handling for unsupported HTML, non-data images, and deep nested lists
- Implemented: strict/fallback handling plus report APIs for unsupported HTML, images, task lists, math, and deep nested lists
- Implemented: approved HWPX baseline backed by manually validated fixtures
- Current HWPX CI gates use these approved fixtures: `core-paragraph`, `blockquote-basic`, `code-block-basic`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`, `list-basic`, `list-nested-depth-2`, `style-typography`, `style-centered-layout`, and `style-brand-color`
- Provisional and quarantined HWPX artifacts are excluded from the release gate
- HWPX style options currently apply to body paragraphs and heading paragraphs; future block types such as lists and tables may add more paragraph categories
- Approved HWPX fixtures are the release gate; provisional and quarantined artifacts are not
- Approved fixture status is separate from strict/compat support claims; ordered lists are compat-only, while tables are supported in strict mode
- Package-specific details such as the current approved fixture set and Rust API notes live in `packages/core-rs/README.md` and `packages/core-rs/src/generators/hwpx/docs/README.md`

## Supported Markdown Today

Expand Down Expand Up @@ -159,6 +163,7 @@ Current behavior:
## Public API

The Rust crate intentionally exposes only the high-level API. Parser, model, generator, and utility modules are internal implementation details.
Repository-level guidance stays high level here; concrete Rust API details live in `packages/core-rs/README.md`, and npm/browser usage lives in `packages/npm-core-rs/README.md`.

Recommended first flow:

Expand All @@ -178,36 +183,33 @@ let docx = generate_docx(
fs::write("output.docx", docx)?;
```

Current options:

- `title: Option<String>`
- `author: Option<String>`
- `strict_mode: bool`
- `style: HwpxStyleOptions` on `HwpxOptions` only

Notes:

- `generate_docx` returns a deterministic `.docx` archive as `Vec<u8>`
- `analyze_markdown`, `generate_docx_with_report`, and `generate_hwpx_with_report` surface strict/compat compatibility issues without widening the core generation API
- `generate_hwpx` targets the approved HWPX baseline reproduced by the committed golden fixtures
- `HwpxStyleOptions` currently supports document-level body/heading font, body/heading size, text/link/heading color, and paragraph alignment overrides
- Custom HWPX fonts are best-effort only; the current HWPX path records font family names but does not embed font binaries
- internal modules such as parser/model/generator helpers are not part of the public contract
- Internal modules such as parser/model/generator helpers are not part of the public contract

## HWPX Supported Today

The current HWPX path is narrower than the DOCX rich slice.

- approved baseline: `core-paragraph`, `blockquote-basic`, `code-block-basic`, `core-heading`, `core-inline-style`, `core-link-text`, `core-mixed`
- approved list baseline: `list-basic`, `list-nested-depth-2`
- approved style baseline: `style-typography`, `style-centered-layout`, `style-brand-color`
- supported content today:
- paragraphs
- headings
- visible-text emphasis/strong/code/link rendering inside the approved compatibility contract
- document-level HWPX style overrides
- not yet part of the approved HWPX baseline:
- approved fixture details: see `packages/core-rs/README.md` and `packages/core-rs/src/generators/hwpx/docs/README.md`
- strict-supported today:
- paragraphs and headings
- blockquotes
- visible-text emphasis/strong/code/link rendering
- unordered lists up to depth 2
- tables
- document-level HWPX style overrides
- compat-only semantic support:
- ordered lists up to depth 2
- compat degraded fallback:
- images
- unsupported rich blocks such as code blocks and thematic breaks
- deep nested lists

Example HWPX generation:

Expand Down Expand Up @@ -244,8 +246,8 @@ The repository also contains an npm package at `packages/npm-core-rs/`.

- package name: `@docxly/core-rs`
- runtime target: Node + Browser
- public npm API: `generateDocx(markdown, options) -> Promise<Uint8Array>`
- HWPX is intentionally not exposed in npm v0.x even though the Rust crate now implements the HWPX core subset
- public npm APIs: `generateDocx`, `generateHwpx`, `analyzeMarkdown`, `generateDocxWithReport`, `generateHwpxWithReport`
- HWPX is exposed as an experimental API in npm, with support differing between strict and compat mode

## Rust Crate Status

Expand Down Expand Up @@ -282,6 +284,7 @@ npm run test:web
npm run demo
npm run build:pages
npm run test:all
npm run changeset:add
```

Live demo URL:
Expand All @@ -295,6 +298,14 @@ What each command does:
- `demo`: builds the Pages artifact and serves the browser demo locally
- `build:pages`: creates the static GitHub Pages artifact at `packages/npm-core-rs/site-dist`
- `test:all`: runs Rust lint, Rust tests, and web smoke tests from one root entrypoint
- `changeset:add`: creates a release note entry that feeds the automated npm version PR workflow

## Release Flow

- Add a changeset for npm-facing changes with `npm run changeset:add`
- When that changeset lands on `main`, the `Version Packages` workflow opens or updates a version PR
- That PR updates `packages/npm-core-rs/package.json`, syncs `packages/core-rs/Cargo.toml`, and refreshes `packages/npm-core-rs/package-lock.json`
- Publishing still happens from the existing tag-based `Release` workflow when a matching `v*.*.*` tag is pushed

## Running Tests

Expand Down Expand Up @@ -359,7 +370,7 @@ hash.txt
- `packages/core-rs/src/generators/hwpx/docs/schema-md/index.md`
- `packages/core-rs/src/generators/hwpx/reference/paragraph-only`

These files are the repository-level reference corpus for HWPX work. They combine curated implementation notes, Markdown conversions of Hancom reference material, and the local approved/reference fixtures used to keep the package contract stable.
These files are contributor-facing HWPX references. They combine curated implementation notes, Markdown conversions of Hancom reference material, and the local approved/reference fixtures used to keep the package contract stable.

## Development Notes

Expand Down
6 changes: 3 additions & 3 deletions docs/ko/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Pandoc이 다양한 형식 사이를 변환하는 범용 도구라면, `docxly`

## 핵심 수치

- 현재 요약 벤치마크 기준 `docxly`는 `80 ms cold / 2 ms steady`
- 같은 코퍼스에서 Pandoc은 `284 ms cold / 210 ms steady`
- 반복 실행 구간에서는 `105x` 빠른 수치가 측정됐습니다.
- 최신 benchmark 수치는 루트 README의 comparison block과 npm comparison dataset을 기준으로 함께 갱신됩니다.
- 고정 수치만 따로 인용하기보다, 같은 저장소에 커밋된 비교 데이터와 caveat를 함께 보는 것이 안전합니다.
- 현재 benchmark는 DOCX generation only, offline Node 환경 기준입니다.

## 언제 docxly를 선택해야 하나

Expand Down
7 changes: 4 additions & 3 deletions docs/ko/benchmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@

## 현재 요약 벤치마크

- `docxly`: `80 ms cold / 2 ms steady`
- `Pandoc`: `284 ms cold / 210 ms steady`
- 반복 실행 기준 `105x`
- 최신 수치는 루트 README의 comparison block과 `packages/npm-core-rs/demo/comparison-data.json`을 기준으로 봅니다.
- 이 문서에는 고정 숫자를 복제하지 않고, 어떤 환경에서 어떤 의미의 수치인지 해석만 남깁니다.

측정 환경은 다음과 같습니다.

- 환경: `darwin 25.2.0 / arm64`
- Node: `v23.7.0`
- Pandoc: `3.9`
- 기준: 복잡한 DOCX 벤치마크 코퍼스
- 범위: DOCX generation only

## 해석

Expand All @@ -27,6 +27,7 @@
- `Pandoc`: process startup 포함
- steady median은 warm-up 이후 반복 생성 비용을 뜻합니다.
- 이 수치는 브라우저 실측이 아니라 오프라인 Node 벤치마크 결과입니다.
- HWPX 비교나 브라우저 런타임 수치로 일반화하면 안 됩니다.

## 언제 Pandoc이 더 적합한가

Expand Down
2 changes: 1 addition & 1 deletion docs/ko/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@

기본 설치 근거 문구:

- `Install the embeddable DOCX engine that measured 105x faster than Pandoc on the summary benchmark.`
- `Install the embeddable DOCX engine backed by the shared benchmark dataset.`

금지:

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"build:pages": "npm run build:pages -w @docxly/core-rs",
"benchmark:pandoc": "npm run benchmark:pandoc -w @docxly/core-rs",
"benchmark:pandoc:check": "npm run benchmark:pandoc:check -w @docxly/core-rs",
"changeset:add": "npx @changesets/cli@2.29.7 add",
"demo": "npm run demo -w @docxly/core-rs",
"test:web": "npm run build:web && npm run test:smoke -w @docxly/core-rs",
"test:rust": "cargo test -p core-rs",
"lint:rust": "cargo clippy -p core-rs --all-targets -- -D warnings",
"test:all": "npm run lint:rust && npm run test:rust && npm run test:web"
"test:all": "npm run lint:rust && npm run test:rust && npm run test:web",
"version:packages": "npx @changesets/cli@2.29.7 version && node ./packages/npm-core-rs/scripts/sync-version.mjs && npm install --package-lock-only -w @docxly/core-rs"
}
}
Loading
Loading