Refactor PDF content rendering: improve list indentation logic, add numbered code block rendering with gutter, and update text wrapping alignment.

This commit is contained in:
Sebastian Unterschütz
2026-05-12 19:23:44 +02:00
parent 81745b5f48
commit 436cdcc516
6 changed files with 95 additions and 21 deletions
+6 -12
View File
@@ -4,20 +4,12 @@
<option name="autoReloadType" value="ALL" /> <option name="autoReloadType" value="ALL" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="c64f46d5-641a-468c-8fc1-94edec1f2deb" name="Changes" comment="Initial commit: added Markdown to IHK Chemnitz PDF converter with core structure and features, including YAML config, Goldmark parser, and PDF renderer."> <list default="true" id="c64f46d5-641a-468c-8fc1-94edec1f2deb" name="Changes" comment="Remove outdated `toc_pages.txt`, add new Go modules for IHK Chemnitz PDF rendering including diagrams, tables, and TOC functionality.">
<change afterPath="$PROJECT_DIR$/it-berufe-handreichung-vo2020-data(1).pdf" afterDir="false" /> <change beforePath="$PROJECT_DIR$/MarkdownToIHKChemnits" beforeDir="false" afterPath="$PROJECT_DIR$/MarkdownToIHKChemnits" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config.go" beforeDir="false" afterPath="$PROJECT_DIR$/config.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/img-000.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/img-001.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/img-002.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/img-003.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/main.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/markdown_parser.go" beforeDir="false" afterPath="$PROJECT_DIR$/markdown_parser.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/markdown_parser.go" beforeDir="false" afterPath="$PROJECT_DIR$/markdown_parser.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/pdf_renderer.go" beforeDir="false" afterPath="$PROJECT_DIR$/pdf_renderer.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/pdf_content.go" beforeDir="false" afterPath="$PROJECT_DIR$/pdf_content.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/projektarbeit.pdf" beforeDir="false" afterPath="$PROJECT_DIR$/projektarbeit.pdf" afterDir="false" /> <change beforePath="$PROJECT_DIR$/projektarbeit.pdf" beforeDir="false" afterPath="$PROJECT_DIR$/projektarbeit.pdf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/report.md" beforeDir="false" afterPath="$PROJECT_DIR$/report.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/report.md" beforeDir="false" afterPath="$PROJECT_DIR$/report.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/toc_pages.txt" beforeDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -101,7 +93,9 @@
</component> </component>
<component name="VcsManagerConfiguration"> <component name="VcsManagerConfiguration">
<MESSAGE value="Initial commit: added Markdown to IHK Chemnitz PDF converter with core structure and features, including YAML config, Goldmark parser, and PDF renderer." /> <MESSAGE value="Initial commit: added Markdown to IHK Chemnitz PDF converter with core structure and features, including YAML config, Goldmark parser, and PDF renderer." />
<option name="LAST_COMMIT_MESSAGE" value="Initial commit: added Markdown to IHK Chemnitz PDF converter with core structure and features, including YAML config, Goldmark parser, and PDF renderer." /> <MESSAGE value="Remove outdated IHK guideline text and refactor PDF renderer for improved modularity, DIN 5008 compliance, and glossary/abbreviation support." />
<MESSAGE value="Remove outdated `toc_pages.txt`, add new Go modules for IHK Chemnitz PDF rendering including diagrams, tables, and TOC functionality." />
<option name="LAST_COMMIT_MESSAGE" value="Remove outdated `toc_pages.txt`, add new Go modules for IHK Chemnitz PDF rendering including diagrams, tables, and TOC functionality." />
</component> </component>
<component name="XDebuggerManager"> <component name="XDebuggerManager">
<breakpoint-manager> <breakpoint-manager>
Binary file not shown.
+2 -2
View File
@@ -119,8 +119,8 @@ func RenderAST(doc ast.Node, content []byte, r *IHKRenderer) error {
} }
// Fall through: render as plain code block on error // Fall through: render as plain code block on error
} }
// Render non-diagram code blocks as monospace paragraphs // Render as a numbered code block (gutter + monospace body).
r.RenderParagraphSpans([]InlineSpan{{Text: code, Code: true}}) r.RenderCodeBlock(lang, code)
return ast.WalkSkipChildren, nil return ast.WalkSkipChildren, nil
// ── Images ──────────────────────────────────────────────────────────── // ── Images ────────────────────────────────────────────────────────────
+86 -6
View File
@@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"github.com/go-pdf/fpdf" "github.com/go-pdf/fpdf"
) )
@@ -89,13 +90,28 @@ func (r *IHKRenderer) RenderListItem(spans []InlineSpan, ordered bool, index, in
r.pdf.SetFont("Helvetica", "", dinFontBody) r.pdf.SetFont("Helvetica", "", dinFontBody)
lm, _, _, _ := r.pdf.GetMargins() lm, _, _, _ := r.pdf.GetMargins()
indentMM := float64(indent+1) * 8.0
r.pdf.SetX(lm + indentMM) // 3 mm base indent + 5 mm per nesting level (reduced from previous 8 mm per level)
indentMM := 3.0 + float64(indent)*5.0
prefix := "• " prefix := "• "
if ordered { if ordered {
prefix = strconv.Itoa(index) + ". " prefix = strconv.Itoa(index) + ". "
} }
prefixW := r.pdf.GetStringWidth(prefix) + 1.0
// textX is where the item text starts; wrapped lines must align here too.
textX := lm + indentMM + prefixW
textW := r.usableWidth() - indentMM - prefixW
// Temporarily move the left margin to textX so that MultiCell wraps
// continuation lines flush with the text, not back to the page margin.
r.pdf.SetLeftMargin(textX)
defer r.pdf.SetLeftMargin(lm)
// Render bullet/number; CellFormat advances X to textX automatically.
r.pdf.SetX(lm + indentMM)
r.pdf.CellFormat(prefixW, dinLineHtBody, r.tr(prefix), "", 0, "L", false, 0, "")
hasFormatting := false hasFormatting := false
for _, s := range spans { for _, s := range spans {
@@ -105,15 +121,13 @@ func (r *IHKRenderer) RenderListItem(spans []InlineSpan, ordered bool, index, in
} }
} }
uw := r.usableWidth() - indentMM
if !hasFormatting { if !hasFormatting {
text := prefix text := ""
for _, s := range spans { for _, s := range spans {
text += s.Text text += s.Text
} }
r.pdf.MultiCell(uw, dinLineHtBody, r.tr(text), "", "L", false) r.pdf.MultiCell(textW, dinLineHtBody, r.tr(text), "", "L", false)
} else { } else {
r.pdf.Write(dinLineHtBody, r.tr(prefix))
for _, span := range spans { for _, span := range spans {
style := fontStyle(span.Bold, span.Italic) style := fontStyle(span.Bold, span.Italic)
if span.Code { if span.Code {
@@ -254,6 +268,72 @@ func (r *IHKRenderer) RenderImage(path string, caption string) {
r.pdf.Ln(dinSpaceAfterParagraph) r.pdf.Ln(dinSpaceAfterParagraph)
} }
// Code block layout constants.
const (
codeFont = 9.0 // pt — Courier, smaller than body text
codeLineHt = 4.5 // mm ≈ 9 pt × 1.3 line spacing
codeGutterW = 11.0 // mm — column reserved for line numbers
)
// RenderCodeBlock renders a fenced code block with a line-number gutter.
//
// Layout:
//
// ┌──────┬──────────────────────────────────┐
// │ 1 │ package main │
// │ 2 │ │
// │ 3 │ func main() { … } │
// └──────┴──────────────────────────────────┘
//
// The language label is shown in small italic text above the block.
// Lines that exceed the printable width are clipped — code should be
// formatted to reasonable lengths before conversion.
func (r *IHKRenderer) RenderCodeBlock(lang, code string) {
lines := strings.Split(strings.TrimRight(code, "\n"), "\n")
if len(lines) == 0 {
return
}
r.pdf.Ln(dinSpaceAfterParagraph)
// Language label — top-right, italic, grey
if lang != "" {
r.pdf.SetFont("Helvetica", "I", dinFontCaption)
r.pdf.SetTextColor(100, 100, 100)
r.pdf.CellFormat(0, dinLineHtCaption, r.tr(lang), "", 1, "R", false, 0, "")
r.pdf.SetTextColor(0, 0, 0)
}
lm, _, _, bm := r.pdf.GetMargins()
_, pageH := r.pdf.GetPageSize()
uw := r.usableWidth()
codeW := uw - codeGutterW
r.pdf.SetFont("Courier", "", codeFont)
for i, line := range lines {
// Start a new page if this line would fall below the bottom margin.
if r.pdf.GetY()+codeLineHt > pageH-bm {
r.pdf.AddPage()
}
// Gutter: darker grey, right-aligned line number.
r.pdf.SetFillColor(218, 218, 218)
r.pdf.SetX(lm)
r.pdf.CellFormat(codeGutterW, codeLineHt,
fmt.Sprintf("%4d ", i+1), "", 0, "R", true, 0, "")
// Code line: lighter grey, left-aligned, small leading space.
r.pdf.SetFillColor(246, 246, 246)
r.pdf.CellFormat(codeW, codeLineHt,
r.tr(" "+line), "", 1, "L", true, 0, "")
}
// Reset colours so subsequent content is unaffected.
r.pdf.SetFillColor(255, 255, 255)
r.pdf.Ln(dinSpaceAfterParagraph)
}
// fontStyle returns the fpdf font style string for a given bold/italic combination. // fontStyle returns the fpdf font style string for a given bold/italic combination.
func fontStyle(bold, italic bool) string { func fontStyle(bold, italic bool) string {
switch { switch {
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -48,7 +48,7 @@ manuelle Sorgfalt bei jedem Absatz.
Ziel ist ein Go-Tool, das **Markdown** in PDF umwandelt und dabei alle Ziel ist ein Go-Tool, das **Markdown** in PDF umwandelt und dabei alle
formalen Anforderungen der IHK Chemnitz erfüllt. Es soll: formalen Anforderungen der IHK Chemnitz erfüllt. Es soll:
- die Prüfungsvorbereitung erleichtern, - die Prüfungsvorbereitung erleichtern, die Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen unddie Qualität der Dokumente *einheitlich* sicherstellen und
- die Qualität der Dokumente *einheitlich* sicherstellen und - die Qualität der Dokumente *einheitlich* sicherstellen und
- den Prozess vollständig automatisieren. - den Prozess vollständig automatisieren.