# 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](https://kroki.io) — 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 ```bash go build -o ihk-pdf . ./ihk-pdf -i report.md -o projektarbeit.pdf ``` Or without building: ```bash 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](https://kroki.io). 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`** ```yaml services: kroki: image: yuzutech/kroki environment: - KROKI_MERMAID_HOST=mermaid ports: - "8000:8000" depends_on: - mermaid mermaid: image: yuzutech/kroki-mermaid expose: - "8002" ``` ```bash docker compose up -d go run . -i report.md -kroki http://localhost:8000 -o projektarbeit.pdf ``` > **Note:** The base `yuzutech/kroki` image does not include a Mermaid renderer. The companion `yuzutech/kroki-mermaid` container is required, wired via `KROKI_MERMAID_HOST=mermaid`. --- ## YAML Front Matter Every input file begins with a YAML block delimited by `---`. All fields are optional except where noted. ```yaml --- 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: 1. **Pass 1** — full render into a scratch PDF to collect the page number of every heading, table, and figure. 2. **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:` use `fpdf.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