package reports // Zeichenbreiten der Schrift Helvetica in 1/1000 em. // // Die Werte stammen aus den Metriken der Standardschrift. Ohne sie liesse sich // kein Zeilenumbruch rechnen und keine Tabellenspalte bemessen: Eine geschaetzte // Durchschnittsbreite laesst „Wiederherstellung fehlgeschlagen" in die // Nachbarspalte laufen, waehrend „111111" viel Platz uebrig liesse. // // Der Index ist der Zeichenwert minus 32; abgedeckt ist 32 bis 126. var helveticaWidths = [95]int{ 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, // 32-41 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, // 42-51 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, // 52-61 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, // 62-71 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, // 72-81 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, // 82-91 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, // 92-101 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, // 102-111 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, // 112-121 500, 334, 260, 334, 584, // 122-126 } // boldWidthFactor naehert die Breite der fetten Schnitte an. // // Helvetica-Bold ist rund drei Prozent breiter als Helvetica. Eine zweite // vollstaendige Tabelle dafuer waere Aufwand ohne Gewinn: Fett gesetzt sind hier // nur Ueberschriften und Spaltenkoepfe, und die stehen fuer sich. const boldWidthFactor = 1.03 // defaultGlyphWidth ist die angenommene Breite eines Zeichens ausserhalb der Tabelle. // // Umlaute liegen in dieser Schrift bei rund 556; das ist deutlich naeher an der // Wahrheit als der Rueckgriff auf null, der eine Zeile beliebig lang erscheinen // liesse. const defaultGlyphWidth = 556 // textWidth misst einen Text in Punkten. func textWidth(text string, font pdfFont, fontSize float64) float64 { totalWidth := 0 for _, textRune := range text { glyphIndex := int(textRune) - 32 if glyphIndex >= 0 && glyphIndex < len(helveticaWidths) { totalWidth += helveticaWidths[glyphIndex] continue } totalWidth += defaultGlyphWidth } measuredWidth := float64(totalWidth) * fontSize / 1000 if font == fontBold { return measuredWidth * boldWidthFactor } return measuredWidth } // wrapText bricht einen Text auf die vorgegebene Breite um. // // Umgebrochen wird an Leerzeichen. Ein Wort, das allein schon zu breit ist, // bleibt ungebrochen stehen und ragt heraus — es zeichenweise zu zerlegen // zerrisse Pfade und Kennungen genau dort, wo sie gelesen werden muessen. func wrapText(text string, font pdfFont, fontSize float64, availableWidth float64) []string { words := splitWords(text) if len(words) == 0 { return []string{""} } wrappedLines := make([]string, 0, 4) currentLine := words[0] for _, word := range words[1:] { candidateLine := currentLine + " " + word if textWidth(candidateLine, font, fontSize) <= availableWidth { currentLine = candidateLine continue } wrappedLines = append(wrappedLines, currentLine) currentLine = word } return append(wrappedLines, currentLine) } // splitWords zerlegt einen Text an Leerzeichen. func splitWords(text string) []string { words := make([]string, 0, 16) currentWord := make([]rune, 0, 16) for _, textRune := range text { if textRune == ' ' || textRune == '\t' || textRune == '\n' { if len(currentWord) > 0 { words = append(words, string(currentWord)) currentWord = currentWord[:0] } continue } currentWord = append(currentWord, textRune) } if len(currentWord) > 0 { words = append(words, string(currentWord)) } return words } // truncateToWidth kuerzt einen Text auf die vorgegebene Breite. // // Gekuerzt wird mit Auslassungspunkten, damit erkennbar bleibt, dass etwas // fehlt. Ein stillschweigend abgeschnittener Pfad sieht aus wie ein anderer // Pfad. func truncateToWidth(text string, font pdfFont, fontSize float64, availableWidth float64) string { if textWidth(text, font, fontSize) <= availableWidth { return text } const ellipsis = "…" textRunes := []rune(text) ellipsisWidth := textWidth(ellipsis, font, fontSize) for runeCount := len(textRunes) - 1; runeCount > 0; runeCount-- { candidate := string(textRunes[:runeCount]) if textWidth(candidate, font, fontSize)+ellipsisWidth <= availableWidth { return candidate + ellipsis } } return ellipsis }