InlineSpan for rich text support, update row preparation, and improve PDF formatting logic.
MarkdownToIHKChemnitz
A Go CLI tool that converts a single Markdown file into a print-ready PDF compliant with the IHK Chemnitz project documentation guidelines (Verordnung 2020) and DIN 5008 formatting rules.
Features
- DIN 5008 page layout — correct margins, font, line spacing, Blocksatz
- Two-pass rendering for a page-accurate table of contents
- Roman/Arabic page numbering with automatic section detection
- Multi-line table cells with uniform row height per row
- Named tables (Tabellenverzeichnis) and captioned figures (Abbildungsverzeichnis)
- Numbered code blocks with line-number gutter and long-line wrapping
- Diagram rendering via Kroki — Mermaid, PlantUML, and more
- Inline landscape diagram pages (
@DiagrammQuer:) - Appendices in portrait and landscape — images, tables, and diagrams
- Abbreviation list and glossary from YAML front matter
- Bibliography sorted alphabetically from inline
@Quelle:directives - Declaration of authenticity page (pre-written legal text)
Prerequisites
- Go 1.22 or later
- Internet access or a local Kroki instance for diagram rendering (see below)
Build & Run
go build -o ihk-pdf .
./ihk-pdf -i report.md -o projektarbeit.pdf
Or without building:
go run . -i report.md -o projektarbeit.pdf
CLI Flags
| Flag | Default | Description |
|---|---|---|
-i |
report.md |
Input Markdown file |
-o |
projektarbeit.pdf |
Output PDF file |
-kroki |
https://kroki.io |
Kroki base URL for diagram rendering |
Diagram Rendering with Kroki
Mermaid and PlantUML fenced code blocks are rendered server-side via Kroki. The resulting PNG is cached locally using a SHA-256 hash of the diagram source, so unchanged diagrams are not re-fetched.
By default the public instance at https://kroki.io is used. If your network blocks it, run a local instance with Docker Compose:
docker-compose.yml
services:
kroki:
image: yuzutech/kroki
environment:
- KROKI_MERMAID_HOST=mermaid
ports:
- "8000:8000"
depends_on:
- mermaid
mermaid:
image: yuzutech/kroki-mermaid
expose:
- "8002"
docker compose up -d
go run . -i report.md -kroki http://localhost:8000 -o projektarbeit.pdf
Note: The base
yuzutech/krokiimage does not include a Mermaid renderer. The companionyuzutech/kroki-mermaidcontainer is required, wired viaKROKI_MERMAID_HOST=mermaid.
YAML Front Matter
Every input file begins with a YAML block delimited by ---. All fields are optional except where noted.
---
student:
name: "Max Mustermann" # required
profession: "Fachinformatiker Fachrichtung Anwendungsentwicklung"
company: "Musterfirma GmbH"
supervisor: "Sabine Supervisor"
project:
title: "Titel der Projektarbeit" # required
subtitle: "Optionaler Untertitel" # optional
period: "Sommer 2026"
abbreviations: # optional → Abkürzungsverzeichnis
- abbr: "API"
meaning: "Application Programming Interface"
- abbr: "IHK"
meaning: "Industrie- und Handelskammer"
glossary: # optional → Glossar (after appendices)
- term: "Goldmark"
definition: "Ein CommonMark-konformer Markdown-Parser für Go."
---
Document Structure
The generated PDF follows the mandatory IHK Chemnitz order:
| # | Section | Page Numbering |
|---|---|---|
| 1 | Title page | none |
| 2 | Table of contents | Roman (II, III, …) |
| 3 | Abbreviation list | Roman (from YAML, optional) |
| 4 | Foreword / front-matter sections | Roman |
| 5 | Main body chapters | Arabic (1, 2, …) |
| 6 | Bibliography | Arabic |
| 7 | List of tables | Arabic |
| 8 | List of figures | Arabic |
| 9 | Appendices | Arabic |
| 10 | Glossary | Arabic (from YAML, optional) |
| 11 | Declaration of authenticity | none |
Front-matter detection
Level-1 headings named Vorwort, Einleitung, or Abkürzungsverzeichnis stay in the Roman-numbered front matter. Every other level-1 heading triggers the switch to Arabic numbering.
DIN 5008 / IHK Formatting Rules
| Property | Value |
|---|---|
| Paper | A4 |
| Left margin | 30 mm |
| Right margin (Korrekturrand) | 40 mm |
| Top margin | 20 mm |
| Bottom margin | 25 mm |
| Body font | Helvetica (Arial) 12 pt, black |
| Body line spacing | 1.5× → 6.35 mm |
| Heading font | Helvetica Bold 14 pt |
| Caption / footnote | Helvetica 10 pt |
| List line spacing | 1.2× → 5.0 mm |
| Alignment | Justified (Blocksatz) |
| Page number position | Centered at bottom |
Directives
Directives are plain paragraphs starting with @. They are consumed by the parser and never appear in the PDF as raw text. Multiple directives may appear in a single paragraph, one per line.
@Quelle: — Bibliography entry
@Quelle: Autor, Titel, Verlag, Jahr
Registers a bibliography entry. All entries are collected and rendered alphabetically at the end of the document in the Literaturverzeichnis. May appear anywhere in the document, any number of times.
@Tabelle: — Named table
Place immediately before a Markdown table to assign it a name and record it in the Tabellenverzeichnis:
@Tabelle: Übersicht der Programmiersprachen
| Sprache | Paradigma | Typsystem |
|---------|-----------|-----------|
| Go | Imperativ | Statisch |
| Python | Multi | Dynamisch |
@TabelleAnhang: — Table as portrait appendix
Sends the following table to the appendix on a portrait A4 page as a numbered Anlage:
@TabelleAnhang: Vollständige Fehlerliste
| Code | Beschreibung | Schwere |
|------|--------------------|----------|
| E001 | Datei nicht gefunden | Kritisch |
@TabelleAnhangQuer: — Table as landscape appendix
Same as @TabelleAnhang: but placed on a landscape A4 page (297 × 210 mm, 15 mm symmetric margins). Use for wide tables that do not fit in portrait:
@TabelleAnhangQuer: Breite Vergleichsmatrix
| Kriterium | Option A | Option B | Option C | Option D |
|-----------|----------|----------|----------|----------|
| Leistung | Gut | Sehr gut | Befriedigend | Gut |
@Anhang: — Image as portrait appendix
@Anhang: Netzwerkdiagramm | diagrams/network.png
Adds an image file as a numbered Anlage on a portrait appendix page. The format is Title | relative/path/to/image.
@AnhangBildQuer: — Image as landscape appendix
@AnhangBildQuer: Großes Architekturdiagramm | diagrams/arch.png
Same as @Anhang: but placed on a landscape A4 page. Useful for wide images.
@AnhangUML: — Diagram as portrait appendix
Renders the immediately following Mermaid or PlantUML code block via Kroki and places the result as a numbered Anlage on a portrait appendix page:
@AnhangUML: Datenbankschema
` ``plantuml
@startuml
entity User {
+ id : int
+ name : string
}
@enduml
` ``
@AnhangUMLQuer: — Diagram as landscape appendix
Same as @AnhangUML: but placed on a landscape A4 page with 15 mm symmetric margins. Use for complex diagrams that need more horizontal space:
@AnhangUMLQuer: Vollständige Modulübersicht
` ``mermaid
graph TD
A --> B --> C
` ``
@DiagrammQuer: — Inline landscape diagram page
Renders the following diagram on a dedicated landscape page inline in the document (not in the appendix). A figure caption is added and the figure is recorded in the Abbildungsverzeichnis. A fresh portrait page opens automatically afterwards:
@DiagrammQuer: Systemarchitektur – Zwei-Pass-Rendering
` ``mermaid
graph LR
MD[report.md] --> Parser --> AST --> Renderer --> PDF
` ``
Directive Summary Table
| Directive | Format | Placement | Orientation |
|---|---|---|---|
@Quelle: |
@Quelle: Text |
Inline anywhere | — |
@Tabelle: |
@Tabelle: Name |
Before a table | — |
@TabelleAnhang: |
@TabelleAnhang: Name |
Before a table | Portrait appendix |
@TabelleAnhangQuer: |
@TabelleAnhangQuer: Name |
Before a table | Landscape appendix |
@Anhang: |
@Anhang: Title | path |
Standalone | Portrait appendix |
@AnhangBildQuer: |
@AnhangBildQuer: Title | path |
Standalone | Landscape appendix |
@AnhangUML: |
@AnhangUML: Title |
Before diagram block | Portrait appendix |
@AnhangUMLQuer: |
@AnhangUMLQuer: Title |
Before diagram block | Landscape appendix |
@DiagrammQuer: |
@DiagrammQuer: Caption |
Before diagram block | Landscape inline page |
File Structure
.
├── main.go # Entry point, CLI flags, two-pass pipeline
├── config.go # Config struct matching the YAML front matter
├── markdown_parser.go # Goldmark AST walker, directive handling
├── pdf_renderer.go # IHKRenderer struct, DIN 5008 constants, appendix registry
├── pdf_content.go # Paragraphs, lists, tables, images, code blocks
├── pdf_toc.go # TOC, list of tables, list of figures
├── pdf_pages.go # Title page, declaration, bibliography, appendices, glossary
├── pdf_numbering.go # Roman/Arabic page numbering helpers
├── diagram.go # Kroki HTTP client, SHA-256 image cache
├── docker-compose.yml # Local Kroki + kroki-mermaid containers
└── report.md # Sample document demonstrating all features
Two-Pass Rendering
The tool renders the document twice:
- Pass 1 — full render into a scratch PDF to collect the page number of every heading, table, and figure.
- Pass 2 — final render using the TOC index from pass 1, producing the output PDF.
Only tocItems are transferred between passes. tableItems and figureItems are rebuilt during pass 2 to avoid duplicates in the respective lists.
Landscape Pages — Technical Notes
- Landscape appendices and
@DiagrammQuer:usefpdf.AddPageFormat("L", {Wd:297, Ht:210})with 15 mm symmetric margins. No 40 mm Korrekturrand — examiners do not annotate diagram or data pages. - After any landscape page,
AddPage()reverts to the default portrait orientation (A4 "P") set at construction time — no explicit orientation restore is needed for subsequent content. - Portrait margins (30/20/40 mm) are restored immediately after the landscape page is closed so all following content is formatted correctly.
License
MIT