package dossier import ( "bytes" "embed" "fmt" "io" "strings" "github.com/go-pdf/fpdf" ) //go:embed assets/cp1252.map var assetsFS embed.FS // Generate erzeugt das PDF-Dossier für data und schreibt es nach w. func Generate(w io.Writer, data Data) error { content, err := BuildContent(data) if err != nil { return err } return Render(w, content) } // Render zeichnet ein bereits aufbereitetes Content als PDF nach w. func Render(w io.Writer, c Content) error { tr, err := unicodeTranslator() if err != nil { return fmt.Errorf("dossier: unicode translator: %w", err) } pdf := fpdf.New("P", "mm", "A4", "") pdf.SetTitle(tr(c.Title), false) pdf.AddPage() heading := func(text string) { pdf.SetFont("Helvetica", "B", 12) pdf.CellFormat(0, 8, tr(text), "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 10) } line := func(format string, args ...any) { pdf.CellFormat(0, 6, tr(fmt.Sprintf(format, args...)), "", 1, "L", false, 0, "") } paragraph := func(text string) { pdf.MultiCell(0, 5, tr(text), "", "L", false) } pdf.SetFont("Helvetica", "B", 16) pdf.CellFormat(0, 10, tr(c.Title), "", 1, "L", false, 0, "") pdf.SetFont("Helvetica", "", 10) line("Erzeugt am %s", c.GeneratedAt.Format("02.01.2006 15:04 MST")) pdf.Ln(4) heading("Beitrag") line("Plattform: %s Typ: %s", c.Platform, c.PostType) line("Eingereicht am: %s", c.SubmittedAt.Format("02.01.2006 15:04")) paragraph("Caption: " + c.Caption) pdf.Ln(2) heading("Kennzeichnung") line("Vorhanden: %s Wortlaut: %q Vor Kürzung sichtbar: %s", yesNo(c.DisclosurePresent), c.DisclosureWording, yesNo(c.DisclosureBeforeCut)) pdf.Ln(2) heading("Findings") if len(c.Findings) == 0 { line("Keine Findings.") } for _, f := range c.Findings { pdf.SetFont("Helvetica", "B", 10) line("%s v%d — %s (%s)", f.RuleID, f.Version, f.Title, f.Severity) pdf.SetFont("Helvetica", "", 10) paragraph("Korrektur: " + f.Fix) if len(f.Sources) > 0 { paragraph("Fundstellen: " + strings.Join(f.Sources, "; ")) } pdf.Ln(1) } pdf.Ln(2) heading("Verantwortungsmatrix") if len(c.Participants) == 0 { line("Keine Beteiligten hinterlegt.") } for _, p := range c.Participants { line("%s: %s (vorgegeben: %s, freigegeben: %s)", p.Role, p.Name, yesNo(p.Vorgegeben), yesNo(p.Freigegeben)) } pdf.Ln(2) heading("Beweiskette") line("Asset-Hash (SHA-256): %s", c.AssetHashHex) line("Metadaten-Hash (SHA-256): %s", c.MetadataHashHex) line("RFC-3161-Zeitstempel: %s", c.TimestampedAt.Format("02.01.2006 15:04:05 MST")) pdf.Ln(4) pdf.SetFont("Helvetica", "I", 8) paragraph(c.Disclaimer) if err := pdf.Error(); err != nil { return fmt.Errorf("dossier: pdf aufbauen: %w", err) } if err := pdf.Output(w); err != nil { return fmt.Errorf("dossier: pdf schreiben: %w", err) } return nil } func yesNo(b bool) string { if b { return "ja" } return "nein" } // unicodeTranslator übersetzt UTF-8 (z. B. Umlaute) in die cp1252- // Kodierung der Helvetica-Kernschriftart. Die Map-Datei ist eingebettet, // damit Deklarix trotz PDF-Erzeugung ein einzelnes Binary bleibt. func unicodeTranslator() (func(string) string, error) { data, err := assetsFS.ReadFile("assets/cp1252.map") if err != nil { return nil, err } return fpdf.UnicodeTranslator(bytes.NewReader(data)) }