Add fonts, autotable, autotable examples

This commit is contained in:
2026-07-13 13:01:26 -04:00
parent ea3d2a6d03
commit cf8342f8d4
71 changed files with 16084 additions and 1443 deletions

View File

@@ -0,0 +1,91 @@
package webui
import (
"regexp"
"strconv"
"strings"
"testing"
)
// pdfHorizontalRules extracts every horizontal line the PDF draws, as (y, width).
// The content stream draws a line as: "<w> w <x1> <y> m <x2> <y> l S".
func pdfHorizontalRules(pdf []byte) map[string]int {
re := regexp.MustCompile(`([\d.]+) w\s*\n?[^\n]*?([\d.]+) ([\d.]+) m\s*\n?[^\n]*?([\d.]+) ([\d.]+) l`)
out := map[string]int{}
for _, m := range re.FindAllStringSubmatch(string(pdf), -1) {
y1, y2 := m[3], m[5]
if y1 != y2 {
continue // not horizontal
}
out[y1]++
}
return out
}
// The bug: the row loop drew a rule under EVERY row including the last, and then the
// summary block drew its divider at exactly the same y. Two rules at one y read as a
// double border between the table and its summaries.
func TestPDFDrawsOneRuleBetweenTableAndSummaries(t *testing.T) {
s := newCalcTable()
s.AddSummaryRow(UserSummaryRow{ID: "t", Label: "Total", Fn: CALC_FN_SUM,
Operands: []string{"Revenue"}, DataType: CALC_TYPE_NUMBER, Precision: CalcPrecision(0)})
s.Render()
rules := pdfHorizontalRules(s.ExportPDFBytes(AutoTablePDFHeader{Title: "R"}))
if len(rules) == 0 {
t.Fatal("no horizontal rules found — the extractor is wrong, not the PDF")
}
for y, n := range rules {
if n > 1 {
t.Errorf("%d rules stacked at y=%s — that is the double border", n, y)
}
}
}
// Without summaries the table still gets its closing rule; removing the last row's
// rule must not leave the table open at the bottom.
func TestPDFClosesTheTableWithoutSummaries(t *testing.T) {
s := newCalcTable()
s.Render()
pdf := s.ExportPDFBytes(AutoTablePDFHeader{Title: "R"})
rules := pdfHorizontalRules(pdf)
if len(rules) < 2 {
t.Errorf("expected a header rule and a closing rule, got %d distinct rules", len(rules))
}
for y, n := range rules {
if n > 1 {
t.Errorf("%d rules stacked at y=%s", n, y)
}
}
// And the lowest rule sits below the last row of text, i.e. the table is closed.
lowest := 1e9
for y := range rules {
if f, err := strconv.ParseFloat(y, 64); err == nil && f < lowest {
lowest = f
}
}
if lowest > 700 || !strings.Contains(string(pdf), "%%EOF") {
t.Errorf("closing rule at y=%v looks wrong", lowest)
}
}
// The extractor must actually see stacked rules, or the two tests above are vacuous.
func TestPDFRuleExtractorCatchesADoubledRule(t *testing.T) {
pdf := NewPDF(PDFOptions{})
pdf.Line(50, 300, 500, 300, 0.5, PDFColor{})
pdf.Line(50, 300, 500, 300, 1, PDFColor{}) // deliberately stacked
pdf.Line(50, 200, 500, 200, 0.5, PDFColor{})
rules := pdfHorizontalRules(pdf.Bytes())
doubled := 0
for _, n := range rules {
if n > 1 {
doubled++
}
}
if doubled != 1 {
t.Fatalf("extractor found %d stacked positions, want exactly 1 — it cannot see the bug it is meant to catch (%v)", doubled, rules)
}
}