한국어 기술 문서 제작
한국어 기술·사업 문서를 자립형 HTML로 제작하고 필요하면 PDF로 출력한다.
- 값은
assets/css/tokens.css한 곳에 있고, 공통 CSS 와 모드별 CSS 는 그 token 만 참조한다. - 수치·표·도해는 제공된 데이터에서 프로그램으로 생성한다.
- HTML은 실행 시점에 외부 자원을 요청하지 않는다.
- 본문 글꼴은 동봉한 Pretendard 공식 subset 을 기본으로 내장한다.
--font는 교체할 때만 쓴다.
문장 규약은 korean-report-style 스킬이 담당한다. 이 스킬의 범위는 제작에 한정한다.
1. 시작 전 결정
1.1 모드
| paper | deck | |
|---|---|---|
| 용도 | 분석 · 벤치마크 · 기술보고 · 진행현황 | 회의 · 아젠다 · 의사결정 요청 |
| 방향 | 세로 | 가로 |
| 페이지 | 연속 흐름 | 1 섹션 = 1 페이지 |
| 본문 폭 | 720px | 1120px |
| 밀도 | 산문 전체 | 한 페이지에 한 가지 |
| 템플릿 | assets/paper_template.html | assets/deck_template.html |
| 본문 구조 | <div class="doc"> 안의 <section> | <section class="tile ..."> 나열 |
이 문서에서 layout은 페이지와 요소의 배치를, typesetting은 CSS·글꼴·수식을
반영해 최종 HTML을 생성하는 처리 단계를 뜻한다.
섞지 않는다. 문단이 긴 deck 은 deck의 layout이 어긋난 보고서로 읽히고,
타일 배경을 쓴 paper 는 슬라이드를 잘못 인쇄한 것으로 읽힌다.
1.2 산출물 필요 여부
문서 산출물을 요청받았을 때만 작성한다. 설명·요약·분석은 대화로 답한다.
보고서 만들어줘 · PDF로 뽑아줘 · 문서로 정리해줘 가 신호다.
검수 요청은 Markdown 을 유지한다. 기존 Markdown 문서의 검수 · 점검 · 리뷰 요청은
HTML·PDF 산출물 요청이 아니다. 원본 .md 를 유지하고, HTML·PDF 를 명시적으로 요청받은
경우에만 typesetting 을 수행한다. 검수에서는 Markdown 에 적용되는 검사만 수행하고 결과를 보고한다.
korean-report-style의lint.py— 문체 · 서식- §4.1 의 번호 항목 — 그림 · 표 · 절 번호의 연속성과 본문 참조의 실재
수정을 요청받으면 .md 원본에 반영한다. 렌더 QA(§4)는 HTML 산출물이 있을 때만 해당한다.
1.3 실행 환경
- HTML 빌드에는 Node 20 이상이 필요하다. KaTeX 는 수식 마커(
⟦I⟧·⟦D⟧)가 있는 문서에만 필요하며,mathbuild.js는 작업 디렉터리와 스킬 설치 경로에서 KaTeX를 순서대로 찾는다. - 렌더 QA, 스크린샷, PDF 출력에는 Python 3.11 이상, Playwright, Chromium이 필요하다.
Chromium 이 없으면
qa.py가 설치 명령을 한 줄로 알리고 exit 2 로 종료한다. - 본문 글꼴은
assets/fonts/에 동봉되어 있다. 다른 글꼴을 쓰려면 사용 권한이 있는 WOFF2 파일을 준비한다.
2. 빌드 파이프라인
데이터 ──▶ 생성기(Python) ──▶ raw HTML (수식 마커 + 치환 토큰) │ ▼ 후처리(Node + KaTeX) — mathbuild.js · ⟦I⟧ / ⟦D⟧ 렌더 · tokens.css + base.css + 모드 CSS 삽입 · 본문 글꼴(동봉 또는 --font) · KaTeX woff2 base64 내장 │ ▼ 단일 .html ──▶ headless print ──▶ .pdf
템플릿은 치환 토큰만 담은 얇은 껍데기다. 실제 규칙은 assets/css/ 에 있다.
| 토큰 | 채우는 주체 |
|---|---|
__TITLE__ · __BODY__ | 생성기 |
__FONTCSS__ · __KATEXCSS__ · __TOKENCSS__ · __BASECSS__ · __MODECSS__ | mathbuild.js |
CSS 를 템플릿에 직접 쓰지 않는다. 두 템플릿이 규칙을 각자 복사해 두었다가
deck 의 인쇄 규칙이 paper 의 것에 덮인 사고가 있었다. 값은 css/tokens.css 한 곳에만
둔다. 공통은 css/base.css, 모드별은 css/paper.css · css/deck.css 한 곳에만 둔다.
2.1 생성기 작성
pythonimport sys; sys.path.insert(0, "<스킬경로>/assets") from figures import tbl, wide, fig_gantt, fig_flow, figcap, esc, BADGE_MEAS def I(t): return "⟦I⟧" + t + "⟦/I⟧" # 인라인 수식 def D(t): return "⟦D⟧" + t + "⟦/D⟧" # 디스플레이 수식 def sub(tpl, *args): """%s 를 순서대로 치환하고 %% 를 % 로 되돌린다""" out, i, ai = [], 0, 0 while True: j = tpl.find("%s", i) if j < 0: out.append(tpl[i:]); break out.append(tpl[i:j]); out.append(str(args[ai])); ai += 1; i = j + 2 return "".join(out).replace("%%", "%") BODY = sub('''<section id="s1"><h2><span class="sn">1</span>제목</h2> %s </section>''', tbl(...)) TPL = open("paper_template.html", encoding="utf-8").read() out = TPL.replace("__BODY__", BODY).replace("__TITLE__", "문서 제목") open("raw.html", "w", encoding="utf-8").write(out)
수식 구분자는 반드시 ⟦I⟧·⟦D⟧ 를 쓴다. %%I%% 같은 ASCII 구분자는
I(r"D") 처럼 한 글자 수식에서 %%D%% 를 우연히 만들어 파서가 오인식한다.
실제로 겪은 버그다. 빌드가 구버전 마커를 발견하면 실패한다.
치환 토큰을 주석이나 본문에 적지 않는다. 파이썬 str.replace 는 모든 등장을
바꾸므로, 주석에 __BODY__ 를 적어 두면 본문이 두 번 들어간다. 이것도 실제로 겪었다.
전체 생성 흐름은 assets/paper_template.html 또는 assets/deck_template.html에
assets/figures.py의 도해를 배치한 뒤 assets/mathbuild.js로 후처리한다.
2.2 후처리
bashnode mathbuild.js raw.html out.html \ --assets <스킬경로>/assets
빌드 시점에 KaTeX 로 렌더하고, 렌더 결과가 참조하는 woff2 만 골라 base64 로 삽입한다. 전체 폰트는 1.2 MB, 실제 필요한 것은 160 KB 정도다. 수식 마커가 없는 문서는 KaTeX 를 요구하지 않으며 KaTeX CSS · 글꼴과 그 고지도 생략한다.
실패하면 exit 1 이다. 수식 오류, 치환되지 않은 토큰, 구버전 마커, 없는 폰트 경로가 모두 빌드를 세운다. 조용히 잘못 렌더된 수식이 수식 없는 것보다 나쁘다.
--font 를 생략하면 동봉한 Pretendard subset 을 내장한다. 본문에 subset 밖의
한글 음절이 있으면 경고하고, 그 글자는 system font 로 표시된다.
2.3 PDF
bashpython <스킬경로>/assets/qa.py out.html --pdf --shot shots/
qa.py --pdf가 paper PDF 의 쪽 하단에 제목과 쪽번호를 인쇄한다.
직접 부르려면(아래 예시는 이 footer 를 인쇄하지 않는다):
pythonfrom playwright.sync_api import sync_playwright with sync_playwright() as p: b = p.chromium.launch(); pg = b.new_page() pg.goto('file:///abs/path/out.html'); pg.wait_for_timeout(2500) pg.pdf(path='out.pdf', format='A4', print_background=True) # deck 은 landscape=True b.close()
wait_for_timeout 을 충분히 준다. 폰트·이미지 로드 전에 인쇄하면 깨진다.
3. 도해
모든 도해는 인라인 SVG 다. matplotlib PNG 를 만들지 않는다. 확대해도 선명하고, 토큰을 상속하고, 텍스트가 검색되고, 파일 크기가 증가하지 않는다.
assets/figures.py 에 7종이 준비되어 있다 — 타임라인 · 간트 · 카드그리드 ·
플로우 · 수직선 · 대비막대 · 산점. 좌표는 Python 에서 계산해 SVG 에 리터럴로 박는다.
브라우저 JS 로 계산하면 PDF 출력 시 동일하게 실행되지 않는다.
색은 hex 가 아니라 클래스로 나간다(fi-* 채움 · st-* 선). base.css 가
그 클래스를 CSS 변수에 연결하므로, 다크 타일 위에서 토큰만 바뀌면 도해가 따라간다.
hex 를 박으면 #1d1d1f 글자가 #272729 타일 위에서 사라진다.
상세 규약은 references/figures.md 참조.
3.1 래스터 예외
실제 도면·점군 위에 측정 데이터를 얹은 시각화는 SVG 로 손으로 그릴 수 없다. 이 경우에 한해 래스터를 허용하되 세 조건을 지킨다.
- base64 data URI 로 내장 — 상대 경로 참조는 파일이 이동하면 깨진다
- 테두리를 두른다 —
.plate클래스를 쓴다 (1px hairline, 10px 반경, 최대 820px) - 수치는 네이티브로 병기 — 래스터가 판단 근거를 단독으로 나르지 않게
3.2 외부 SVG
TikZ · matplotlib 처럼 다른 도구가 생성한 벡터 그림은 embed_svg() 로 삽입한다.
pdftocairo 는 출력마다 같은 id(glyph-0-0 · clip-0)를 쓰므로, 그대로 두 개를 삽입하면
뒤 그림이 앞 그림의 글리프를 참조해 글자가 바뀐다. embed_svg() 는 id 와 내부 참조에
접두어를 부여하고 <style> 을 그 그림 안으로 한정한다.
원본 색은 token 을 따르지 않으므로 밝은 surface 에만 배치한다. 다크 타일 위의 외부 SVG 는
qa.py 가 실패로 판정한다. 경로와 실패 조건은 references/figures.md §7,
색 정책은 references/design.md §5.5 참조.
4. QA — 렌더 후 검사
소스를 믿지 않는다. 반드시 브라우저로 렌더해 검사한다.
bashpython <스킬경로>/assets/qa.py out.html --shot shots/
수식 마커·치환 토큰 잔존, 가로 넘침, 컨테이너를 초과하는 표, CSS 삽입 실패, viewBox 밖으로 나간 도해, 캡션 없는 도해를 검사하고 실패하면 exit 1 이다.
deck 은 인쇄 레이아웃까지 검사한다. 타일이 한 쪽(A4 가로 210mm)을 초과하면
실패하고 어느 타일이 몇 px 넘쳤는지 알린다. .tile 은 인쇄 시 overflow:hidden
이라 넘친 내용이 조용히 잘린다 — deck 에서 가장 흔한 실패다.
넘치면 타입을 줄이지 말고 섹션을 쪼갠다(design.md §7.1).
종료 코드 — 0 통과 · 1 검사 실패 · 2 검사를 실행하지 못함(Playwright·Chromium 미설치, 대상 파일 없음, 인자 오류). 2 는 문서가 아니라 환경의 문제이고, stderr 한 줄이 다음 명령을 알린다.
4.1 사람이 봐야 하는 항목
기계가 못 잡는 것은 스크린샷과 PDF 로 확인한다.
인쇄
- 페이지 수가 의도와 맞음 (deck: 섹션 수 = 페이지 수)
- 표 · 수식 · 콜아웃이 페이지 경계에서 잘리지 않음
- 절반 이상 빈 페이지 없음
- 제목이 페이지 끝에 홀로 남지 않음
- 다크 타일이 인쇄 시 반전됨
번호
- 그림 · 표 · 절 · 리스크 번호가 연속
- 본문 참조(
§5.2,그림 8)가 실재 대상을 가리킴
편집을 여러 번 하였다면 매번 돌린다. 항목 하나를 삭제하면 번호와 참조가 줄줄이 어긋난다.
5. 편집 요청 처리
문서 수정 요청은 텍스트 치환이 아니라 구조 연산으로 처리한다.
| 요청 | 함께 해야 할 것 |
|---|---|
| 항목 삭제 | 이후 번호 재정렬 · 본문 참조 갱신 · 요약문 검토 · 미사용 정의 제거 |
| 절 삭제 | 인자 목록 수 정합 · 도해 함수 미사용 여부 · 하위 절 번호 |
| 표 열 삭제 | 열이 사라져 무의미해진 도입 문장 · 남은 열의 정합 |
| 문구 완화 | 같은 표현이 도해 라벨 · 캡션 · 표 셀에도 있는지 전역 확인 |
연쇄를 발견하면 알린다. "S3 를 지우면 아래 요약문이 S4 를 참조하는데 함께 고칠까요" 처럼 묻는다. 조용히 고치면 사용자가 무엇이 바뀌었는지 모른다.
6. 판단이 필요한 지점
- 한계 서술 삭제 —
../korean-report-style/SKILL.md§1.6·§7에 따라 포함 여부를 판단한다. 삭제 후 남는 주장이 검증 범위를 초과하는 경우에는 조건을 함께 명시하거나 주장을 한정한다. - 정정 고지 — 이전 공유 수치와 산출 기준이 달라졌다면, 톤을 낮추더라도 사실은 남긴다.
- 분석과 document layout의 경계 — 데이터에서 관계를 찾아내는 것은 이 스킬의 범위 밖이지만, 문서의 가치를 상당 부분 결정한다. 수치가 주어지면 정합성을 검증하고 이상이 있으면 알린다.
7. 참고 파일
references/design.md— 색 · 타이포 · 컴포넌트 · 인쇄 규약 전체references/figures.md— 도해 7종의 사용법과 좌표 규약assets/paper_template.html·assets/deck_template.htmlassets/css/tokens.cssassets/css/base.css·assets/css/paper.css·assets/css/deck.cssassets/figures.py·assets/mathbuild.js·assets/qa.pyassets/fonts/— Pretendard 공식 subset 과 license


