본문으로 건너뛰기
5분 AI.
TRENDSHIFT MONTHLY #22 딥다이브 · 2026-10-23 분석

wordcraft 딥다이브
— 글자 하나가 문서와 화면과 PDF가 되기까지

워드프로세서

빠른 판단

무엇
DOCX 문서 모델·조판·편집 엔진을 데스크탑·웹·CLI·MCP에 연결한 Rust 워드프로세서
이럴 때
Word 문서를 편집·자동화할 때
입력
DOCX와 문단·표·페이지 편집 명령
결과
편집·조판한 문서와 지원 형식의 저장 파일
도입 전 확인
독립 구현으로 Word 호환을 목표로 하며 모든 문서의 동일한 동작을 보증하는 것은 아니다
WordCraft는 Microsoft Word와의 기능 호환을 목표로 하는 Rust 문서 편집기입니다. 데스크탑·브라우저·CLI·MCP가 같은 편집 엔진을 공유합니다. 이 글에서는 문서 모델 → 명령 실행 → 페이지 배치 → 출력을 실제 소스로 따라갑니다. Rust 문법을 모두 몰라도 괜찮습니다. 각 부품이 어떤 문제를 해결하는지부터 이해하면 됩니다.
저장소: storytold/wordcraft · ⭐15,592 · 포크 4,228 · 최신 릴리스 v0.10.0
GitHub 수치는 2026-10-23 기준, 소스 설명은 커밋 3a4770e 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

문서 편집기의 화면 뒤에 있는 작은 조판 시스템

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에서 찾을 수 있습니다.

2왜 주목받는가

큰 목표보다 흥미로운 것은 그 목표를 쪼개는 방법

2-1. 빠른 관심 증가와 독립적인 구현 목표

TrendShift Monthly #22 선정 프로젝트입니다. 2026-10-07에 생성되어 2026-10-23 GitHub 기준 스타 15,592개, 포크 4,228개를 기록했습니다. 짧은 기간에 관심을 모은 것은 확인 가능한 사실이지만, 스타가 증가한 정확한 원인을 이 수치만으로 증명할 수는 없습니다.

주목할 요인으로는 익숙한 대형 제품을 Rust로 다시 구현하는 목표, Linux·BSD·웹까지 아우르는 진입점, 그리고 CLI·MCP 자동화가 있습니다. 이는 README의 목표와 소스 구성을 근거로 한 해석입니다. 성능·호환성·인기도 사이의 인과관계를 측정한 결과는 아닙니다.

2-2. 경쟁 제품과는 무엇을 비교해야 할까

비교 대상선택할 때 보는 축WordCraft에서 눈여겨볼 점
Microsoft Word기존 문서 교환과 업무 흐름편집·조판 내부를 공개 Rust 코드로 추적할 수 있음. 호환성은 별도 검증 필요
LibreOffice Writer오픈소스 문서 편집기Rust 중심의 계층 구조와 명령 등록부를 학습 대상으로 삼기 좋음
웹 기반 편집기브라우저 접근과 협업 흐름브라우저판도 Rust 엔진과 egui 화면을 공유. 공동 편집 서비스와는 평가 축이 다름
문서 변환 도구배치 변환과 자동 생성CLI 변환에 더해 같은 엔진의 선택·서식·검사 명령을 조합할 수 있음

이 비교는 제품 순위표가 아닙니다. 초보 개발자에게 중요한 장점은 화면에서 보이는 기능과 파일 형식 내부 구현을 한 저장소에서 연결해 볼 수 있다는 점입니다. 다른 제품보다 빠르거나 DOCX를 더 정확하게 처리한다는 비교 실험은 제시하지 않습니다.

2-3. “97.1%”를 읽는 올바른 방법

docs/parity-checklist.md에는 카탈로그 기능 513개 중 498개에 명령이 있다(97.1%)고 적혀 있습니다. 반면 README의 카탈로그 수치는 약 97%, 실무 준비도는 약 60%라는 자체 추정입니다. 명령의 존재, 기능의 깊이, 실제 문서 보존은 서로 다른 지표입니다.

기능 목록에 명령이 있다는 사실은, 복잡한 Word 문서가 손실 없이 왕복 저장된다는 뜻이 아닙니다. 표를 넣는 명령이 있어도 셀 병합·페이지 분할·스타일 상속·다른 프로그램과의 교환까지 확인해야 합니다.

숫자를 해석할 때
현재 단계는 alpha

ROADMAP.md는 alpha라고 명시합니다. README의 약 60% 준비도는 프로젝트의 자체 추정치이며, 외부 인증을 받은 평가 점수가 아닙니다. 97.1%는 해당 커밋의 체크리스트에 적힌 명령 연결 비율이며, 문서 호환성 평가 점수가 아닙니다.

2-4. AI가 들어 있는 편집기인가

MCP 서버가 있다는 것과 언어 모델이 내장됐다는 것은 다릅니다. crates/mcp/src/lib.rs는 외부 클라이언트의 요청을 편집 세션에 연결합니다. 프로젝트의 문서도 외부 에이전트 제어와 앱 안의 어시스턴트를 구분합니다. 이 연결 구조를 이해하면 일반 프로그램에 자동화 인터페이스를 붙이는 방법을 배울 수 있습니다.

3기술 스택 전체 지도

웹 서버의 앞뒤보다, 편집 코어와 플랫폼 연동부로 나눠 보기

이 저장소의 “백엔드”는 주로 프로세스 안에서 움직이는 문서 엔진을 뜻합니다. 루트에는 package.json, pyproject.toml, go.mod, Dockerfile이 없습니다. 빌드의 중심은 Cargo.toml과 Cargo.lock입니다.

3-1. Rust 작업 공간과 버전 읽기

# 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과 Cargo.lock
Cargo.toml의 버전은 의존성 선택 조건입니다. 예를 들어 egui = "0.36"은 정확히 0.36.0만 가리키지 않습니다. Cargo.lock은 이 커밋이 고정한 실제 의존성 해석 결과로, 여기에는 0.36.2가 기록돼 있습니다.

3-2. 편집 코어 · 파일 입출력

기술선언 → 잠금 버전이 저장소에서의 역할
Rust최소 1.95 · edition 2024문서 모델, 조판, UI, 도구를 작업 공간으로 관리
serde1 → 1.0.229문서 구조의 직렬화. derive, rc 활성화
serde_json1 → 1.0.151명령 매개변수·검사 결과·제어 메시지
thiserror2 → 2.0.21실패 이유를 오류 타입으로 표현
quick-xml0.41 → 0.41.0DOCX 안의 XML 처리
zip3 → 3.0.0DOCX·ODT 패키지 처리. 기본 기능을 끄고 deflate 사용
chrono0.4.45 → 0.4.45날짜 필드와 저장 시각의 변환

위 선언은 루트 Cargo.toml, 잠금 버전은 Cargo.lock 기준입니다. 잠금 파일에는 간접 의존성이 요구하는 다른 버전도 함께 있을 수 있습니다. 예를 들어 thiserror 1.x도 있지만, 작업 공간의 직접 선언은 2.x입니다.

3-3. 텍스트 · 조판 · 출력

기술선언 → 잠금 버전어떤 문제를 푸는가
HarfRust0.13 → 0.13.3문자열을 글꼴의 글리프와 위치 정보로 변환
skrifa0.47 → 0.47.0글꼴 메트릭과 윤곽선 읽기
vello_cpu0.2 → 0.2.0페이지 그리기 명령을 CPU에서 픽셀로 변환
image0.25 → 0.25.10PNG·JPEG·WebP·GIF·BMP·TIFF 지원 기능 선택
craft-pdf=0.1.0 · Git 리비전 고정공유 PDF 작성기. Krilla 그리기 기능과 연결
krilla패치된 Git 의존성 · 잠금 0.8.2PDF 텍스트·도형·링크·구조 정보 출력

craft-pdf와 [patch.crates-io]의 krilla는 storytold/craft-libs의 리비전 c3487320b2d1de657b860b3c8abb44e1f2b32bd8을 가리킵니다. 따라서 버전 번호만 보고 일반 배포판과 완전히 같다고 가정하면 안 됩니다.

3-4. 프론트엔드 · 플랫폼 접점

구성실제 의존성소스에서 확인할 경계
UIegui / eframe 0.36 → 0.36.2crates/ui-egui의 리본·캔버스·대화상자
GPU 표시잠금 파일의 wgpu 30.0.1eframe의 wgpu 기능. CPU 페이지 렌더링과 역할이 다름
파일 대화상자rfd 0.17 → 0.17.2운영체제·브라우저가 제공하는 파일 선택 연결
브라우저 연결wasm-bindgen 0.2 → 0.2.129Rust와 브라우저 기능 사이의 바인딩
웹 APIweb-sys 0.3 → 0.3.106Canvas·클립보드·Storage·IndexedDB 등 선택 기능

웹 의존성은 apps/wordcraft-web/Cargo.toml에 있습니다. apps/wordcraft-web/src/web.rs::start()는 Canvas를 찾아 eframe::WebRunner를 시작합니다. React 컴포넌트를 공유하는 방식이 아니라, Rust UI와 엔진을 함께 재사용하는 방식입니다.

3-5. 인프라 · 빌드 · 품질 관문

파일실제 설정학습 포인트
.cargo/config.tomlxtask = "run --package xtask --"개발용 명령도 Rust 프로그램으로 관리
.github/workflows/ci.ymlubuntu-24.04, 제한 60분, stable RustPR과 main 푸시에서 동일 관문 실행
xtask/src/main.rsfmt, clippy, test, assets, layers, wasm테스트 외에 저작물 출처와 의존 방향까지 검사
apps/wordcraft-web/Trunk.toml포트 8771, 127.0.0.1, filehash = trueRust 웹 빌드와 정적 결과물 관리
Cargo.toml releaseopt-level = 3, lto = "thin", codegen-units = 16릴리스 빌드 설정을 중앙에서 공유

CI는 wasm32-unknown-unknown 타깃과 clippy·rustfmt를 설치하고 cargo xtask ci를 호출합니다. 추가 글꼴 저장소 craft-fonts도 고정 리비전으로 받아 CRAFT_FONTS_DIR로 전달합니다. “같은 코드”에 더해 “같은 글꼴”도 레이아웃 재현성에 영향을 준다는 예입니다.

웹과 데스크탑의 재사용은 파일 접근과 운영체제 기능을 경계 밖으로 밀어내는 설계에서 시작됩니다. “Rust로 썼다”는 사실만으로 모든 플랫폼에서 같은 동작이 자동 보장되지는 않습니다.

4아키텍처 심화 분석

입력에서 출력까지 따라가며 네 가지 설계 패턴 읽기

4-1. 전체 시스템 구조

사람의 입력 자동화 요청 │ │ ├─ 데스크탑 / 웹 UI ├─ CLI │ WordApp └─ MCP Server │ + Services │ Backend │ ├─ Headless: 같은 프로세스 │ └─ Remote: 앱 제어 채널 └─────────────────┬────────────────────┘ ▼ engine: Session::run(id, params) ├─ Registry / CommandSpec ├─ 문서 보호 검사 / 선택 영역 └─ 실행 취소 / 오류 복구 │ ▼ 파일 읽기 ──> doc::Document <──> 파일 쓰기 docx/docbin/ 문단·표·스타일 docx/formats formats 그림·각주·변경 이력 │ fonts ─────┤ 글꼴·셰이핑 proof ─────┤ 교정 정보 ▼ layout: 문단 배치 + 페이지 나누기 DocLayout / Page / LayoutCache │ ▼ display: Draw 목록 / page_display() │ ┌────────┴────────┐ ▼ ▼ render pdf vello_cpu craft-pdf / Krilla 픽셀·페이지 이미지 텍스트·도형을 담은 PDF │ ▼ egui / wgpu 화면 표시 공통 기초: geom의 단위·좌표, doc의 문서 규칙

도식의 화살표는 주요 데이터 흐름입니다. 모든 상자의 직접 의존성을 나열한 그래프는 아닙니다. 엔진은 레이아웃과 출력 기능을 사용하지만, 문서 모델이 UI를 알아야 할 이유는 없습니다. 이 방향을 유지하면 창 없이도 변환하고 검사할 수 있습니다.

4-2. 패턴 ① 명령 등록부: 기능을 한곳에서 설명하기

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()는 그 표시를 끕니다. 이 메타데이터는 실행 취소와 보호 검사에 사용되므로 정확해야 합니다.

  1. crates/engine/src/cmd/format.rs::specs()에서 format.bold를 찾습니다.
  2. 단축키 Mod+B, 매개변수 {"value"?: bool}, 실제 처리 함수 연결을 봅니다.
  3. Session::run()이 ID를 조회하고 enabled를 검사하는 부분으로 이동합니다.
  4. crates/mcp/src/lib.rs::Headless::answer()의 engine.execute 분기도 같은 함수를 호출하는지 확인합니다.

{"value"?: bool}은 사람이 읽는 매개변수 설명 문법입니다. 그 자체가 유효한 JSON 요청은 아닙니다. 실제 호출은 {"value": true}처럼 씁니다. 또한 UI는 대화상자를 먼저 띄울 수 있습니다. WordApp::run()의 화면 처리를 거쳐 엔진으로 들어가는 흐름과, 곧바로 Session::run()을 부르는 흐름을 구분해야 합니다.

비유

명령 등록부는 식당 메뉴판과 주문 번호를 합친 것입니다. 홀 직원·전화 주문·키오스크가 같은 “굵게” 주문 번호를 사용하면 조리법은 한곳에 둘 수 있습니다. 다만 메뉴 설명이 있다는 것만으로 재료 검사가 끝나지는 않습니다. 매개변수 검증은 실행 경로에서도 필요합니다.

4-3. 패턴 ② 스냅샷과 쓰기 시 복사: 실행 취소 비용 줄이기

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입니다.

변경 전: 현재 문서 ──> [문단 A] [문단 B] ▲ ▲ 실행 취소 기록 ─┘────────┘ 같은 블록 공유 B 수정: 현재 문서 ─────> [문단 A] [수정된 B] ▲ 이전 기록 ──────┘──────> [원래 B] 공유 중인 B만 복제해서 이전 원고를 남김

Session::run()은 변경 명령을 실행하기 전 문서·선택·undo/redo 스택 등을 보관합니다. 처리 함수는 catch_unwind 안에서 호출되며, 일반 오류나 잡힌 panic으로 실패하면 보관한 편집 상태를 복구하는 분기로 갑니다. 문서 보호의 사후 검사에서 거부되는 경우도 고려합니다.

복구 범위
편집 스냅샷은 운영체제 전체의 트랜잭션이 아닙니다

이 경로는 문서와 관련 편집 상태의 복구 장치입니다. 이미 수행한 외부 파일 쓰기나 프로세스 종료, 메모리 부족까지 모두 되돌린다고 해석하면 안 됩니다. catch_unwind도 모든 종류의 프로그램 종료를 잡는 기능은 아닙니다.

4-4. 패턴 ③ 공통 중간 표현과 캐시: 배치와 출력을 분리하기

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 한 장에 붙이는 방식만으로 처리하지 않습니다.

중간 표현
그리기 목록 (display list)
어떤 출력 장치를 쓸지 정하기 전에 모아 둔 그리기 작업 목록입니다. 번역기의 중간 언어처럼, 문서의 의미와 화면·PDF의 세부 규칙을 연결합니다. 같은 배치 규칙을 공유해도 글꼴 대체나 출력기의 기능 차이로 결과가 달라질 수는 있습니다.

속도 쪽의 핵심은 LayoutCache입니다. 내부의 Key는 문단 리비전뿐 아니라 폭·목록 라벨·표·각주·그리드·변경 내용 표시 등 배치 조건을 담습니다. Ctx::para_labelled()는 키가 있으면 기존 Arc<ParaLayout>을 재사용하고, 없으면 para::layout_para()를 호출합니다. hits와 misses도 셉니다.

캐시를 잘 만드는 일은 결과를 저장하는 일보다, 결과를 바꾸는 모든 입력을 찾아내는 일에 가깝습니다. 글자가 같아도 문단 폭이나 글꼴이 달라지면 줄바꿈 결과를 다시 계산해야 합니다.

4-5. 패턴 ④ 어댑터와 제한된 입력: 바깥세상을 통제하기

파일 형식은 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_PART256 MiB단일 압축 항목이 지나치게 커지는 문제
MAX_TOTAL512 MiB전체 압축 해제 데이터가 계속 쌓이는 문제
MAX_ENTRIES20,000개아주 많은 항목을 검사하는 비용

이 값은 기본 DOCX 패키지 읽기의 예산이며, 앱 전체의 최대 메모리 사용량은 아닙니다. take(room + 1)로 한도보다 한 바이트 더 읽고 초과 여부를 검사하는 방식까지 살펴보세요. 파일을 여는 기능은 정상 문서를 지원하는 일과, 비정상 입력의 비용을 제한하는 일을 함께 해야 합니다.

5디렉토리 구조 해부

폴더 이름을 외우기보다 질문에 맞는 파일을 찾기
wordcraft/ ├── Cargo.toml / Cargo.lock 작업 공간 설정 / 의존성 잠금 ├── apps/ │ ├── wordcraft/ 데스크탑 진입점과 OS 연동 │ ├── wordcraft-cli/ 변환·검사·명령·MCP 진입점 │ └── wordcraft-web/ WASM 진입점과 브라우저 연결 ├── crates/ │ ├── geom/ 좌표·길이 단위·픽셀 예산 │ ├── doc/ 문서 트리와 수정 기본 연산 │ ├── fonts/ / proof/ 글꼴·셰이핑 / 언어별 교정 │ ├── layout/ 줄바꿈·표·페이지·그리기 목록 │ ├── docx/ / docbin/ OOXML 입출력 / 구형 DOC 읽기 │ ├── formats/ TXT·MD·HTML·RTF·ODT·LaTeX │ ├── metafile/ WMF·EMF 그림 처리 │ ├── render/ / pdf/ 화면용 픽셀 / PDF 출력 │ ├── engine/ Session·명령·파일 형식 연결 │ ├── mcp/ / zotero/ 자동화 도구 / 참고문헌 연동 │ ├── control-key/ 앱 제어 키 경로 공유 │ └── ui-egui/ 리본·캔버스·대화상자·번역 ├── assets/ 글꼴·아이콘·교정 자료 ├── docs/ 설계·호환성·미완성 영역 문서 ├── packaging/ OS별·웹 배포 구성 ├── xtask/ 품질 관문과 개발 도구 └── .github/workflows/ CI·릴리스 등 자동화

작업 공간은 라이브러리 크레이트 16개, 앱 3개, 개발 도구 xtask로 구성됩니다. 루트 설정을 포함한 Cargo.toml은 21개입니다. docs/architecture.md에는 15개 크레이트라고 적혀 있지만, 그 문서는 과거 커밋의 구조를 설명합니다.

5-1. 코드 규모

파일 종류파일 수물리적 줄 수
Rust .rs452292,913줄
Shell .sh8669줄
PowerShell .ps12261줄
Python .py1158줄
HTML .html120줄
JavaScript / TypeScript0해당 확장자 파일 없음

커밋 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·형식 호환·테스트에 많은 코드가 필요하다는 사실을 보여 줍니다.

5-2. 처음 읽을 때의 순서

  1. crates/geom/src/lib.rs: 길이 단위를 이해합니다.
  2. crates/doc/src/lib.rs: Block, Document, Pos를 봅니다.
  3. crates/engine/src/cmd/format.rs: 기능 한 개가 등록되는 모습을 읽습니다.
  4. crates/engine/src/session.rs: 그 명령의 실행·실패·취소를 따라갑니다.
  5. crates/layout/src/display.rs: 편집 결과가 그리기 목록이 되는 경계를 봅니다.
읽기 전략
작은 기능을 세로로 관통하세요

큰 파일 하나를 처음부터 끝까지 읽기보다, “굵게 한 번” 또는 “TXT를 PDF로 저장”을 골라 관련 파일만 따라가세요. 이해한 경로에 타입 이름과 데이터 변화를 적으면 다음 기능을 읽을 때 지도가 됩니다.

6학습 포인트 (기술별)

새 언어 문법보다 오래 남는 설계 원리

6-1. Rust 타입으로 문서의 규칙 표현하기

Block이 Para와 Table로 나뉘면, 호출자는 “이 블록이 어떤 종류인가”를 match로 분기합니다. Option은 값의 부재를, Result는 실패할 수 있는 작업을 드러냅니다. 잘못된 문서 위치를 받는 Document::para_mut()가 어떤 오류를 돌려주는지부터 읽어 보세요.

작은 실습

문단 두 개와 표 하나를 종이에 그리기

Document → Blocks → Block 관계를 그림으로 표현하고, StoryRef가 본문·머리글·각주 같은 글의 영역을 어떻게 구분하는지 적습니다. 화면 좌표와 문서 위치 Pos를 같은 값으로 취급하면 왜 곤란한지도 설명해 보세요.

6-2. 유니코드와 글꼴: 글자 하나가 픽셀 하나가 아닌 이유

crates/fonts/src/lib.rs::shape_run()은 방향 정보를 포함해 글리프를 배치하는 경로입니다. crates/fonts/src/word.rs::resolve()는 요청한 글꼴·굵기·기울임을 실제 글꼴로 연결합니다. 같은 파일의 대체 목록에는 Calibri에 대응하는 Carlito, Cambria에 대응하는 Caladea 등이 있습니다.

텍스트 엔진의 기본
셰이핑 (shaping)
유니코드 문자열을 글꼴의 실제 그림인 글리프로 바꾸고 간격·위치를 정하는 작업입니다. 글자와 글리프는 항상 일대일이 아닙니다. 결합 문자, 합자, 오른쪽에서 왼쪽으로 쓰는 문자에서는 이 차이가 특히 중요합니다.

실습 아이디어: 가나다, 결합 악센트가 있는 문자열, 영문과 숫자가 섞인 문장을 고릅니다. UTF-8 바이트 수·유니코드 문자 수·사용자가 인식하는 글자 수가 같은지 생각하고, crates/doc/src/cluster.rs와 crates/layout/src/tests_ligature.rs에서 관련 경계를 찾아보세요.

6-3. 파일 포맷: 읽기 성공과 보존 성공 구별하기

DOCX는 본문 XML 하나만 있는 파일이 아닙니다. 스타일·번호 매기기·그림·관계 정보가 함께 들어 있습니다. crates/docx/src/read/mod.rs::read()에서 모델로 읽고, crates/docx/src/write/mod.rs::write_as()에서 문서 종류(Flavor)에 맞는 패키지로 씁니다.

Document::passthrough는 원래 형식의 일부 데이터를 바이트로 보관합니다. 매크로 프로젝트를 보관하는 경로도 있지만, 보관과 실행 지원은 별개입니다. 실습 아이디어: “보여 주기”, “편집하기”, “모르는 데이터를 유지하기”를 세 열로 나누어 지원 상태를 기록하세요.

6-4. 테스트: 성공 예시보다 지켜야 할 성질 찾기

crates/engine/src/tests.rs::hostile_params_never_panic()는 명령에 엉뚱한 매개변수를 넣습니다. null, 잘못된 타입, 매우 큰 크기와 좌표 등이 포함됩니다. 파일 작업 일부는 제외하고, 명령 실행 뒤 선택 영역 보정과 레이아웃까지 이어 갑니다.

Rust 파일 전체의 #[test] 표기는 3,928회, proptest! { 형태의 속성 테스트 매크로 호출은 16곳입니다(줄 시작의 호출만 집계하며, 네임스페이스 접두사는 포함하고 문자열 안의 예시는 제외). 이는 정적 텍스트 집계이며, 조건부 컴파일·매크로를 반영한 실행 테스트 수나 통과 결과가 아닙니다. README의 약 800개 테스트라는 서술과는 집계 시점·방법을 구별해야 합니다.

실습에서 물어볼 것
실패한 편집 뒤에도 무엇이 같아야 할까

본문 텍스트, 선택 영역, 실행 취소 깊이를 먼저 비교하세요. “오류 문자열이 나왔다”만 확인하는 테스트보다 “거부된 작업이 문서를 바꾸지 않았다”는 성질이 중요합니다. 단, 읽기·쓰기처럼 세션 밖으로 나가는 부작용은 별도 검증이 필요합니다.

6-5. 의존성 규칙도 자동으로 검사하기

xtask/src/layers.rs::TABLE은 크레이트의 계층을 지정합니다. UI_MIN_LAYER = 6과 UI_CRATES 목록은 낮은 계층에 UI 의존성이 스며드는 것을 검사합니다. 같은 계층의 예외는 INTRA_LAYER_ORDER처럼 명시합니다. 오래된 설명 문서보다 실제 검사표를 따라가야 하는 이유이기도 합니다.

아키텍처 규칙을 사람의 기억에만 맡기지 않고, 어기면 검사에서 드러나도록 만드는 방법을 배울 수 있습니다. 자신의 작은 프로젝트에서도 “도메인 모델은 UI를 가져오지 않는다” 같은 한 가지 규칙부터 적용해 볼 수 있습니다.

7하드웨어 / 시스템 요구사항

실행 환경, 개발 환경, 대용량 문서를 나누어 판단하기

소스와 배포 문서는 여러 운영체제를 다루지만, 모든 기기에 공통인 공식 최소 RAM·CPU 수치는 제시하지 않습니다. 다음 표는 확인 가능한 조건과 학습용 제안을 나눈 것입니다. 압축된 문서의 파일 크기만으로 실행에 필요한 메모리를 판단할 수는 없습니다.

용도확인 가능한 조건학습 환경 제안
소스 읽기텍스트 편집기와 파일 검색 도구로 시작 가능일반 노트북으로 타입·명령 경로부터 학습
Rust 빌드rust-version = "1.95", Cargo여러 크레이트의 컴파일 결과를 둘 SSD 여유 공간 확보
데스크탑macOS·Windows·Linux·BSD용 구성. eframe/wgpu 사용자신의 OS·아키텍처에 맞는 배포판과 그래픽 드라이버 확인
브라우저WebGPU 또는 WebGL2를 사용할 수 있는 환경최신 브라우저에서 작은 문서로 시작
웹 개발Trunk와 wasm32-unknown-unknown 타깃데스크탑 빌드와 별도로 웹 경계의 기능 차이 확인
대용량 문서페이지·그림·폰트·스냅샷에 따라 비용 증가작은 문서부터 페이지 수·그림 수를 한 번에 하나씩 늘리기

7-1. Linux에서 Rust 외에 필요한 것

.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의 개발 환경 목록이며, 모든 배포판에서 같은 이름의 패키지를 요구한다는 뜻은 아닙니다.

7-2. CPU와 GPU가 각각 하는 일

crates/render/src/lib.rs::default_threads()는 데스크탑에서 사용 가능한 병렬도에 따라 작업 스레드를 1~8개 범위로 고릅니다. 병렬도 조회 실패 시 기본값은 2이고, WASM 분기는 0입니다. 즉, 웹판은 같은 방식의 렌더 작업 스레드를 만들지 않습니다.

페이지의 픽셀화는 vello_cpu, UI의 표시 경로는 egui/wgpu가 담당합니다. CPU 렌더러를 쓴다고 그래픽 환경이 무관한 것은 아닙니다. apps/wordcraft/src/graphics.rs에는 Windows의 그래픽 백엔드와 드라이버 문제를 다루는 코드도 있습니다.

7-3. 글꼴도 시스템 의존성이다

글꼴이 없으면 대체 글꼴을 사용하고, 글자 폭이 달라지면 줄 수와 페이지 수가 바뀔 수 있습니다. 코드의 대체 목록은 호환성을 높이는 전략이지 모든 문서의 동일한 페이지 나누기를 보증하는 장치는 아닙니다. 문서를 비교할 때는 파일뿐 아니라 설치 글꼴과 화면 배율도 기록하세요.

메모리 비용을 보는 비유

압축된 DOCX 파일 크기는 접힌 이삿짐 상자의 크기와 같습니다. 압축을 풀고 그림을 픽셀로 만들고 이전 편집 상태를 보관하면 더 넓은 공간이 필요합니다. 512 MiB 압축 해제 제한을 RAM 512 MiB면 충분하다는 의미로 읽으면 안 됩니다.

8직접 해볼 수 있는 실습 과제

읽기부터 시작해, 나중에 별도 실습 환경에서 동작을 확인하기

첫 과제는 소스 읽기만으로 완성할 수 있습니다. 실행 명령이 있는 과제는 해당 버전의 CLI를 준비한 개인 실습 환경을 전제로 합니다. 문서 예시는 학습자가 작성할 실습 입력이며, 실행 성공이나 출력 모양을 미리 보증하는 결과물이 아닙니다.

과제 1 · 입문

“굵게” 버튼의 여행 지도 만들기

목표: 기능 이름에서 데이터 변경까지 소스를 따라갑니다.

읽을 곳: 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가 같은 엔진 명령을 부른다”를 파일과 함수 네 곳으로 설명하고, 버튼의 대화상자 처리와 엔진 처리를 구별할 수 있습니다.

과제 2 · 중급

명령 목록으로 짧은 문서 만들기

목표: 사람의 조작을 재현 가능한 입력 파일로 바꿉니다.

읽을 곳: 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를 잘못된 타입으로 바꿨을 때의 처리도 소스에서 추적하세요.

과제 3 · 중급

왕복 저장의 손실 지도 만들기

목표: “열린다”와 “동일한 정보를 보존한다”를 나누어 검증합니다.

읽을 곳: crates/docx/tests/roundtrip.rs, crates/docx/src/read/mod.rs, crates/docx/src/write/mod.rs.

할 일: 직접 작성한 짧은 문서에 표·그림·각주를 하나씩 추가합니다. 원본과 저장 후 재개방본의 본문·스타일·관계·미디어 항목을 비교할 체크리스트를 만듭니다. 화면이 비슷하다는 관찰과 원본 구조가 유지됐다는 증거를 각각 기록합니다.

완료 기준: 기능 하나마다 표시·편집·보존 여부를 구별하고, 지원하지 않는 정보가 passthrough로 유지되는지 확인할 방법을 설명합니다.

과제 4 · 고급

캐시 키에서 빠뜨리면 안 되는 입력 찾기

목표: 성능 최적화와 정확성이 충돌하는 지점을 이해합니다.

읽을 곳: crates/layout/src/lib.rs의 Key, LayoutCache, Ctx::para_labelled().

할 일: 텍스트·문단 폭·목록 번호·글꼴·표 폭을 각각 바꾸는 사례를 만듭니다. 어떤 값이 키에 직접 들어가고 어떤 값이 환경 해시 등에 반영되는지 표로 적습니다. 측정한다면 최초 배치와 반복 배치의 hits·misses를 구별하세요.

완료 기준: 키를 단순히 텍스트만으로 만들었을 때 틀리는 반례 두 개를 제시하고, 캐시가 있는 결과와 새 캐시로 계산한 결과를 비교하는 검증안을 작성합니다.

과제 5 · 고급

MCP 도구의 성공·실패 계약 작성하기

목표: 에이전트가 호출하는 도구를 작은 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는 “기본적으로 첫 오류에서 중단”하는 도구이므로 전체 원자적 롤백을 가정하지 않습니다.

실습의 완료 기준은 명령을 한 번 실행하는 것이 아니라, 바뀐 것과 보존돼야 할 것을 증거로 설명하는 것입니다.

9관련 기술 심화 학습 로드맵

한 번에 워드프로세서를 만들기보다, 한 경로를 완성하는 6주

매주 읽은 내용을 작은 표·그림·입력 예시로 남기는 학습 계획입니다. 모든 기능을 구현하는 일정이 아니라, 한 기능의 앞뒤를 설명할 수 있도록 범위를 늘리는 순서입니다. 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주차에 고른 명령 하나를 마지막 주까지 계속 따라가면 학습 범위가 덜 흩어집니다.

9-1. 막혔을 때 되돌아갈 질문

마지막 주의 발표 과제
글자 한 번 입력한 결과를 세 장으로 설명하기

첫 장에는 문서 모델과 실행 취소, 둘째 장에는 글리프와 페이지 배치, 셋째 장에는 화면·PDF·MCP 경계를 그립니다. 각 장에 실제 타입이나 함수 이름을 두 개 이상 넣으면 개념과 소스가 연결됩니다.

다른 프로젝트로 가져갈 지식은 Word의 메뉴 개수보다, 상태·계산·출력·플랫폼 경계를 나누는 판단력입니다.

10핵심 키워드 사전

모르는 말이 나오면 이 표로 돌아오기
용어쉬운 설명
clean-room공개 규격과 관찰한 동작 등을 토대로 독립 구현을 지향하는 개발 방식. 이 프로젝트가 밝힌 원칙
크레이트 / workspaceRust 패키지 단위 / 여러 패키지를 공통 설정으로 묶어 관리하는 작업 공간
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 / OPCOffice 문서의 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의 수치와 화면의 좌표를 연결할 때 변환 규칙이 흩어지는 일을 줄일 수 있습니다.

11참고 링크

공식 설명, 구현 근거, 심화 규격을 나누어 읽기

11-1. 프로젝트와 소스 근거

11-2. 관련 기술의 공식 학습 자료

11-3. 현재 상태 (2026-10-23 확인)

항목상태근거와 읽는 법
스타 / 포크15,592 / 4,2282026-10-23 GitHub 기준
생성일2026-10-07GitHub 저장소 메타데이터
최신 릴리스v0.10.0 · 2026-10-22GitHub 기준. Cargo 작업 공간 버전도 0.10.0
최근 push2026-10-23GitHub 메타데이터의 push 날짜
분석 기준 커밋3a4770e · 2026-10-22Merge 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%커밋에 저장된 체크리스트. 전체 호환성 비율이 아님
개발 단계alphaROADMAP.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를 함께 읽어야 합니다.