346 lines
10 KiB
Markdown
346 lines
10 KiB
Markdown
# 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
|