Remove outdated toc_pages.txt, add new Go modules for IHK Chemnitz PDF rendering including diagrams, tables, and TOC functionality.
This commit is contained in:
+107
-485
@@ -1,524 +1,136 @@
|
||||
// Package main implements a Markdown-to-PDF converter that produces documents
|
||||
// compliant with IHK Chemnitz project documentation guidelines (Verordnung 2020)
|
||||
// and DIN 5008 formatting rules.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-pdf/fpdf"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pdf/fpdf"
|
||||
)
|
||||
|
||||
type NumberingType int
|
||||
|
||||
// IHK Chemnitz / DIN 5008 format constants.
|
||||
// Source: "Hinweise zur Erarbeitung der Dokumentation über die Projektarbeit",
|
||||
// IHK Chemnitz, Verordnung 2020.
|
||||
const (
|
||||
NumNone NumberingType = iota
|
||||
NumRoman
|
||||
NumArabic
|
||||
// Page margins in mm
|
||||
dinMarginLeft = 30.0 // 3.0 cm left margin
|
||||
dinMarginRight = 40.0 // 4.0 cm right margin — Korrekturrand (examiner correction space)
|
||||
dinMarginTop = 20.0 // 2.0 cm top margin
|
||||
dinMarginBottom = 25.0 // 2.5 cm bottom margin
|
||||
|
||||
// Font sizes in pt — Arial/Helvetica, black
|
||||
dinFontBody = 12.0 // body text
|
||||
dinFontHeading = 14.0 // headings, bold
|
||||
dinFontCaption = 10.0 // captions and footnotes
|
||||
|
||||
// Line heights in mm — 1½ line spacing: pt × 1.5 × (25.4 / 72)
|
||||
dinLineHtBody = 6.35 // 12 pt × 1.5 = 18 pt = 6.35 mm
|
||||
dinLineHtHeading = 7.41 // 14 pt × 1.5 = 21 pt = 7.41 mm
|
||||
dinLineHtCaption = 5.29 // 10 pt × 1.5 = 15 pt = 5.29 mm
|
||||
|
||||
// Vertical spacing in mm
|
||||
// IHK: two blank lines before a heading that is not at the top of the page;
|
||||
// one blank line after every heading.
|
||||
dinSpaceBeforeHeading = 2 * dinLineHtBody // ≈ 12.7 mm
|
||||
dinSpaceAfterHeading = dinLineHtBody // ≈ 6.35 mm
|
||||
dinSpaceAfterParagraph = 4.0 // between body paragraphs
|
||||
)
|
||||
|
||||
type TOCItem struct {
|
||||
Level int
|
||||
Title string
|
||||
PageStr string
|
||||
}
|
||||
|
||||
type TableItem struct {
|
||||
Title string
|
||||
PageStr string
|
||||
}
|
||||
|
||||
// Appendix holds the title and image path for one annex entry.
|
||||
type Appendix struct {
|
||||
Title string
|
||||
Path string
|
||||
}
|
||||
|
||||
// IHKRenderer is the central PDF generator for IHK Chemnitz project documentation.
|
||||
// All DIN 5008 formatting rules are applied through its methods.
|
||||
//
|
||||
// Usage: create with NewIHKRenderer, call Render* methods in document order,
|
||||
// then call Save. The two-pass rendering in main.go fills the TOC correctly.
|
||||
type IHKRenderer struct {
|
||||
pdf *fpdf.Fpdf
|
||||
config Config
|
||||
numType NumberingType
|
||||
tocItems []TOCItem
|
||||
tableItems []TableItem
|
||||
sources []string
|
||||
appendices []Appendix
|
||||
pageOffset int
|
||||
tableCount int
|
||||
tr func(string) string
|
||||
pdf *fpdf.Fpdf
|
||||
config Config
|
||||
numType NumberingType
|
||||
tocItems []TOCItem
|
||||
tableItems []TableItem
|
||||
figureItems []FigureItem
|
||||
sources []string
|
||||
appendices []Appendix
|
||||
// pageOffset is the PDF page number of the last Roman-numbered page.
|
||||
// Main body page 1 is rendered as PDF page (pageOffset + 1).
|
||||
pageOffset int
|
||||
tableCount int
|
||||
figureCount int
|
||||
// tr translates UTF-8 strings to the Latin-1 encoding used by fpdf.
|
||||
tr func(string) string
|
||||
}
|
||||
|
||||
// NewIHKRenderer constructs a renderer pre-configured with DIN 5008 margins
|
||||
// and an auto-footer that renders the correct page number style per section.
|
||||
func NewIHKRenderer(config Config) *IHKRenderer {
|
||||
pdf := fpdf.New("P", "mm", "A4", "")
|
||||
|
||||
// Margins in mm: Top 20, Bottom 25, Left 30, Right 40
|
||||
pdf.SetMargins(30, 20, 40)
|
||||
pdf.SetAutoPageBreak(true, 25)
|
||||
pdf.SetMargins(dinMarginLeft, dinMarginTop, dinMarginRight)
|
||||
pdf.SetAutoPageBreak(true, dinMarginBottom)
|
||||
|
||||
r := &IHKRenderer{
|
||||
pdf: pdf,
|
||||
config: config,
|
||||
numType: NumNone,
|
||||
tocItems: make([]TOCItem, 0),
|
||||
tableItems: make([]TableItem, 0),
|
||||
sources: make([]string, 0),
|
||||
appendices: make([]Appendix, 0),
|
||||
tr: pdf.UnicodeTranslatorFromDescriptor(""),
|
||||
pdf: pdf,
|
||||
config: config,
|
||||
numType: NumNone,
|
||||
tocItems: make([]TOCItem, 0),
|
||||
tableItems: make([]TableItem, 0),
|
||||
figureItems: make([]FigureItem, 0),
|
||||
sources: make([]string, 0),
|
||||
appendices: make([]Appendix, 0),
|
||||
tr: pdf.UnicodeTranslatorFromDescriptor(""),
|
||||
}
|
||||
|
||||
// IHK: page number centered at the bottom of every numbered page.
|
||||
pdf.SetFooterFunc(func() {
|
||||
if r.numType == NumNone {
|
||||
return
|
||||
}
|
||||
|
||||
pdf.SetY(-15)
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
|
||||
pdf.SetFont("Helvetica", "", dinFontCaption)
|
||||
var pageStr string
|
||||
if r.numType == NumRoman {
|
||||
switch r.numType {
|
||||
case NumRoman:
|
||||
pageStr = toRoman(pdf.PageNo())
|
||||
} else {
|
||||
// Arabic numbering starts at 1 for the main body
|
||||
displayPage := pdf.PageNo() - r.pageOffset
|
||||
if displayPage <= 0 {
|
||||
case NumArabic:
|
||||
dp := pdf.PageNo() - r.pageOffset
|
||||
if dp <= 0 {
|
||||
return
|
||||
}
|
||||
pageStr = strconv.Itoa(displayPage)
|
||||
pageStr = strconv.Itoa(dp)
|
||||
}
|
||||
|
||||
pdf.CellFormat(0, 10, pageStr, "", 0, "C", false, 0, "")
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderTOC() {
|
||||
r.numType = NumRoman
|
||||
r.pdf.AddPage()
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Inhaltsverzeichnis"), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
// TOC Header as a table
|
||||
totalWidth, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
usableWidth := totalWidth - lm - rm
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(usableWidth-20, 10, r.tr("Inhalt"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(20, 10, r.tr("Seite"), "", 1, "R", false, 0, "")
|
||||
r.pdf.Ln(2)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
_, lineHt := r.pdf.GetFontSize()
|
||||
lineHt *= 1.5
|
||||
|
||||
for _, item := range r.tocItems {
|
||||
indent := float64((item.Level - 1) * 10)
|
||||
r.pdf.SetX(lm + indent)
|
||||
|
||||
title := r.tr(item.Title)
|
||||
pageStr := item.PageStr
|
||||
|
||||
titleWidth := r.pdf.GetStringWidth(title)
|
||||
pageWidth := r.pdf.GetStringWidth(pageStr)
|
||||
|
||||
// Available width for dots
|
||||
availableWidth := usableWidth - indent - titleWidth - pageWidth - 4
|
||||
|
||||
r.pdf.CellFormat(titleWidth+2, lineHt, title, "", 0, "L", false, 0, "")
|
||||
|
||||
if availableWidth > 0 {
|
||||
dots := ""
|
||||
dotWidth := r.pdf.GetStringWidth(".")
|
||||
for i := 0; float64(i)*dotWidth < availableWidth; i++ {
|
||||
dots += "."
|
||||
}
|
||||
r.pdf.CellFormat(availableWidth, lineHt, dots, "", 0, "L", false, 0, "")
|
||||
}
|
||||
|
||||
r.pdf.CellFormat(pageWidth+2, lineHt, pageStr, "", 1, "R", false, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RecordHeader(level int, title string) {
|
||||
if r.numType != NumArabic {
|
||||
return // Exclude front matter from TOC per IHK example
|
||||
}
|
||||
|
||||
displayPage := r.pdf.PageNo() - r.pageOffset
|
||||
pageStr := strconv.Itoa(displayPage)
|
||||
if displayPage <= 0 {
|
||||
pageStr = "1" // Fallback
|
||||
}
|
||||
|
||||
r.tocItems = append(r.tocItems, TOCItem{
|
||||
Level: level,
|
||||
Title: title,
|
||||
PageStr: pageStr,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RecordTable(title string) {
|
||||
displayPage := r.pdf.PageNo() - r.pageOffset
|
||||
pageStr := strconv.Itoa(displayPage)
|
||||
if displayPage <= 0 {
|
||||
pageStr = "1"
|
||||
}
|
||||
r.tableItems = append(r.tableItems, TableItem{
|
||||
Title: title,
|
||||
PageStr: pageStr,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderListOfTables() {
|
||||
if len(r.tableItems) == 0 {
|
||||
return
|
||||
}
|
||||
r.pdf.AddPage()
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Tabellenverzeichnis"), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
totalWidth, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
usableWidth := totalWidth - lm - rm
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(usableWidth-20, 10, r.tr("Titel"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(20, 10, r.tr("Seite"), "", 1, "R", false, 0, "")
|
||||
r.pdf.Ln(2)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
_, lineHt := r.pdf.GetFontSize()
|
||||
lineHt *= 1.5
|
||||
|
||||
for _, item := range r.tableItems {
|
||||
title := r.tr(item.Title)
|
||||
pageStr := item.PageStr
|
||||
titleWidth := r.pdf.GetStringWidth(title)
|
||||
pageWidth := r.pdf.GetStringWidth(pageStr)
|
||||
availableWidth := usableWidth - titleWidth - pageWidth - 4
|
||||
|
||||
r.pdf.CellFormat(titleWidth+2, lineHt, title, "", 0, "L", false, 0, "")
|
||||
if availableWidth > 0 {
|
||||
dots := ""
|
||||
dotWidth := r.pdf.GetStringWidth(".")
|
||||
for i := 0; float64(i)*dotWidth < availableWidth; i++ {
|
||||
dots += "."
|
||||
}
|
||||
r.pdf.CellFormat(availableWidth, lineHt, dots, "", 0, "L", false, 0, "")
|
||||
}
|
||||
r.pdf.CellFormat(pageWidth+2, lineHt, pageStr, "", 1, "R", false, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderTitlePage() {
|
||||
r.numType = NumNone
|
||||
|
||||
// Temporary symmetrical margins for title page to ensure visual centering
|
||||
oldLM, oldTM, oldRM, _ := r.pdf.GetMargins()
|
||||
r.pdf.SetMargins(30, 20, 30)
|
||||
|
||||
r.pdf.AddPage()
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 16)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Abschlussprüfung zum ..."), "", 1, "C", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Profession), "", 1, "C", false, 0, "")
|
||||
|
||||
r.pdf.Ln(30)
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
r.pdf.CellFormat(0, 10, r.tr("Projektarbeit von"), "", 1, "C", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Name), "", 1, "C", false, 0, "")
|
||||
|
||||
r.pdf.Ln(30)
|
||||
r.pdf.SetFont("Helvetica", "B", 16)
|
||||
r.pdf.MultiCell(0, 10, r.tr(r.config.Project.Title), "", "C", false)
|
||||
|
||||
r.pdf.SetY(-80)
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
r.pdf.CellFormat(60, 10, r.tr("Prüfungsperiode:"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Project.Period), "", 1, "L", false, 0, "")
|
||||
|
||||
r.pdf.CellFormat(60, 10, r.tr("Ausbildungsbetrieb:"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Company), "", 1, "L", false, 0, "")
|
||||
|
||||
r.pdf.CellFormat(60, 10, r.tr("Projektbetreuer:"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Supervisor), "", 1, "L", false, 0, "")
|
||||
|
||||
// Restore margins for the rest of the document
|
||||
r.pdf.SetMargins(oldLM, oldTM, oldRM)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderDeclarationPage() {
|
||||
r.numType = NumNone
|
||||
r.pdf.AddPage()
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Erklärung"), "", 1, "C", false, 0, "")
|
||||
r.pdf.Ln(10)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
text := fmt.Sprintf("Ich versichere durch meine Unterschrift, dass ich diese Projektarbeit mit dem Thema „%s“ selbstständig, ohne fremde Hilfe angefertigt, alle Stellen, die ich wörtlich oder annähernd wörtlich aus Veröffentlichungen entnommen, als solche kenntlich gemacht und mich auch keiner anderen als der angegebenen Literatur oder sonstiger Hilfsmittel bedient habe. Die Projektarbeit hat in dieser oder ähnlicher Form weder der Industrie- und Handelskammer Chemnitz noch einer anderen Prüfungsinstitution vorgelegen.", r.config.Project.Title)
|
||||
|
||||
r.pdf.MultiCell(0, 7.5, r.tr(text), "", "J", false)
|
||||
|
||||
r.pdf.Ln(20)
|
||||
r.pdf.CellFormat(0, 10, r.tr("Ort, Datum, Unterschrift (mit Vor- und Nachnamen)"), "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderHeader(level int, title string) {
|
||||
if level == 1 {
|
||||
r.pdf.AddPage() // New page for major sections? Maybe optional.
|
||||
}
|
||||
r.RecordHeader(level, title)
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.Ln(5)
|
||||
r.pdf.CellFormat(0, 10, r.tr(title), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(2)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderParagraph(text string) {
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
// Line height for 12pt is 12pt * 1.5 = 18pt.
|
||||
// 18pt is approx 6.35mm.
|
||||
r.pdf.MultiCell(0, 7.5, r.tr(text), "", "J", false)
|
||||
r.pdf.Ln(4)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderListItem(text string, bullet bool, index int) {
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
prefix := "• "
|
||||
if !bullet {
|
||||
prefix = strconv.Itoa(index) + ". "
|
||||
}
|
||||
|
||||
currentX := r.pdf.GetX()
|
||||
r.pdf.SetX(currentX + 10)
|
||||
r.pdf.MultiCell(0, 7.5, r.tr(prefix+text), "", "J", false)
|
||||
r.pdf.SetX(currentX)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderTable(data [][]string) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.tableCount++
|
||||
tableTitle := fmt.Sprintf("Tab. %d", r.tableCount)
|
||||
r.RecordTable(tableTitle)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 10)
|
||||
header := data[0]
|
||||
|
||||
// Calculate column widths
|
||||
numCols := len(header)
|
||||
totalWidth, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
usableWidth := totalWidth - lm - rm
|
||||
colWidth := usableWidth / float64(numCols)
|
||||
|
||||
// Function to render header with optional "(Fortsetzung)"
|
||||
renderHeader := func(continued bool) {
|
||||
r.pdf.SetFont("Helvetica", "B", 10)
|
||||
title := tableTitle
|
||||
if continued {
|
||||
title += " (Fortsetzung)"
|
||||
}
|
||||
r.pdf.CellFormat(0, 10, r.tr(title), "", 1, "L", false, 0, "")
|
||||
|
||||
r.pdf.SetFillColor(230, 230, 230)
|
||||
for _, col := range header {
|
||||
r.pdf.CellFormat(colWidth, 10, r.tr(col), "1", 0, "C", true, 0, "")
|
||||
}
|
||||
r.pdf.Ln(-1)
|
||||
}
|
||||
|
||||
renderHeader(false)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 10)
|
||||
r.pdf.SetFillColor(255, 255, 255)
|
||||
|
||||
for i := 1; i < len(data); i++ {
|
||||
row := data[i]
|
||||
|
||||
// Check for page break
|
||||
_, pageH := r.pdf.GetPageSize()
|
||||
_, _, _, bm := r.pdf.GetMargins()
|
||||
if r.pdf.GetY()+10 > pageH-bm {
|
||||
r.pdf.AddPage()
|
||||
renderHeader(true)
|
||||
r.pdf.SetFont("Helvetica", "", 10)
|
||||
}
|
||||
|
||||
for _, col := range row {
|
||||
r.pdf.CellFormat(colWidth, 10, r.tr(col), "1", 0, "L", false, 0, "")
|
||||
}
|
||||
r.pdf.Ln(-1)
|
||||
}
|
||||
r.pdf.Ln(5)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderImage(path string, caption string) {
|
||||
r.pdf.Ln(5)
|
||||
|
||||
info := r.pdf.GetImageInfo(path)
|
||||
if info == nil {
|
||||
// Try to register it first
|
||||
r.pdf.RegisterImageOptions(path, fpdf.ImageOptions{ReadDpi: true})
|
||||
info = r.pdf.GetImageInfo(path)
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
r.pdf.CellFormat(0, 10, r.tr("[Fehler beim Laden des Bildes: "+path+"]"), "1", 1, "C", false, 0, "")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate dimensions in mm
|
||||
// 1 point = 0.352778 mm
|
||||
imgW := info.Width() * 0.352778
|
||||
imgH := info.Height() * 0.352778
|
||||
|
||||
maxWidth := 140.0
|
||||
displayW := imgW
|
||||
displayH := imgH
|
||||
|
||||
if displayW > maxWidth {
|
||||
ratio := maxWidth / displayW
|
||||
displayW = maxWidth
|
||||
displayH = displayH * ratio
|
||||
}
|
||||
|
||||
// Check if we need a new page
|
||||
_, pageH := r.pdf.GetPageSize()
|
||||
_, _, _, bottomMargin := r.pdf.GetMargins()
|
||||
currentY := r.pdf.GetY()
|
||||
|
||||
if currentY+displayH+15 > pageH-bottomMargin {
|
||||
r.pdf.AddPage()
|
||||
currentY = r.pdf.GetY()
|
||||
}
|
||||
|
||||
// Center horizontally
|
||||
posX := 30.0 + (maxWidth-displayW)/2
|
||||
|
||||
r.pdf.ImageOptions(path, posX, currentY, displayW, displayH, false, fpdf.ImageOptions{ReadDpi: true}, 0, "")
|
||||
|
||||
// Move Y after the image
|
||||
r.pdf.SetY(currentY + displayH + 2)
|
||||
|
||||
if caption != "" {
|
||||
r.pdf.SetFont("Helvetica", "I", 10)
|
||||
r.pdf.CellFormat(0, 10, r.tr(caption), "", 1, "C", false, 0, "")
|
||||
}
|
||||
r.pdf.Ln(5)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) StartMainBody() {
|
||||
r.numType = NumArabic
|
||||
// The current page is the last Roman page
|
||||
// So the next page will be display page 1
|
||||
r.pageOffset = r.pdf.PageNo()
|
||||
}
|
||||
|
||||
// StartFrontMatter activates Roman-numeral page numbering.
|
||||
// Must be called before adding the first front-matter page (i.e., before RenderTOC).
|
||||
// The title page (page 1) is unnumbered; the TOC appears as page II.
|
||||
func (r *IHKRenderer) StartFrontMatter() {
|
||||
r.numType = NumRoman
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderBibliography() {
|
||||
if len(r.sources) == 0 {
|
||||
return
|
||||
}
|
||||
// RenderHeader(1, ...) already adds a page
|
||||
r.RenderHeader(1, "Literaturverzeichnis")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
|
||||
// IHK Rule 2.8.1: Sort by author name
|
||||
sort.Strings(r.sources)
|
||||
|
||||
for _, source := range r.sources {
|
||||
r.pdf.MultiCell(0, 7.5, r.tr("- "+source), "", "J", false)
|
||||
r.pdf.Ln(2)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderAppendices() {
|
||||
if len(r.appendices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// RenderHeader(1, ...) already adds a page
|
||||
r.RenderHeader(1, "Anhang")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(0, 10, r.tr("Anlagenverzeichnis"), "", 1, "L", false, 0, "")
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
for i, app := range r.appendices {
|
||||
r.pdf.CellFormat(0, 10, r.tr(fmt.Sprintf("Anlage %d: %s", i+1, app.Title)), "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
for i, app := range r.appendices {
|
||||
r.pdf.AddPage()
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(0, 10, r.tr(fmt.Sprintf("Anlage %d: %s", i+1, app.Title)), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
info := r.pdf.GetImageInfo(app.Path)
|
||||
if info == nil {
|
||||
r.pdf.RegisterImageOptions(app.Path, fpdf.ImageOptions{ReadDpi: true})
|
||||
info = r.pdf.GetImageInfo(app.Path)
|
||||
}
|
||||
|
||||
if info != nil {
|
||||
imgW := info.Width() * 0.352778
|
||||
imgH := info.Height() * 0.352778
|
||||
|
||||
maxWidth := 140.0
|
||||
// Scale to full width
|
||||
displayW := maxWidth
|
||||
ratio := displayW / imgW
|
||||
displayH := imgH * ratio
|
||||
|
||||
// Check if it fits on page height, if not, scale down
|
||||
_, pageH := r.pdf.GetPageSize()
|
||||
_, _, _, bottomMargin := r.pdf.GetMargins()
|
||||
availableH := pageH - r.pdf.GetY() - bottomMargin - 10
|
||||
|
||||
if displayH > availableH {
|
||||
ratio := availableH / displayH
|
||||
displayH = availableH
|
||||
displayW = displayW * ratio
|
||||
}
|
||||
|
||||
posX := 30.0 + (140.0-displayW)/2
|
||||
r.pdf.ImageOptions(app.Path, posX, r.pdf.GetY(), displayW, displayH, false, fpdf.ImageOptions{ReadDpi: true}, 0, "")
|
||||
} else {
|
||||
r.pdf.CellFormat(0, 10, r.tr("[Bild konnte nicht geladen werden]"), "1", 1, "C", false, 0, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) AddAppendix(titlePath string) {
|
||||
parts := strings.Split(titlePath, "|")
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(parts[0])
|
||||
path := strings.TrimSpace(parts[1])
|
||||
|
||||
r.appendices = append(r.appendices, Appendix{Title: title, Path: path})
|
||||
// StartMainBody switches to Arabic page numbering and captures the current PDF
|
||||
// page as the offset so that the first main-body page displays as "1".
|
||||
func (r *IHKRenderer) StartMainBody() {
|
||||
r.numType = NumArabic
|
||||
r.pageOffset = r.pdf.PageNo()
|
||||
}
|
||||
|
||||
// AddSource registers a bibliography entry. Whitespace is trimmed;
|
||||
// duplicate entries are silently ignored.
|
||||
func (r *IHKRenderer) AddSource(source string) {
|
||||
// Normalize and store
|
||||
source = strings.TrimSpace(source)
|
||||
if source == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent duplicates
|
||||
for _, s := range r.sources {
|
||||
if s == source {
|
||||
return
|
||||
@@ -527,23 +139,33 @@ func (r *IHKRenderer) AddSource(source string) {
|
||||
r.sources = append(r.sources, source)
|
||||
}
|
||||
|
||||
// AddAppendix registers an annex entry in "Title | /path/to/image" format.
|
||||
func (r *IHKRenderer) AddAppendix(titlePath string) {
|
||||
parts := strings.SplitN(titlePath, "|", 2)
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
r.appendices = append(r.appendices, Appendix{
|
||||
Title: strings.TrimSpace(parts[0]),
|
||||
Path: strings.TrimSpace(parts[1]),
|
||||
})
|
||||
}
|
||||
|
||||
// Save writes the completed PDF to the given file path.
|
||||
func (r *IHKRenderer) Save(filename string) error {
|
||||
return r.pdf.OutputFileAndClose(filename)
|
||||
}
|
||||
|
||||
func toRoman(n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
values := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
|
||||
symbols := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
|
||||
|
||||
res := ""
|
||||
for i := 0; i < len(values); i++ {
|
||||
for n >= values[i] {
|
||||
n -= values[i]
|
||||
res += symbols[i]
|
||||
}
|
||||
}
|
||||
return res
|
||||
// usableWidth returns the printable line width on the current page in mm.
|
||||
func (r *IHKRenderer) usableWidth() float64 {
|
||||
w, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
return w - lm - rm
|
||||
}
|
||||
|
||||
// isAtPageTop returns true when the Y cursor sits at or within 1 mm of the
|
||||
// top margin, meaning no body content has been placed on this page yet.
|
||||
func (r *IHKRenderer) isAtPageTop() bool {
|
||||
_, tm, _, _ := r.pdf.GetMargins()
|
||||
return r.pdf.GetY() <= tm+1.0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user