# 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
