Overview
with the single weasyprint enginerenders all input as PDF. Regardless of the input form,
입력 → HTML → weasyprint → PDF it is unified into one path, faithfully handling both of the following scenarios.
- Styled HTML reports (예:
moai-officer:doc-html-report·moai-officer:doc-html-slideoutput, or user-made design HTML) → renders its own CSS as is to preserve the screen design. Modern CSS layouts such as two-column card grids, custom colors, and web fonts are not broken. - Markdown · structured JSON · plain text → rendered by applying default document formatting (A4, tables, heading styles).
CJK (Korean·Chinese·Japanese) glyphs are blocked from breaking by embedding the bundled Noto Sans CJK font as @font-face, and
if Noto Sans CJK KRis installed on the system, fontconfig automatically falls back.
Why the single weasyprint engine: weasyprint faithfully renders full CSS (layout, color, web fonts, tables). "Turn this HTML report into a PDF exactly as designed" is the most common request, and this path satisfies that requirement precisely. Coordinate-based low-level rendering (e.g. PyMuPDF insert_text) cannot reproduce modern CSS layouts, so it is not used.
Workflow
0. [폰트 확인] → 시스템 Noto Sans CJK KR 존재 확인, 없으면 폰트 미지정 폴백 (weasyprint fontconfig 자동 폴백)
1. [입력 감지] → HTML(완성 문서) / Markdown / JSON / Text 자동 감지
2. [HTML 빌드] → 완성 HTML은 그대로, 그 외는 HTML로 변환 (+ 기본 문서 CSS)
3. [폰트 주입] → Noto Sans CJK @font-face CSS 결합 (CJK 깨짐 방지)
4. [렌더] → weasyprint HTML.write_pdf() 로 A4 PDF 생성
5. [자체 검수] → 산출 PDF 재확인 (페이지 수·CJK 표시·플레이스홀더 잔존)
The core execution is performed directly with the inline weasyprint code below (no separate script). It detects HTML / Markdown / JSON / Text by the input file extension and content, branches accordingly, preserves the completed HTML's own CSS, and applies default formatting + Noto Sans CJK to the rest.
Step 0: Font assurance (conditional path)
CJK fonts are handled in the following priority order — without a separate download script:
-
System fonts first: if
Noto Sans CJK KRis installed on the system, weasyprint fontconfig automatically falls back, so@font-faceCJK is displayed safely even without it. This applies to most Cowork sandboxes and Linux/macOS. -
Embed if bundled fonts exist:
${CLAUDE_PLUGIN_ROOT}/skills/doc-pdf/assets/fonts/If Noto Sans CJK OTF exists in@font-faceis automatically embedded by -
When neither exists: guide the user to install system
Noto Sans CJK KR. The commands by operating system are as follows, and automatic bulk downloading is not performed.OS Install Windows fonts.google.com/noto/specimen/Noto+Sans+KRDownload from , extract the archive → select all font files → right-click → Install macOS brew install --cask font-noto-sans-cjk-krDebian/Ubuntu apt install fonts-noto-cjk
Steps 1–4: weasyprint rendering inline code
Run the code below as is:
from weasyprint import HTML, CSS
from pathlib import Path
import os
FONT_DIR = Path(os.environ.get("CLAUDE_PLUGIN_ROOT", str(Path(__file__).parent.parent))) / "assets" / "fonts"
# 번들 Noto Sans CJK를 @font-face로 등록 (weight별)
# 경로 → URL 변환은 반드시 Path.as_uri()로 한다. 문자열 "file://" + 경로 조립은
# Windows에서 file://C:\... 형태의 잘못된 URL이 되어 폰트 로드에 실패한다.
FONT_FACE = "".join(
f"@font-face{{font-family:'Noto Sans CJK';font-weight:{w};"
f"src:url('{(FONT_DIR / f'NotoSansCJK-{n}.otf').as_uri()}');}}"
for w, n in {300: "Light", 400: "Regular", 500: "Medium", 700: "Bold"}.items()
if (FONT_DIR / f"NotoSansCJK-{n}.otf").exists()
)
# (A) 완성 스타일 HTML → 자체 CSS 보존, CJK 폰트만 보강
HTML(string=styled_html, base_url=".").write_pdf("out.pdf", stylesheets=[CSS(string=FONT_FACE)])
# (B) Markdown/JSON/Text에서 만든 HTML → 기본 문서 CSS + 폰트
HTML(string=plain_html, base_url=".").write_pdf("out.pdf", stylesheets=[CSS(string=FONT_FACE + BASE_CSS)])
BASE_CSS는 @page { size:A4; margin:20mm } + with body/heading/table styles, font-family에
'Noto Sans CJK'specifies that CJK is always rendered with an embedded font.
This skill replaced references to absent scripts (render_pdf.py, download_fonts.py) with Principle A (prompt/inline code path) — run the weasyprint code above directly, and use the system fallback as the primary path for fonts.
Dependencies
pip install weasyprint # 핵심 렌더링 엔진 (풀 CSS → PDF)
pip install markdown # Markdown → HTML 변환 (선택, 없으면 최소 폴백)
weasyprint uses the system library
cairo/pango/gdk-pixbufMost Cowork sandboxes and Linux/macOS environments include it by default, so it works withpip install weasyprintalone.When running locally on Windows, these libraries are not provided by default.
pip install weasyprinteven if succeeds,import weasyprintat the stepcannot load library 'libpango...'it fails with. You must install the GTK runtime first — see the Windows row in §Troubleshooting below. In environments where installation is difficult, instead of PDFmoai-officer:doc-html-reportguide users to a path of printing the artifact to "PDF" in the browser.
Input Format Specification
Completed HTML (design preserved)
<html>·<body>·<!doctype html> if there is a signal, treat it as a completed documentand render its own <style>and CSS
as-is. moai-officer:doc-html-report·moai-officer:doc-html-slide If you pass the artifact as-is,
you get a PDF with the same design as the screen.
Structured JSON Spec
{
"title": "문서 제목",
"subtitle": "부제목 (선택)",
"author": "작성자 (선택)",
"date": "2026-01-01 (선택)",
"sections": [
{
"heading": "섹션 제목",
"level": 2,
"body": "본문 텍스트 (마크다운 인라인 지원)",
"table": { "headers": ["열1", "열2"], "rows": [["a", "b"]] },
"image": { "path": "./chart.png", "caption": "그림 1" }
}
]
}
Markdown Input Example
# 프로젝트 보고서
## 1. 개요
본 보고서는 ...
## 2. 결과
| 항목 | 수치 |
|------|------|
| 완료율 | 95% |
Usage Examples
- "Make this HTML report into a PDF with the design as-is" → Completed HTML path (CSS preserved)
- "Weekly report Markdown into a PDF without broken Korean" → Markdown path (default formatting + CJK)
- "Generate a branch report PDF with the JSON structure below" → JSON path
- "Output a Korean·Japanese·Chinese·English mixed manual as PDF" → multilingual embedding
Output Format
- File format:
.pdf(ISO 32000 PDF standard) - Page size: A4 (210 × 297mm, default margin 20mm) — completed HTML prioritizes its own
@pagesettings - Font: Noto Sans CJK (single family covering Korean·Japanese·Simplified Chinese·Traditional Chinese·Latin, subset embedding)
- Encoding: Full UTF-8 support
Self-inspection
After generating the PDF, reopen the output .pdfand check for remaining placeholders, page count shortfall, broken CJK encoding, missing designby self-inspection, and if there are problems, automatically fix and regenerate, and report the final PASS/FAIL.
Troubleshooting
| Symptom | Cause | Solution |
|---|---|---|
| Korean/Hanja appear as □□□ or blank | CJK font unresolved | System Noto Sans CJK KR installation (Windows: download from Google Fonts and right-click to install, macOS brew install --cask font-noto-sans-cjk-kr, Debian/Ubuntu apt install fonts-noto-cjk), or place Noto Sans CJK OTF in assets/fonts/and re-render |
ModuleNotFoundError: weasyprint |
weasyprint not installed | pip install weasyprint run |
cannot load library 'libpango...' (Windows) |
GTK runtime absent — on Windows, resolved by pip installalone does not work |
① After installing MSYS2, UCRT64 셸in pacman -S mingw-w64-ucrt-x86_64-pango (per WeasyPrint's official guidance — not the MINGW64 shell's mingw-w64-x86_64-pango, not that), then $env:WEASYPRINT_DLL_DIRECTORIES="C:\msys64\ucrt64\bin"and set new terminal, then retry. If you don't specify the DLL path, Pango won't be found even after installation. ② Or GTK for Windows Runtime installer. ③ If both are difficult, moai-officer:doc-html-reportto create the HTML, then fall back to the browser's "Print to PDF" (Ctrl+P → Destination: Save as PDF) |
cannot load library 'libpango...' (macOS/Linux) |
Missing system libraries | (Debian/Ubuntu) apt install libpango-1.0-0 libpangocairo-1.0-0, (macOS) brew install pango |
| HTML design breaks in PDF | Completed HTML mistaken for a fragment | Include <html>/<body> wrapper in the input, or pass it as an --in *.html file |
| Web fonts (CDN) not applied | Offline/CDN blocked | Inline the fonts in the HTML or substitute system fonts (offline PDFs should avoid CDN dependencies) |
| Tables split at page boundaries | CSS not specified | tr { page-break-inside: avoid; } add |
Related skills / handoff
| Skill | Relationship | When to use |
|---|---|---|
moai-officer:doc-html-report |
before (generate HTML report → convert to PDF with this skill) | "Make a report and also a PDF" flow |
moai-officer:doc-html-slide |
before (generate HTML slides → convert to PDF) | Distribute the slide deck as a PDF |
moai-officer:doc-docx |
alternative (editable Word output) | When the recipient needs an editable file |
moai-coworker:ai-slop-reviewer |
after (review AI patterns in text output) | Review the manuscript before generating the text PDF |
Technical reference
- weasyprint official documentation: https://doc.courtbouillon.org/weasyprint/stable/
- Noto Sans CJK GitHub: https://github.com/notofonts/noto-cjk
- Noto Sans CJK release (otf download): https://github.com/notofonts/noto-cjk/releases
- SIL OFL 1.1 license: https://openfontlicense.org/open-font-license-official-text/
- PDF ISO 32000 standard: https://opensource.adobe.com/dc-acrobat-sdk-docs/