What does swiss-pdf do?
Convert md / docx / txt / html into an A4 PDF in Swiss International Typographic Style with one click.
White background, black text, ultra-thin dividing lines, large chapter numbers, monospaced numerals, single accent color red #E5484D.
Design tokens are drawn from a Swiss International Style hardcover book (large chapter numbers, ultra-thin separators, single accent color), adapted into a single-flow auto-pagination system, so documents of any length can be processed directly.
How to Use
Method 1: Drag and Drop (Recommended)
Drag a file onto swiss-pdf.bat → an identically named .pdf is generated in the same directory, along with a xxx.pdf-src.html (reviewable HTML source).
Method 2: Command Line
python make.py <input_file> # Generate PDF
python make.py <input_file> --html # Generate HTML only (saved as xxx.html, open in browser to print manually)
python make.py <input_file> -o <output.pdf>
python make.py a.md b.docx c.txt # Batch processing
python make.py <input_file> --no-cover --no-toc
python make.py <input_file> --title "Title" --kick "Kicker" --date 2026.11.09
Supported Input Formats
| Format | Conversion Method | Notes |
|---|---|---|
.md | pandoc | Supports YAML front-matter, GFM tables, lists, code blocks, images |
.docx | pandoc | Heading styles → section titles; tables → styled tables; images auto-extracted |
.txt | Built-in parser | Blank lines separate paragraphs; short lines with numbering (一、/ 1. / Chapter X) → section headers |
.html | Built-in parser | Extracts <body>, removes script/style; <title> used as document title |
Cover Page: YAML Front-Matter
Write this at the very top of your markdown file (for other formats, use --title etc.):
---
title: Urban Cycling Safety White Paper
subtitle: Observations and Recommendations Based on a Three-City Sample Survey
kick: Traffic Governance Research
en: Urban Cycling Safety White Paper
slug: urban-cycling-safety
nature: Public Version
project: Urban Slow Traffic System Research Plan
party_a: Client Name
party_b: Contractor Name
stage: Research → Writing → Finalization
source: 01_Observation Records 02_Interview Notes 03_Plan
date: 2026.11.10
version: v2.1
badges: Public; !Critical; Data Supported
---
badgesseparated by;, prefix!for black-background white text.- If
dateis empty, today's date is used automatically. - It works without front-matter; the cover page will show only the title + date + version placeholder.
Body: Six Styled Blocks
In markdown, wrap them with ::: (each line inside = one item):
::: note
Supplementary notes, caliber reminders. Gray background.
:::
::: quote
Key conclusion citations, 3pt left vertical line.
:::
::: inverse
Black background, white text block – use for the most important statement.
:::
::: warn
Red box warning – the only place where color is allowed.
:::
::: stat
4,280 | Total observation samples || 24 | Observation points || 3 | Cities covered
:::
::: check
Morning rush hour intersections (must be in place by 7:30 AM)
Nighttime illuminated sections (need supplementary lighting)
:::
::: kv
Pages | Plan A 20 pages / Plan C 32 pages
Recommendation | Plan C, but need additional city data
:::
stat uses number | description per cell, || separates cells. kv uses key | value.
Automatic Behaviors
| Behavior | Rule |
|---|---|
| Chapter numbering | # H1 → large number 00/01/02…; if the title already contains "01 " or "一、" numbering, that is retained |
| Table of Contents | Generated only when there are ≥ 5 sections (# + ##) combined; otherwise the page is omitted; section titles have anchors for clickable navigation in the PDF |
| Tables | Columns ≥ 6 automatically reduce font size (table.wide); pure number cells are right-aligned with monospaced font |
| Headers and Footers | Cover page has no header/footer; body page header shows running title on the left, footer shows "Page i of N" on the right |
| Printing | @page A4, margins 14/15/13mm; titles are not separated from body text; table rows do not break across pages |
Additional HTML Source Processing (Verified Extensions)
When the source is a "self-designed webpage", this skill automatically normalizes the following to avoid orphaned numbering or duplicate titles after reflow:
| Source Structure | Processing |
|---|---|
<div class="sec-head"><span class="sec-num">3</span><h2>Title</h2></div> | Convert to template .sec section header (number from span, title from h2) |
<h1>03 Title</h1> / <h1>Title</h1> | Same as above; if no number, auto-increment based on occurrence order |
Standalone paragraph with pure number <p>0</p> | Discard (common when section numbers are used as decorative numerals) |
<header class="masthead"> / <nav> | Discard (cover page is separate; duplicate kicker/big title/table of contents would pollute the body) |
<section> division | Retained as a section container; titles still need sec-head or h1 to enter the TOC |
<div id="xxx" class="chart"> + echarts | Screenshot using headless browser and embed as PNG; wait for networkidle + 2.5s before screenshot |
Prerequisite for chart screenshots: Need network access if echarts uses CDN; for offline environments, inline echarts.min.js in the source.
Fonts and Punctuation
Fonts: Built-in Source Han Sans, Inlined via @font-face
The fonts/ directory contains two files shipped with the repo; make.py inlines their base64 into <style> when generating HTML,
registering them as the font family Swiss Sans SC:
| File | Weight |
|---|---|
SourceHanSansCN-Regular.subset.ttf | 400 |
SourceHanSansCN-Bold.subset.ttf | 700 |
The subset covers about 7100 characters (ASCII + CJK punctuation + all 6763 GB2312 Chinese characters); see fonts/charset.txt.
Benefit: Consistent output on any machine without requiring users to install fonts.
Users can replace or download fonts themselves – three approaches (see fonts/README.md for details):
- Replace files (recommended): Rename your TTF files to the two filenames above and overwrite. Subset them first, otherwise the HTML will be bloated with the full 10 MB:
pyftsubset YourFont-Regular.ttf --text-file=fonts/charset.txt --output-file=fonts/SourceHanSansCN-Regular.subset.ttf - Delete
fonts/:make.pywill skip@font-faceif it can't find the files, falling back to the CSS system font stack (Source Han Sans SC/CN → Noto Sans SC → Noto Sans CJK SC → Source Han Sans CN → PingFang SC → Segoe UI Symbol). - Only modify CSS: Edit the first font name in
--sansat the top oftemplates/swiss.css.
⚠️ Must use .ttf (TrueType outlines), not .otf (CFF). Chromium PDF printing downgrades CFF Web fonts to Type3 glyph instructions (each glyph stored as a drawing path), bloating file size several times. Same 4-page document:
OTF → 328 KB / 205 Type3 glyphs; TTF → 79 KB / 1 Type3 glyph.
Microsoft YaHei lacks glyphs for · (U+00B7), → ↓ ≤ ≥ etc. under Chromium, which renders as boxes, so it has been removed from the primary font stack.
Quotes: National Standard Curly Quotes Enforced Throughout
convert.normalize_quotes() handles this uniformly in the final step, covering titles, cover page, headers, TOC, tables, and styled blocks:
| Input | Output |
|---|---|
「」 | “” |
『』 | ‘’ |
Straight quotes in code " ' | Markdown: pandoc +smart auto-converts to curly quotes; code blocks <code> / <pre> remain unchanged |
The implementation only replaces 4 codepoints (U+300C/D/E/F → U+201C/D/8/9), without touching HTML tag attributes or CSS/JS.
Verified Real Run Records
| Input | Size | Pages | Time |
|---|---|---|---|
| md | 29 KB | 15 | ~6s |
| html | 105 KB + 2 echarts charts | 21 | ~15s |
| docx | heading styles + table | 2 | ~6s |
txt (samples/) | 1.2 KB | 2 | ~5s |
| md long document regression | 71 KB | 21 | ~24s |
Environment Dependencies
| Dependency | Check Command | Behavior When Missing |
|---|---|---|
| pandoc (≥2.19) | Run pandoc -v | md/docx conversion will fail |
| playwright + chromium | Or system Chrome / Edge | PDF rendering will fail |
| python-docx (optional) | Only used for auxiliary parsing | Does not affect main flow |
Specify browser: set SWISS_PDF_CHROME=<path to chrome.exe>
File Structure
swiss-pdf/
├── SKILL.md This file
├── swiss-pdf.bat Drag-and-drop entry point
├── make.py Main flow: convert → assemble → render
├── convert.py Format → HTML fragment + block-level syntax
├── templates/
│ └── swiss.css All styles (design tokens + print rules)
└── samples/ Sample files in three formats for comparison
Limitations (What It Cannot Do)
- Manual pagination like a hardcover book (carefully placed content per page, cross-page spread design) – the template cannot achieve this; hand-crafted HTML is still needed. This skill is positioned as "quick draft for routine documents."
- Illustration layout depends on the original document's image dimensions; no cropping or bleed handling.
- Printed page numbers in the table of contents are not implemented (page numbers cannot be retrieved for elements within the page); section titles are clickable, and the footer shows "Page i / Page N".
- Short table blocks may be pushed entirely to the next page – to avoid key-value tables being broken,
.kvblocks are set to be non-breakable across pages. - Complex Word styles (text boxes, WordArt, multi-level numbered lists) will be degraded when converted by pandoc.