워드프로세서
3a4770e 기준입니다.
WordCraft는 Word 문서를 읽고 편집하고 여러 형식으로 저장하는 기능을 Rust 크레이트로 나누고, 그 위에 여러 사용 창구를 올린 워드프로세서입니다. DOCX 입출력뿐 아니라 문단·표·머리글·각주·글꼴·줄바꿈·페이지 나누기를 직접 다룹니다. GitHub 설명의 “clean-room reimplementation”은 프로젝트가 공개 규격과 독립 구현을 지향한다는 선언입니다. 완전한 Word 호환을 보증하는 문구로 읽어서는 안 됩니다.
가장 먼저 기억할 문장은 이것입니다. 문서와 명령을 UI와 분리했기 때문에, 같은 편집 기능을 사람과 자동화 도구가 함께 사용할 수 있습니다.
원고 보관소는 doc, 교정·편집 담당자는 engine, 지면을 짜는 조판사는 layout입니다.
render는 화면용 인쇄소, pdf는 배포용 인쇄소입니다.
리본 메뉴와 CLI는 이 출판사에 주문을 넣는 서로 다른 접수 창구입니다. 접수 창구가 늘어나도 원고의 규칙과 조판법을 다시 만들 필요가 없도록 나누는 것이 핵심 설계입니다.
| 접근 방법 | 실제 진입점 | 배울 수 있는 것 |
|---|---|---|
| 데스크탑 | apps/wordcraft | 운영체제 창·클립보드·인쇄와 편집 엔진 연결 |
| 브라우저 | apps/wordcraft-web | 같은 Rust 엔진을 WASM으로 옮기는 경계 |
| 명령줄 | apps/wordcraft-cli | 창 없이 문서 변환·검사·명령 실행 |
| 에이전트 연결 | crates/mcp | 편집 기능을 구조화된 도구 호출로 공개 |
예를 들어 format.bold는 버튼 이름만이 아닙니다.
crates/engine/src/cmd/format.rs::specs()에 등록된 명령 ID이며, 단축키 Mod+B와 매개변수 설명도 함께 가집니다.
UI와 자동화가 같은 기능을 부르는 연결 고리를 이 ID에서 찾을 수 있습니다.
TrendShift Monthly #22 선정 프로젝트입니다. 2026-10-07에 생성되어 2026-10-23 GitHub 기준 스타 15,592개, 포크 4,228개를 기록했습니다. 짧은 기간에 관심을 모은 것은 확인 가능한 사실이지만, 스타가 증가한 정확한 원인을 이 수치만으로 증명할 수는 없습니다.
주목할 요인으로는 익숙한 대형 제품을 Rust로 다시 구현하는 목표, Linux·BSD·웹까지 아우르는 진입점, 그리고 CLI·MCP 자동화가 있습니다. 이는 README의 목표와 소스 구성을 근거로 한 해석입니다. 성능·호환성·인기도 사이의 인과관계를 측정한 결과는 아닙니다.
| 비교 대상 | 선택할 때 보는 축 | WordCraft에서 눈여겨볼 점 |
|---|---|---|
| Microsoft Word | 기존 문서 교환과 업무 흐름 | 편집·조판 내부를 공개 Rust 코드로 추적할 수 있음. 호환성은 별도 검증 필요 |
| LibreOffice Writer | 오픈소스 문서 편집기 | Rust 중심의 계층 구조와 명령 등록부를 학습 대상으로 삼기 좋음 |
| 웹 기반 편집기 | 브라우저 접근과 협업 흐름 | 브라우저판도 Rust 엔진과 egui 화면을 공유. 공동 편집 서비스와는 평가 축이 다름 |
| 문서 변환 도구 | 배치 변환과 자동 생성 | CLI 변환에 더해 같은 엔진의 선택·서식·검사 명령을 조합할 수 있음 |
이 비교는 제품 순위표가 아닙니다. 초보 개발자에게 중요한 장점은 화면에서 보이는 기능과 파일 형식 내부 구현을 한 저장소에서 연결해 볼 수 있다는 점입니다. 다른 제품보다 빠르거나 DOCX를 더 정확하게 처리한다는 비교 실험은 제시하지 않습니다.
docs/parity-checklist.md에는 카탈로그 기능 513개 중 498개에 명령이 있다(97.1%)고 적혀 있습니다.
반면 README의 카탈로그 수치는 약 97%, 실무 준비도는 약 60%라는 자체 추정입니다.
명령의 존재, 기능의 깊이, 실제 문서 보존은 서로 다른 지표입니다.
기능 목록에 명령이 있다는 사실은, 복잡한 Word 문서가 손실 없이 왕복 저장된다는 뜻이 아닙니다. 표를 넣는 명령이 있어도 셀 병합·페이지 분할·스타일 상속·다른 프로그램과의 교환까지 확인해야 합니다.
ROADMAP.md는 alpha라고 명시합니다.
README의 약 60% 준비도는 프로젝트의 자체 추정치이며, 외부 인증을 받은 평가 점수가 아닙니다.
97.1%는 해당 커밋의 체크리스트에 적힌 명령 연결 비율이며, 문서 호환성 평가 점수가 아닙니다.
MCP 서버가 있다는 것과 언어 모델이 내장됐다는 것은 다릅니다.
crates/mcp/src/lib.rs는 외부 클라이언트의 요청을 편집 세션에 연결합니다.
프로젝트의 문서도 외부 에이전트 제어와 앱 안의 어시스턴트를 구분합니다.
이 연결 구조를 이해하면 일반 프로그램에 자동화 인터페이스를 붙이는 방법을 배울 수 있습니다.
이 저장소의 “백엔드”는 주로 프로세스 안에서 움직이는 문서 엔진을 뜻합니다.
루트에는 package.json, pyproject.toml, go.mod, Dockerfile이 없습니다.
빌드의 중심은 Cargo.toml과 Cargo.lock입니다.
# Cargo.toml — 실제 설정 일부
[workspace]
resolver = "3"
members = ["crates/*", "apps/*", "xtask"]
[workspace.package]
version = "0.10.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
Cargo.toml의 버전은 의존성 선택 조건입니다.
예를 들어 egui = "0.36"은 정확히 0.36.0만 가리키지 않습니다.
Cargo.lock은 이 커밋이 고정한 실제 의존성 해석 결과로, 여기에는 0.36.2가 기록돼 있습니다.| 기술 | 선언 → 잠금 버전 | 이 저장소에서의 역할 |
|---|---|---|
| Rust | 최소 1.95 · edition 2024 | 문서 모델, 조판, UI, 도구를 작업 공간으로 관리 |
| serde | 1 → 1.0.229 | 문서 구조의 직렬화. derive, rc 활성화 |
| serde_json | 1 → 1.0.151 | 명령 매개변수·검사 결과·제어 메시지 |
| thiserror | 2 → 2.0.21 | 실패 이유를 오류 타입으로 표현 |
| quick-xml | 0.41 → 0.41.0 | DOCX 안의 XML 처리 |
| zip | 3 → 3.0.0 | DOCX·ODT 패키지 처리. 기본 기능을 끄고 deflate 사용 |
| chrono | 0.4.45 → 0.4.45 | 날짜 필드와 저장 시각의 변환 |
위 선언은 루트 Cargo.toml, 잠금 버전은 Cargo.lock 기준입니다.
잠금 파일에는 간접 의존성이 요구하는 다른 버전도 함께 있을 수 있습니다.
예를 들어 thiserror 1.x도 있지만, 작업 공간의 직접 선언은 2.x입니다.
| 기술 | 선언 → 잠금 버전 | 어떤 문제를 푸는가 |
|---|---|---|
| HarfRust | 0.13 → 0.13.3 | 문자열을 글꼴의 글리프와 위치 정보로 변환 |
| skrifa | 0.47 → 0.47.0 | 글꼴 메트릭과 윤곽선 읽기 |
| vello_cpu | 0.2 → 0.2.0 | 페이지 그리기 명령을 CPU에서 픽셀로 변환 |
| image | 0.25 → 0.25.10 | PNG·JPEG·WebP·GIF·BMP·TIFF 지원 기능 선택 |
| craft-pdf | =0.1.0 · Git 리비전 고정 | 공유 PDF 작성기. Krilla 그리기 기능과 연결 |
| krilla | 패치된 Git 의존성 · 잠금 0.8.2 | PDF 텍스트·도형·링크·구조 정보 출력 |
craft-pdf와 [patch.crates-io]의 krilla는
storytold/craft-libs의 리비전 c3487320b2d1de657b860b3c8abb44e1f2b32bd8을 가리킵니다.
따라서 버전 번호만 보고 일반 배포판과 완전히 같다고 가정하면 안 됩니다.
| 구성 | 실제 의존성 | 소스에서 확인할 경계 |
|---|---|---|
| UI | egui / eframe 0.36 → 0.36.2 | crates/ui-egui의 리본·캔버스·대화상자 |
| GPU 표시 | 잠금 파일의 wgpu 30.0.1 | eframe의 wgpu 기능. CPU 페이지 렌더링과 역할이 다름 |
| 파일 대화상자 | rfd 0.17 → 0.17.2 | 운영체제·브라우저가 제공하는 파일 선택 연결 |
| 브라우저 연결 | wasm-bindgen 0.2 → 0.2.129 | Rust와 브라우저 기능 사이의 바인딩 |
| 웹 API | web-sys 0.3 → 0.3.106 | Canvas·클립보드·Storage·IndexedDB 등 선택 기능 |
웹 의존성은 apps/wordcraft-web/Cargo.toml에 있습니다.
apps/wordcraft-web/src/web.rs::start()는 Canvas를 찾아 eframe::WebRunner를 시작합니다.
React 컴포넌트를 공유하는 방식이 아니라, Rust UI와 엔진을 함께 재사용하는 방식입니다.
| 파일 | 실제 설정 | 학습 포인트 |
|---|---|---|
.cargo/config.toml | xtask = "run --package xtask --" | 개발용 명령도 Rust 프로그램으로 관리 |
.github/workflows/ci.yml | ubuntu-24.04, 제한 60분, stable Rust | PR과 main 푸시에서 동일 관문 실행 |
xtask/src/main.rs | fmt, clippy, test, assets, layers, wasm | 테스트 외에 저작물 출처와 의존 방향까지 검사 |
apps/wordcraft-web/Trunk.toml | 포트 8771, 127.0.0.1, filehash = true | Rust 웹 빌드와 정적 결과물 관리 |
Cargo.toml release | opt-level = 3, lto = "thin", codegen-units = 16 | 릴리스 빌드 설정을 중앙에서 공유 |
CI는 wasm32-unknown-unknown 타깃과 clippy·rustfmt를 설치하고 cargo xtask ci를 호출합니다.
추가 글꼴 저장소 craft-fonts도 고정 리비전으로 받아 CRAFT_FONTS_DIR로 전달합니다.
“같은 코드”에 더해 “같은 글꼴”도 레이아웃 재현성에 영향을 준다는 예입니다.
웹과 데스크탑의 재사용은 파일 접근과 운영체제 기능을 경계 밖으로 밀어내는 설계에서 시작됩니다. “Rust로 썼다”는 사실만으로 모든 플랫폼에서 같은 동작이 자동 보장되지는 않습니다.
도식의 화살표는 주요 데이터 흐름입니다. 모든 상자의 직접 의존성을 나열한 그래프는 아닙니다. 엔진은 레이아웃과 출력 기능을 사용하지만, 문서 모델이 UI를 알아야 할 이유는 없습니다. 이 방향을 유지하면 창 없이도 변환하고 검사할 수 있습니다.
crates/engine/src/lib.rs의 CommandSpec은 기능의 ID·이름·위치·단축키·매개변수 설명과 실행 함수를 묶습니다.
Registry::new()는 명령 목록과 ID 조회용 HashMap을 만듭니다.
crates/engine/src/cmd/mod.rs::registry()가 영역별 specs() 결과를 합칩니다.
// crates/engine/src/lib.rs — CommandSpec의 필드 일부
pub id: &'static str,
pub params: &'static str,
pub mutates: bool,
pub enabled: fn(&Session) -> Option<&'static str>,
pub run: fn(&mut Session, &Value) -> CmdResult,
enabled는 실행 불가 이유가 있으면 Some(reason)을 돌려줍니다.
mutates는 문서를 바꾸는 명령인지 표시합니다.
CommandSpec::new()는 기본적으로 변경 명령을 만들며, .pure()는 그 표시를 끕니다.
이 메타데이터는 실행 취소와 보호 검사에 사용되므로 정확해야 합니다.
crates/engine/src/cmd/format.rs::specs()에서 format.bold를 찾습니다.Mod+B, 매개변수 {"value"?: bool}, 실제 처리 함수 연결을 봅니다.Session::run()이 ID를 조회하고 enabled를 검사하는 부분으로 이동합니다.crates/mcp/src/lib.rs::Headless::answer()의 engine.execute 분기도 같은 함수를 호출하는지 확인합니다.{"value"?: bool}은 사람이 읽는 매개변수 설명 문법입니다. 그 자체가 유효한 JSON 요청은 아닙니다.
실제 호출은 {"value": true}처럼 씁니다.
또한 UI는 대화상자를 먼저 띄울 수 있습니다. WordApp::run()의 화면 처리를 거쳐 엔진으로 들어가는 흐름과,
곧바로 Session::run()을 부르는 흐름을 구분해야 합니다.
명령 등록부는 식당 메뉴판과 주문 번호를 합친 것입니다. 홀 직원·전화 주문·키오스크가 같은 “굵게” 주문 번호를 사용하면 조리법은 한곳에 둘 수 있습니다. 다만 메뉴 설명이 있다는 것만으로 재료 검사가 끝나지는 않습니다. 매개변수 검증은 실행 경로에서도 필요합니다.
crates/doc/src/lib.rs의 문서 본문은 다음 타입으로 표현됩니다.
// 실제 문서 블록 타입
pub enum Block {
Para(Paragraph),
Table(Table),
}
pub type Blocks = Vec<Arc<Block>>;
Arc는 여러 곳이 같은 값을 참조하도록 돕습니다.
문서를 복제하면 블록을 가리키는 참조를 공유하고, 수정할 때 Document::para_mut() 등에서
Arc::make_mut를 통해 공유 중인 블록을 필요한 만큼 복제합니다.
이것이 쓰기 시 복사입니다.
따라서 “문서 복제는 무료”라고 말하면 부정확합니다. 블록 안의 내용은 공유할 수 있어도 바깥 벡터와 일부 메타데이터의 복제 비용은 남습니다. 이 설계의 이점은 매번 모든 문단과 큰 그림 바이트까지 통째로 복사하는 일을 줄인다는 데 있습니다.
crates/engine/src/session.rs의 Undo는 문서뿐 아니라 선택 영역·다음 입력에 적용할 서식도 보관합니다.
Session::undo()는 저장된 상태를 현재 상태와 교체하고, 현재 상태를 다시 실행 스택에 넣습니다.
같은 파일의 MAX_UNDO는 500입니다.
Session::run()은 변경 명령을 실행하기 전 문서·선택·undo/redo 스택 등을 보관합니다.
처리 함수는 catch_unwind 안에서 호출되며, 일반 오류나 잡힌 panic으로 실패하면 보관한 편집 상태를 복구하는 분기로 갑니다.
문서 보호의 사후 검사에서 거부되는 경우도 고려합니다.
이 경로는 문서와 관련 편집 상태의 복구 장치입니다.
이미 수행한 외부 파일 쓰기나 프로세스 종료, 메모리 부족까지 모두 되돌린다고 해석하면 안 됩니다.
catch_unwind도 모든 종류의 프로그램 종료를 잡는 기능은 아닙니다.
crates/layout/src/lib.rs::layout()은 Document를 DocLayout으로 바꿉니다.
DocLayout에는 페이지 목록과 문단 위치를 찾는 인덱스가 있습니다.
그다음 crates/layout/src/display.rs::page_display()가 Vec<Draw>를 만듭니다.
Draw는 “이 위치에 글리프·선·도형을 그린다”는 공통 언어입니다.
crates/render/src/lib.rs::render_page()는 page_layers()를 이용해 픽셀을 만들고,
crates/pdf/src/lib.rs::export() 쪽은 page_display()를 이용해 PDF를 만듭니다.
화면 이미지를 PDF 한 장에 붙이는 방식만으로 처리하지 않습니다.
속도 쪽의 핵심은 LayoutCache입니다.
내부의 Key는 문단 리비전뿐 아니라 폭·목록 라벨·표·각주·그리드·변경 내용 표시 등 배치 조건을 담습니다.
Ctx::para_labelled()는 키가 있으면 기존 Arc<ParaLayout>을 재사용하고,
없으면 para::layout_para()를 호출합니다. hits와 misses도 셉니다.
캐시를 잘 만드는 일은 결과를 저장하는 일보다, 결과를 바꾸는 모든 입력을 찾아내는 일에 가깝습니다. 글자가 같아도 문단 폭이나 글꼴이 달라지면 줄바꿈 결과를 다시 계산해야 합니다.
파일 형식은 crates/engine/src/io.rs::open_bytes()와 save_bytes()에서 연결됩니다.
실제 분기는 read_bytes()와 crates/engine/src/io_ext.rs::open()·save() 등을 거칩니다.
확장자만 믿지 않고 내용의 시그니처가 모순되는 경우도 분기하며, 최종적으로 공통 Document를 주고받습니다.
MCP도 같은 생각입니다. crates/mcp/src/backend.rs::Backend는
call()·has_ui()·describe()라는 경계를 정의합니다.
Headless는 프로세스 내부의 세션, Remote는 앱의 루프백 제어 포트에 연결됩니다.
도구를 정의하는 쪽이 두 구현의 연결 방식을 매번 알 필요가 없습니다.
파일 어댑터에는 크기 제한도 중요합니다.
crates/docx/src/package.rs::Package::open_limited()는 선언된 크기뿐 아니라 실제 압축 해제 크기도 확인합니다.
| 상수 | 값 | 방어하려는 문제 |
|---|---|---|
MAX_PART | 256 MiB | 단일 압축 항목이 지나치게 커지는 문제 |
MAX_TOTAL | 512 MiB | 전체 압축 해제 데이터가 계속 쌓이는 문제 |
MAX_ENTRIES | 20,000개 | 아주 많은 항목을 검사하는 비용 |
이 값은 기본 DOCX 패키지 읽기의 예산이며, 앱 전체의 최대 메모리 사용량은 아닙니다.
take(room + 1)로 한도보다 한 바이트 더 읽고 초과 여부를 검사하는 방식까지 살펴보세요.
파일을 여는 기능은 정상 문서를 지원하는 일과, 비정상 입력의 비용을 제한하는 일을 함께 해야 합니다.
작업 공간은 라이브러리 크레이트 16개, 앱 3개, 개발 도구 xtask로 구성됩니다.
루트 설정을 포함한 Cargo.toml은 21개입니다.
docs/architecture.md에는 15개 크레이트라고 적혀 있지만, 그 문서는 과거 커밋의 구조를 설명합니다.
| 파일 종류 | 파일 수 | 물리적 줄 수 |
|---|---|---|
Rust .rs | 452 | 292,913줄 |
Shell .sh | 8 | 669줄 |
PowerShell .ps1 | 2 | 261줄 |
Python .py | 1 | 158줄 |
HTML .html | 1 | 20줄 |
| JavaScript / TypeScript | 0 | 해당 확장자 파일 없음 |
커밋 3a4770e의 저장소 파일에서 확장자별 줄바꿈을 센 값입니다.
주석·빈 줄·테스트·예제·빌드 도구를 포함하며, 의존성 저장소와 빌드 산출물은 포함하지 않습니다.
별도로 번역 자료 .tsv는 26개, 68,192줄입니다. 번역 행 수를 Rust 코드 줄 수에 더하지 않았습니다.
crates/ui-egui는 Rust 69,007줄, crates/engine은 63,196줄,
crates/docx는 34,669줄, crates/layout은 30,877줄입니다.
“순수 Rust”라는 중심 기술과 별개로, 제품을 완성하려면 UI·형식 호환·테스트에 많은 코드가 필요하다는 사실을 보여 줍니다.
crates/geom/src/lib.rs: 길이 단위를 이해합니다.crates/doc/src/lib.rs: Block, Document, Pos를 봅니다.crates/engine/src/cmd/format.rs: 기능 한 개가 등록되는 모습을 읽습니다.crates/engine/src/session.rs: 그 명령의 실행·실패·취소를 따라갑니다.crates/layout/src/display.rs: 편집 결과가 그리기 목록이 되는 경계를 봅니다.큰 파일 하나를 처음부터 끝까지 읽기보다, “굵게 한 번” 또는 “TXT를 PDF로 저장”을 골라 관련 파일만 따라가세요. 이해한 경로에 타입 이름과 데이터 변화를 적으면 다음 기능을 읽을 때 지도가 됩니다.
Block이 Para와 Table로 나뉘면,
호출자는 “이 블록이 어떤 종류인가”를 match로 분기합니다.
Option은 값의 부재를, Result는 실패할 수 있는 작업을 드러냅니다.
잘못된 문서 위치를 받는 Document::para_mut()가 어떤 오류를 돌려주는지부터 읽어 보세요.
Document → Blocks → Block 관계를 그림으로 표현하고,
StoryRef가 본문·머리글·각주 같은 글의 영역을 어떻게 구분하는지 적습니다.
화면 좌표와 문서 위치 Pos를 같은 값으로 취급하면 왜 곤란한지도 설명해 보세요.
crates/fonts/src/lib.rs::shape_run()은 방향 정보를 포함해 글리프를 배치하는 경로입니다.
crates/fonts/src/word.rs::resolve()는 요청한 글꼴·굵기·기울임을 실제 글꼴로 연결합니다.
같은 파일의 대체 목록에는 Calibri에 대응하는 Carlito, Cambria에 대응하는 Caladea 등이 있습니다.
실습 아이디어: 가나다, 결합 악센트가 있는 문자열, 영문과 숫자가 섞인 문장을 고릅니다.
UTF-8 바이트 수·유니코드 문자 수·사용자가 인식하는 글자 수가 같은지 생각하고,
crates/doc/src/cluster.rs와 crates/layout/src/tests_ligature.rs에서 관련 경계를 찾아보세요.
DOCX는 본문 XML 하나만 있는 파일이 아닙니다.
스타일·번호 매기기·그림·관계 정보가 함께 들어 있습니다.
crates/docx/src/read/mod.rs::read()에서 모델로 읽고,
crates/docx/src/write/mod.rs::write_as()에서 문서 종류(Flavor)에 맞는 패키지로 씁니다.
Document::passthrough는 원래 형식의 일부 데이터를 바이트로 보관합니다.
매크로 프로젝트를 보관하는 경로도 있지만, 보관과 실행 지원은 별개입니다.
실습 아이디어: “보여 주기”, “편집하기”, “모르는 데이터를 유지하기”를 세 열로 나누어 지원 상태를 기록하세요.
crates/engine/src/tests.rs::hostile_params_never_panic()는 명령에 엉뚱한 매개변수를 넣습니다.
null, 잘못된 타입, 매우 큰 크기와 좌표 등이 포함됩니다.
파일 작업 일부는 제외하고, 명령 실행 뒤 선택 영역 보정과 레이아웃까지 이어 갑니다.
Rust 파일 전체의 #[test] 표기는 3,928회,
proptest! { 형태의 속성 테스트 매크로 호출은 16곳입니다(줄 시작의 호출만 집계하며, 네임스페이스 접두사는 포함하고 문자열 안의 예시는 제외).
이는 정적 텍스트 집계이며, 조건부 컴파일·매크로를 반영한 실행 테스트 수나 통과 결과가 아닙니다.
README의 약 800개 테스트라는 서술과는 집계 시점·방법을 구별해야 합니다.
본문 텍스트, 선택 영역, 실행 취소 깊이를 먼저 비교하세요. “오류 문자열이 나왔다”만 확인하는 테스트보다 “거부된 작업이 문서를 바꾸지 않았다”는 성질이 중요합니다. 단, 읽기·쓰기처럼 세션 밖으로 나가는 부작용은 별도 검증이 필요합니다.
xtask/src/layers.rs::TABLE은 크레이트의 계층을 지정합니다.
UI_MIN_LAYER = 6과 UI_CRATES 목록은 낮은 계층에 UI 의존성이 스며드는 것을 검사합니다.
같은 계층의 예외는 INTRA_LAYER_ORDER처럼 명시합니다.
오래된 설명 문서보다 실제 검사표를 따라가야 하는 이유이기도 합니다.
아키텍처 규칙을 사람의 기억에만 맡기지 않고, 어기면 검사에서 드러나도록 만드는 방법을 배울 수 있습니다. 자신의 작은 프로젝트에서도 “도메인 모델은 UI를 가져오지 않는다” 같은 한 가지 규칙부터 적용해 볼 수 있습니다.
소스와 배포 문서는 여러 운영체제를 다루지만, 모든 기기에 공통인 공식 최소 RAM·CPU 수치는 제시하지 않습니다. 다음 표는 확인 가능한 조건과 학습용 제안을 나눈 것입니다. 압축된 문서의 파일 크기만으로 실행에 필요한 메모리를 판단할 수는 없습니다.
| 용도 | 확인 가능한 조건 | 학습 환경 제안 |
|---|---|---|
| 소스 읽기 | 텍스트 편집기와 파일 검색 도구로 시작 가능 | 일반 노트북으로 타입·명령 경로부터 학습 |
| Rust 빌드 | rust-version = "1.95", Cargo | 여러 크레이트의 컴파일 결과를 둘 SSD 여유 공간 확보 |
| 데스크탑 | macOS·Windows·Linux·BSD용 구성. eframe/wgpu 사용 | 자신의 OS·아키텍처에 맞는 배포판과 그래픽 드라이버 확인 |
| 브라우저 | WebGPU 또는 WebGL2를 사용할 수 있는 환경 | 최신 브라우저에서 작은 문서로 시작 |
| 웹 개발 | Trunk와 wasm32-unknown-unknown 타깃 | 데스크탑 빌드와 별도로 웹 경계의 기능 차이 확인 |
| 대용량 문서 | 페이지·그림·폰트·스냅샷에 따라 비용 증가 | 작은 문서부터 페이지 수·그림 수를 한 번에 하나씩 늘리기 |
.github/workflows/ci.yml은 pkg-config,
libxkbcommon-dev, libwayland-dev, libx11-dev,
libxcursor-dev, libxrandr-dev, libxi-dev,
libgtk-3-dev, libfontconfig1-dev, libfreetype-dev,
mesa-vulkan-drivers를 설치합니다.
이는 Ubuntu CI의 개발 환경 목록이며, 모든 배포판에서 같은 이름의 패키지를 요구한다는 뜻은 아닙니다.
crates/render/src/lib.rs::default_threads()는 데스크탑에서 사용 가능한 병렬도에 따라
작업 스레드를 1~8개 범위로 고릅니다. 병렬도 조회 실패 시 기본값은 2이고, WASM 분기는 0입니다.
즉, 웹판은 같은 방식의 렌더 작업 스레드를 만들지 않습니다.
페이지의 픽셀화는 vello_cpu, UI의 표시 경로는 egui/wgpu가 담당합니다.
CPU 렌더러를 쓴다고 그래픽 환경이 무관한 것은 아닙니다.
apps/wordcraft/src/graphics.rs에는 Windows의 그래픽 백엔드와 드라이버 문제를 다루는 코드도 있습니다.
글꼴이 없으면 대체 글꼴을 사용하고, 글자 폭이 달라지면 줄 수와 페이지 수가 바뀔 수 있습니다. 코드의 대체 목록은 호환성을 높이는 전략이지 모든 문서의 동일한 페이지 나누기를 보증하는 장치는 아닙니다. 문서를 비교할 때는 파일뿐 아니라 설치 글꼴과 화면 배율도 기록하세요.
압축된 DOCX 파일 크기는 접힌 이삿짐 상자의 크기와 같습니다. 압축을 풀고 그림을 픽셀로 만들고 이전 편집 상태를 보관하면 더 넓은 공간이 필요합니다. 512 MiB 압축 해제 제한을 RAM 512 MiB면 충분하다는 의미로 읽으면 안 됩니다.
첫 과제는 소스 읽기만으로 완성할 수 있습니다. 실행 명령이 있는 과제는 해당 버전의 CLI를 준비한 개인 실습 환경을 전제로 합니다. 문서 예시는 학습자가 작성할 실습 입력이며, 실행 성공이나 출력 모양을 미리 보증하는 결과물이 아닙니다.
목표: 기능 이름에서 데이터 변경까지 소스를 따라갑니다.
읽을 곳: crates/engine/src/cmd/format.rs::specs(),
crates/engine/src/cmd/mod.rs::registry(), crates/engine/src/session.rs::Session::run(),
crates/mcp/src/lib.rs::Headless::answer(). 앞의 세 경로는 crates/engine/src/ 아래입니다.
할 일: ID·단축키·매개변수·오류 검사 위치를 표로 적고, 선택 영역이 있을 때와 커서만 있을 때 서식 적용 대상이 어떻게 달라질지 추적합니다.
완료 기준: “GUI와 MCP가 같은 엔진 명령을 부른다”를 파일과 함수 네 곳으로 설명하고, 버튼의 대화상자 처리와 엔진 처리를 구별할 수 있습니다.
목표: 사람의 조작을 재현 가능한 입력 파일로 바꿉니다.
읽을 곳: apps/wordcraft-cli/src/script.rs::parse()와
apps/wordcraft-cli/src/main.rs의 run 하위 명령 처리입니다.
아래 내용을 lesson.json으로 두고 명령별 예상 상태를 먼저 적습니다.
상대 저장 경로는 이 JSON 파일이 있는 디렉토리를 기준으로 해석됩니다.
{
"commands": [
{"command": "text.insert", "params": {"text": "Rust로 문서를 만듭니다."}},
{"command": "select.text", "params": {"text": "Rust"}},
{"command": "format.bold", "params": {"value": true}}
],
"save": ["lesson.docx", "lesson.pdf"]
}
# CLI가 준비된 실습 환경에서 사용하는 예
wordcraft-cli run --script lesson.json
wordcraft-cli text lesson.docx
wordcraft-cli inspect lesson.docx
완료 기준: 재개방한 DOCX의 본문과 서식 정보를 검사하고, PDF의 모양도 별도로 확인합니다.
text 출력에 문장이 보이는 것만으로 굵은 서식까지 보존됐다고 결론 내리지 않습니다.
매개변수의 true를 잘못된 타입으로 바꿨을 때의 처리도 소스에서 추적하세요.
목표: “열린다”와 “동일한 정보를 보존한다”를 나누어 검증합니다.
읽을 곳: crates/docx/tests/roundtrip.rs,
crates/docx/src/read/mod.rs, crates/docx/src/write/mod.rs.
할 일: 직접 작성한 짧은 문서에 표·그림·각주를 하나씩 추가합니다. 원본과 저장 후 재개방본의 본문·스타일·관계·미디어 항목을 비교할 체크리스트를 만듭니다. 화면이 비슷하다는 관찰과 원본 구조가 유지됐다는 증거를 각각 기록합니다.
완료 기준: 기능 하나마다 표시·편집·보존 여부를 구별하고,
지원하지 않는 정보가 passthrough로 유지되는지 확인할 방법을 설명합니다.
목표: 성능 최적화와 정확성이 충돌하는 지점을 이해합니다.
읽을 곳: crates/layout/src/lib.rs의
Key, LayoutCache, Ctx::para_labelled().
할 일: 텍스트·문단 폭·목록 번호·글꼴·표 폭을 각각 바꾸는 사례를 만듭니다.
어떤 값이 키에 직접 들어가고 어떤 값이 환경 해시 등에 반영되는지 표로 적습니다.
측정한다면 최초 배치와 반복 배치의 hits·misses를 구별하세요.
완료 기준: 키를 단순히 텍스트만으로 만들었을 때 틀리는 반례 두 개를 제시하고, 캐시가 있는 결과와 새 캐시로 계산한 결과를 비교하는 검증안을 작성합니다.
목표: 에이전트가 호출하는 도구를 작은 API로 봅니다.
읽을 곳: crates/mcp/src/tools.rs,
crates/mcp/src/server.rs::Server::handle_line(), crates/mcp/src/backend.rs::Backend.
뒤의 두 파일도 crates/mcp/src/ 아래입니다.
할 일: list_commands, execute,
inspect_document의 입력과 응답을 표로 정리합니다.
없는 명령 ID, 잘못된 JSON, UI가 필요한 요청을 headless에 보낸 경우를 각각 분류합니다.
완료 기준: JSON-RPC 메시지 오류와 도구 실행 오류를 구별하고,
실패 뒤 문서가 유지되는지 점검할 항목을 적습니다.
batch는 “기본적으로 첫 오류에서 중단”하는 도구이므로 전체 원자적 롤백을 가정하지 않습니다.
실습의 완료 기준은 명령을 한 번 실행하는 것이 아니라, 바뀐 것과 보존돼야 할 것을 증거로 설명하는 것입니다.
매주 읽은 내용을 작은 표·그림·입력 예시로 남기는 학습 계획입니다. 모든 기능을 구현하는 일정이 아니라, 한 기능의 앞뒤를 설명할 수 있도록 범위를 늘리는 순서입니다. Rust를 처음 배운다면 1주차를 충분히 늘려도 좋습니다.
| 주차 | 배울 기술과 읽을 곳 | 손에 남길 결과물 |
|---|---|---|
| 1주차 | Rust의 enum, Option, Result, 참조. crates/geom과 crates/doc | 문서 타입 관계도와 단위 변환 예시 3개 |
| 2주차 | 명령 패턴·등록부·선택 영역. crates/engine/src/lib.rs, crates/engine/src/cmd/format.rs | 명령 3개의 입력·출력·실패 조건 표 |
| 3주차 | 구조 공유·쓰기 시 복사·실행 취소. crates/doc/src/lib.rs, crates/engine/src/session.rs | 편집 전후 공유 블록 그림과 실패 복구 체크리스트 |
| 4주차 | 유니코드·셰이핑·레이아웃·캐시. crates/fonts, crates/layout | 바이트·문자·글리프 차이 설명과 캐시 반례 |
| 5주차 | OOXML 패키지·관계·왕복 저장. crates/docx, crates/engine/src/io_ext.rs | 문서 한 개의 보존 항목 표와 형식 변환 경로 |
| 6주차 | WASM 경계·MCP 어댑터·품질 관문. apps/wordcraft-web, crates/mcp, xtask | 같은 기능을 GUI·CLI·MCP로 부르는 흐름도 |
명령은 crates/engine/src/cmd/에서 등록되고,
실행은 crates/engine/src/session.rs의 Session::run()으로 모입니다.
2주차에 고른 명령 하나를 마지막 주까지 계속 따라가면 학습 범위가 덜 흩어집니다.
첫 장에는 문서 모델과 실행 취소, 둘째 장에는 글리프와 페이지 배치, 셋째 장에는 화면·PDF·MCP 경계를 그립니다. 각 장에 실제 타입이나 함수 이름을 두 개 이상 넣으면 개념과 소스가 연결됩니다.
다른 프로젝트로 가져갈 지식은 Word의 메뉴 개수보다, 상태·계산·출력·플랫폼 경계를 나누는 판단력입니다.
| 용어 | 쉬운 설명 |
|---|---|
| clean-room | 공개 규격과 관찰한 동작 등을 토대로 독립 구현을 지향하는 개발 방식. 이 프로젝트가 밝힌 원칙 |
| 크레이트 / workspace | Rust 패키지 단위 / 여러 패키지를 공통 설정으로 묶어 관리하는 작업 공간 |
| Session | 열린 문서와 선택 영역·실행 취소·보기 상태 등을 함께 관리하는 편집 세션 |
| CommandSpec / Registry | 명령 하나의 설명과 실행 함수 / ID로 명령을 찾는 등록부 |
| StoryRef / Pos | 본문·머리글·각주 같은 글 영역 / 문서 안의 위치. 화면의 픽셀 좌표와 구별 |
| run | 텍스트 모델에서는 같은 서식이 이어지는 구간. 문맥에 따라 명령 실행 함수 이름이기도 함 |
| Arc / 쓰기 시 복사 | 같은 데이터를 여러 참조가 공유하는 수단 / 공유 중인 데이터를 수정할 때 필요한 사본을 만드는 전략 |
| 스냅샷 / undo | 이전 상태를 보관한 것 / 그 상태로 돌아가는 기능 |
| 셰이핑 / 글리프 | 문자를 글꼴의 그림과 배치로 바꾸는 과정 / 실제로 그려지는 글자 모양의 단위 |
| 메트릭 / 폴백 | 글자의 폭·높이·기준선 같은 수치 / 요청한 글꼴을 쓸 수 없을 때 다른 글꼴을 고르는 처리 |
| bidi | 왼쪽 쓰기와 오른쪽 쓰기가 섞인 텍스트의 표시 순서를 다루는 규칙 |
| pt / twip / EMU | 길이 단위. 1 pt는 1/72인치, 1 twip은 1/20 pt, 1 pt는 12,700 EMU |
| 레이아웃 / 페이지 나누기 | 글과 표를 좌표에 배치하는 계산 / 어느 내용이 다음 페이지로 넘어갈지 정하는 계산 |
| display list / 래스터화 | 그릴 작업을 모은 목록 / 그 목록을 화면용 픽셀로 바꾸는 일 |
| 메모이제이션 / 캐시 키 | 이미 계산한 결과를 재사용하는 전략 / 같은 계산 조건인지 판별하는 정보 묶음 |
| OOXML / OPC | Office 문서의 XML 기반 규격 / 여러 파일 조각과 관계를 패키지로 묶는 규칙 |
| 왕복 저장 / passthrough | 읽기→쓰기→다시 읽기 / 일부 원본 데이터를 그대로 전달해 보존하는 처리 |
| WASM / wasm-bindgen | 브라우저 등에서 실행할 수 있는 코드 형식 / Rust와 브라우저 기능을 연결하는 도구 |
| headless / Backend | 창 없이 동작하는 방식 / 호출 대상의 구체적인 연결 방식을 감추는 인터페이스 |
| MCP / JSON-RPC | 클라이언트와 도구를 연결하는 프로토콜 / 메서드·매개변수·결과·오류를 JSON으로 주고받는 규칙 |
| parity / alpha | 목표 제품과의 기능·동작 대응 수준 / 기능이 있어도 안정성·호환성 검증이 더 필요한 초기 단계 |
| 속성 테스트 | 특정 예시의 답뿐 아니라, 다양한 입력에서 계속 지켜져야 할 성질을 검사하는 방식 |
길이 단위는 crates/geom/src/lib.rs의 PT_PER_IN,
TWIPS_PER_PT, EMU_PER_PT에서 확인할 수 있습니다.
단위를 한곳에 모으면 DOCX의 수치와 화면의 좌표를 연결할 때 변환 규칙이 흩어지는 일을 줄일 수 있습니다.
| 항목 | 상태 | 근거와 읽는 법 |
|---|---|---|
| 스타 / 포크 | 15,592 / 4,228 | 2026-10-23 GitHub 기준 |
| 생성일 | 2026-10-07 | GitHub 저장소 메타데이터 |
| 최신 릴리스 | v0.10.0 · 2026-10-22 | GitHub 기준. Cargo 작업 공간 버전도 0.10.0 |
| 최근 push | 2026-10-23 | GitHub 메타데이터의 push 날짜 |
| 분석 기준 커밋 | 3a4770e · 2026-10-22 | Merge pull request #3286 from matstrange/fix/protection-inspect-tracking |
| 주 언어 / 규모 | Rust · 452파일 · 292,913줄 | 주석·빈 줄·테스트·예제를 포함한 물리적 줄 수 |
| 구성 | 16개 라이브러리 크레이트 + 3개 앱 + xtask | 루트 포함 Cargo.toml 21개 |
| 테스트 표기 | #[test] 3,928회 · 속성 블록 16회 | 정적 집계. 실행 수·통과 수로 해석하지 않음 |
| 기능 카탈로그 | 498 / 513 · 97.1% | 커밋에 저장된 체크리스트. 전체 호환성 비율이 아님 |
| 개발 단계 | alpha | ROADMAP.md. README의 실무 준비도 약 60%는 자체 추정 |
| 라이선스 | GitHub 표시 Apache-2.0 패키지 선언 MIT OR Apache-2.0 | LICENSE-MIT·LICENSE-APACHE·NOTICE 및 개별 자산의 출처를 함께 확인 |
커밋 날짜와 push 날짜는 같은 개념이 아닙니다.
기준 커밋의 기록 시각은 2026-10-22T22:06:08-06:00으로,
UTC로 환산하면 2026-10-23입니다. 날짜만 비교할 때는 시간대도 고려해야 합니다.
alpha와 문서 보존: 실무 투입 전에는 실제 사용하는 문서의 재개방·왕복 저장·글꼴·페이지 나누기를 검증해야 합니다. 중요 원본의 사본으로 시작하고, 표시·편집·보존을 각각 확인하세요. 정적 소스 분석은 실행 호환성 검증을 대신하지 않습니다.
문서 간 시차: README에는 422개 명령·약 800개 테스트, docs/architecture.md에는 Rust 약 85,600줄 등 오래된 규모 수치가 남아 있습니다.
ROADMAP의 일부 미지원 설명도 이후 코드와 시차가 있습니다.
현재 소스의 Document::passthrough와 패키지 보존 경로를 근거로, “모든 미지원 객체가 항상 삭제된다”는 단정은 피해야 합니다.
반대로 일부 보존 코드가 있다는 것만으로 모든 객체의 완전 보존을 주장해서도 안 됩니다.
숫자와 성능: 코드 줄 수·테스트 표기 수·카탈로그 비율은 서로 다른 지표입니다. 과거 문서의 188페이지 성능 사례를 현재 버전·모든 하드웨어·대형 실제 문서의 보장으로 일반화하지 마세요.
구현 목표와 권리: clean-room은 프로젝트가 밝힌 개발 원칙입니다.
재배포 시에는 패키지의 이중 라이선스 선언과 개별 폰트·아이콘의 ATTRIBUTION.md를 함께 읽어야 합니다.