250 lines
8.6 KiB
Go
250 lines
8.6 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"sort"
|
||
|
||
"github.com/go-pdf/fpdf"
|
||
)
|
||
|
||
// RenderTitlePage renders the cover page without a page number.
|
||
// Layout follows the IHK Chemnitz template (section 2.2 of the guidelines).
|
||
func (r *IHKRenderer) RenderTitlePage() {
|
||
r.numType = NumNone
|
||
r.pdf.AddPage()
|
||
|
||
// Use symmetrical margins on the title page for visual centering.
|
||
r.pdf.SetLeftMargin(dinMarginLeft)
|
||
r.pdf.SetRightMargin(dinMarginLeft)
|
||
defer func() {
|
||
r.pdf.SetLeftMargin(dinMarginLeft)
|
||
r.pdf.SetRightMargin(dinMarginRight)
|
||
}()
|
||
|
||
// Exam type and profession
|
||
r.pdf.SetFont("Helvetica", "B", 16)
|
||
r.pdf.CellFormat(0, 12, r.tr("Abschlussprüfung zum"), "", 1, "C", false, 0, "")
|
||
r.pdf.CellFormat(0, 12, r.tr(r.config.Student.Profession), "", 1, "C", false, 0, "")
|
||
|
||
r.pdf.Ln(20)
|
||
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
r.pdf.CellFormat(0, dinLineHtBody, r.tr("Projektarbeit von"), "", 1, "C", false, 0, "")
|
||
r.pdf.Ln(4)
|
||
|
||
// Candidate name — bold, 14 pt
|
||
r.pdf.SetFont("Helvetica", "B", dinFontHeading)
|
||
r.pdf.CellFormat(0, dinLineHtHeading, r.tr(r.config.Student.Name), "", 1, "C", false, 0, "")
|
||
|
||
r.pdf.Ln(20)
|
||
|
||
// Project title — bold, 16 pt, centred, word-wrapped
|
||
r.pdf.SetFont("Helvetica", "B", 16)
|
||
r.pdf.MultiCell(0, 10, r.tr(r.config.Project.Title), "", "C", false)
|
||
|
||
if r.config.Project.Subtitle != "" {
|
||
r.pdf.Ln(4)
|
||
r.pdf.SetFont("Helvetica", "I", dinFontBody)
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr(r.config.Project.Subtitle), "", "C", false)
|
||
}
|
||
|
||
// Bottom block: exam period, training company, supervisor
|
||
r.pdf.SetY(-80)
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
labelW := 60.0
|
||
|
||
r.pdf.CellFormat(labelW, dinLineHtBody, r.tr("Prüfungsperiode:"), "", 0, "L", false, 0, "")
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr(r.config.Project.Period), "", "L", false)
|
||
|
||
r.pdf.CellFormat(labelW, dinLineHtBody, r.tr("Ausbildungsbetrieb:"), "", 0, "L", false, 0, "")
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr(r.config.Student.Company), "", "L", false)
|
||
|
||
r.pdf.CellFormat(labelW, dinLineHtBody, r.tr("Projektbetreuer:"), "", 0, "L", false, 0, "")
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr(r.config.Student.Supervisor), "", "L", false)
|
||
}
|
||
|
||
// RenderDeclarationPage renders the declaration of authenticity without a page
|
||
// number, as required by IHK Chemnitz (section 2.12 of the guidelines).
|
||
// This page must be physically inserted in every submitted copy.
|
||
func (r *IHKRenderer) RenderDeclarationPage() {
|
||
r.numType = NumNone
|
||
r.pdf.AddPage()
|
||
|
||
r.pdf.SetFont("Helvetica", "B", dinFontHeading)
|
||
r.pdf.CellFormat(0, dinLineHtHeading+4, r.tr("Erklärung"), "", 1, "C", false, 0, "")
|
||
r.pdf.Ln(dinSpaceAfterHeading)
|
||
|
||
// Legally required declaration text — must remain in German.
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
declaration := 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, dinLineHtBody, r.tr(declaration), "", "J", false)
|
||
|
||
r.pdf.Ln(dinLineHtBody)
|
||
additional := "Mir ist bekannt, dass gemäß der Prüfungsordnung für die Durchführung" +
|
||
" von Abschlussprüfungen der Industrie- und Handelskammer Chemnitz" +
|
||
" Täuschungshandlungen zum Ausschluss von der Prüfung führen können und" +
|
||
" die Prüfung als nicht bestanden erklärt werden kann."
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr(additional), "", "J", false)
|
||
|
||
r.pdf.Ln(20)
|
||
r.pdf.CellFormat(0, dinLineHtBody, r.tr("Ort, Datum, Unterschrift (mit Vor- und Nachnamen)"), "", 1, "L", false, 0, "")
|
||
r.pdf.Ln(4)
|
||
|
||
// Signature line
|
||
r.pdf.SetDrawColor(0, 0, 0)
|
||
x, y := r.pdf.GetXY()
|
||
r.pdf.Line(x, y, x+80, y)
|
||
}
|
||
|
||
// RenderBibliography renders the bibliography sorted alphabetically per IHK
|
||
// rule 2.8.1. Skipped when no @Quelle directives were found in the document.
|
||
func (r *IHKRenderer) RenderBibliography() {
|
||
if len(r.sources) == 0 {
|
||
return
|
||
}
|
||
r.RenderHeader(1, "Literaturverzeichnis")
|
||
|
||
sorted := make([]string, len(r.sources))
|
||
copy(sorted, r.sources)
|
||
sort.Strings(sorted)
|
||
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
for _, source := range sorted {
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr("– "+source), "", "J", false)
|
||
r.pdf.Ln(2)
|
||
}
|
||
}
|
||
|
||
// RenderAppendices renders the appendix section followed by individual annex
|
||
// pages. The opening page contains the index of all annexes. Each annex image
|
||
// is scaled to fill the available page area.
|
||
func (r *IHKRenderer) RenderAppendices() {
|
||
if len(r.appendices) == 0 {
|
||
return
|
||
}
|
||
r.RenderHeader(1, "Anhang")
|
||
|
||
// Annex index
|
||
r.pdf.SetFont("Helvetica", "B", dinFontBody)
|
||
r.pdf.CellFormat(0, dinLineHtBody, r.tr("Anlagenverzeichnis"), "", 1, "L", false, 0, "")
|
||
r.pdf.Ln(dinSpaceAfterHeading)
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
for i, app := range r.appendices {
|
||
r.pdf.CellFormat(0, dinLineHtBody,
|
||
r.tr(fmt.Sprintf("Anlage %d: %s", i+1, app.Title)), "", 1, "L", false, 0, "")
|
||
}
|
||
|
||
// Individual annex pages
|
||
for i, app := range r.appendices {
|
||
r.pdf.AddPage()
|
||
r.pdf.SetFont("Helvetica", "B", dinFontBody)
|
||
r.pdf.CellFormat(0, dinLineHtBody,
|
||
r.tr(fmt.Sprintf("Anlage %d: %s", i+1, app.Title)), "", 1, "L", false, 0, "")
|
||
r.pdf.Ln(dinSpaceAfterHeading)
|
||
r.renderAppendixImage(app.Path)
|
||
}
|
||
}
|
||
|
||
// renderAppendixImage scales and embeds an image to fill the remaining page area.
|
||
func (r *IHKRenderer) renderAppendixImage(path string) {
|
||
info := r.ensureImageRegistered(path)
|
||
if info == nil {
|
||
r.pdf.CellFormat(0, dinLineHtBody, r.tr("[Image could not be loaded: "+path+"]"),
|
||
"1", 1, "C", false, 0, "")
|
||
return
|
||
}
|
||
|
||
_, pageH := r.pdf.GetPageSize()
|
||
lm, _, _, bm := r.pdf.GetMargins()
|
||
uw := r.usableWidth()
|
||
availH := pageH - r.pdf.GetY() - bm - 10
|
||
|
||
imgW := info.Width() * ptToMM
|
||
imgH := info.Height() * ptToMM
|
||
|
||
// Scale to fill available width, then clamp to available height.
|
||
displayW := uw
|
||
displayH := imgH * (displayW / imgW)
|
||
if displayH > availH {
|
||
scale := availH / displayH
|
||
displayH = availH
|
||
displayW = displayW * scale
|
||
}
|
||
|
||
posX := lm + (uw-displayW)/2
|
||
r.pdf.ImageOptions(path, posX, r.pdf.GetY(), displayW, displayH, false,
|
||
fpdf.ImageOptions{ReadDpi: true}, 0, "")
|
||
}
|
||
|
||
// RenderAbbreviations renders the list of abbreviations from the YAML config.
|
||
// It is placed after the TOC and uses Roman page numbering.
|
||
func (r *IHKRenderer) RenderAbbreviations() {
|
||
if len(r.config.Abbreviations) == 0 {
|
||
return
|
||
}
|
||
r.pdf.AddPage()
|
||
r.RecordHeader(1, "Abkürzungsverzeichnis")
|
||
|
||
r.pdf.SetFont("Helvetica", "B", dinFontHeading)
|
||
r.pdf.CellFormat(0, dinLineHtHeading+4, r.tr("Abkürzungsverzeichnis"), "", 1, "L", false, 0, "")
|
||
r.pdf.Ln(dinSpaceAfterHeading)
|
||
|
||
uw := r.usableWidth()
|
||
abbrColW := 40.0
|
||
meaningColW := uw - abbrColW
|
||
|
||
// Header row with grey fill
|
||
r.pdf.SetFillColor(230, 230, 230)
|
||
r.pdf.SetFont("Helvetica", "B", dinFontBody)
|
||
r.pdf.CellFormat(abbrColW, dinLineHtBody, r.tr("Abkürzung"), "1", 0, "L", true, 0, "")
|
||
r.pdf.CellFormat(meaningColW, dinLineHtBody, r.tr("Bedeutung"), "1", 1, "L", true, 0, "")
|
||
|
||
r.pdf.SetFillColor(255, 255, 255)
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
for _, a := range r.config.Abbreviations {
|
||
r.pdf.CellFormat(abbrColW, dinLineHtBody, r.tr(a.Abbr), "1", 0, "L", false, 0, "")
|
||
r.pdf.CellFormat(meaningColW, dinLineHtBody, r.tr(a.Meaning), "1", 1, "L", false, 0, "")
|
||
}
|
||
}
|
||
|
||
// RenderGlossary renders the glossary from the YAML config.
|
||
// It is placed after the appendices and uses Arabic page numbering.
|
||
func (r *IHKRenderer) RenderGlossary() {
|
||
if len(r.config.Glossary) == 0 {
|
||
return
|
||
}
|
||
r.RenderHeader(1, "Glossar")
|
||
|
||
for _, entry := range r.config.Glossary {
|
||
r.pdf.SetFont("Helvetica", "B", dinFontBody)
|
||
r.pdf.CellFormat(0, dinLineHtBody, r.tr(entry.Term), "", 1, "L", false, 0, "")
|
||
r.pdf.SetFont("Helvetica", "", dinFontBody)
|
||
r.pdf.MultiCell(0, dinLineHtBody, r.tr(entry.Definition), "", "J", false)
|
||
r.pdf.Ln(4)
|
||
}
|
||
}
|
||
|
||
// ensureImageRegistered registers an image with fpdf if not already known
|
||
// and returns its metadata. Returns nil if the image cannot be loaded.
|
||
func (r *IHKRenderer) ensureImageRegistered(path string) *fpdf.ImageInfoType {
|
||
info := r.pdf.GetImageInfo(path)
|
||
if info == nil {
|
||
r.pdf.RegisterImageOptions(path, fpdf.ImageOptions{ReadDpi: true})
|
||
info = r.pdf.GetImageInfo(path)
|
||
}
|
||
return info
|
||
}
|
||
|
||
// ptToMM converts PDF points to millimetres (1 pt = 25.4 / 72 mm).
|
||
const ptToMM = 25.4 / 72.0
|