[🤖] Add Unit-Tests
Build Docker and Deploy / Run goext test-suite (push) Successful in 1m34s

This commit is contained in:
2026-04-27 10:46:08 +02:00
parent dad0e3240d
commit 02d6894ec6
116 changed files with 18795 additions and 1 deletions
+42
View File
@@ -0,0 +1,42 @@
package wpdf
import (
"testing"
)
func TestHexToColor(t *testing.T) {
cases := []struct {
in uint32
want PDFColor
}{
{0x000000, PDFColor{R: 0, G: 0, B: 0}},
{0xFFFFFF, PDFColor{R: 255, G: 255, B: 255}},
{0xFF0000, PDFColor{R: 255, G: 0, B: 0}},
{0x00FF00, PDFColor{R: 0, G: 255, B: 0}},
{0x0000FF, PDFColor{R: 0, G: 0, B: 255}},
{0x123456, PDFColor{R: 0x12, G: 0x34, B: 0x56}},
{0xC0C0C0, PDFColor{R: 192, G: 192, B: 192}},
}
for _, c := range cases {
got := hexToColor(c.in)
if got != c.want {
t.Errorf("hexToColor(%#x) = %+v, want %+v", c.in, got, c.want)
}
}
}
func TestHexToColorIgnoresHigherBits(t *testing.T) {
got := hexToColor(0xFF123456)
want := PDFColor{R: 0x12, G: 0x34, B: 0x56}
if got != want {
t.Errorf("hexToColor(0xFF123456) = %+v, want %+v", got, want)
}
}
func TestRgbToColor(t *testing.T) {
got := rgbToColor(10, 20, 30)
want := PDFColor{R: 10, G: 20, B: 30}
if got != want {
t.Errorf("rgbToColor(10,20,30) = %+v, want %+v", got, want)
}
}
+368
View File
@@ -0,0 +1,368 @@
package wpdf
import (
"bytes"
"testing"
)
func newBuilderWithPage(t *testing.T) *WPDFBuilder {
t.Helper()
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
return b
}
func TestNewPDFBuilderDefaults(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
if b == nil {
t.Fatal("expected non-nil builder")
}
if b.FPDF() == nil {
t.Fatal("expected non-nil underlying gofpdf builder")
}
if b.fontName != FontHelvetica {
t.Errorf("default fontName = %v, want %v", b.fontName, FontHelvetica)
}
if b.fontStyle != Normal {
t.Errorf("default fontStyle = %v, want %v", b.fontStyle, Normal)
}
if b.fontSize != 12 {
t.Errorf("default fontSize = %v, want 12", b.fontSize)
}
left, top, right, _ := b.GetMargins()
if left != 15 || top != 25 || right != 15 {
t.Errorf("default margins = (%v, %v, %v), want (15, 25, 15)", left, top, right)
}
}
func TestNewPDFBuilderUnicode(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, true)
if b.tr == nil {
t.Fatal("expected unicode translator to be set")
}
// Translator should not panic on simple ASCII input
out := b.tr("hello")
if out == "" {
t.Errorf("translator returned empty string for non-empty input")
}
}
func TestNewPDFBuilderNonUnicode(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
if got := b.tr("hello"); got != "hello" {
t.Errorf("non-unicode translator = %q, want %q", got, "hello")
}
}
func TestNewPDFBuilderLandscape(t *testing.T) {
b := NewPDFBuilder(Landscape, SizeA4, false)
w, h := b.GetPageSize()
if w <= h {
t.Errorf("landscape: expected width>height, got w=%v h=%v", w, h)
}
}
func TestNewPDFBuilderPortrait(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
w, h := b.GetPageSize()
if h <= w {
t.Errorf("portrait: expected height>width, got w=%v h=%v", w, h)
}
}
func TestSetMargins(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.SetMargins(PDFMargins{Left: 5, Top: 10, Right: 7})
if got := b.GetMarginLeft(); got != 5 {
t.Errorf("MarginLeft = %v, want 5", got)
}
if got := b.GetMarginTop(); got != 10 {
t.Errorf("MarginTop = %v, want 10", got)
}
if got := b.GetMarginRight(); got != 7 {
t.Errorf("MarginRight = %v, want 7", got)
}
// MarginBottom is not set explicitly via SetMargins, just verify accessor doesn't panic.
_ = b.GetMarginBottom()
}
func TestGetWorkAreaWidth(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.SetMargins(PDFMargins{Left: 10, Top: 20, Right: 5})
pw := b.GetPageWidth()
want := pw - 10 - 5
if got := b.GetWorkAreaWidth(); got != want {
t.Errorf("GetWorkAreaWidth = %v, want %v", got, want)
}
}
func TestGetPageWidthHeight(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
w, h := b.GetPageSize()
if got := b.GetPageWidth(); got != w {
t.Errorf("PageWidth = %v, want %v", got, w)
}
if got := b.GetPageHeight(); got != h {
t.Errorf("PageHeight = %v, want %v", got, h)
}
}
func TestSetGetTextColor(t *testing.T) {
b := newBuilderWithPage(t)
b.SetTextColor(10, 20, 30)
r, g, bl := b.GetTextColor()
if r != 10 || g != 20 || bl != 30 {
t.Errorf("GetTextColor = (%d,%d,%d), want (10,20,30)", r, g, bl)
}
}
func TestSetGetDrawColor(t *testing.T) {
b := newBuilderWithPage(t)
b.SetDrawColor(40, 50, 60)
r, g, bl := b.GetDrawColor()
if r != 40 || g != 50 || bl != 60 {
t.Errorf("GetDrawColor = (%d,%d,%d), want (40,50,60)", r, g, bl)
}
}
func TestSetGetFillColor(t *testing.T) {
b := newBuilderWithPage(t)
b.SetFillColor(70, 80, 90)
r, g, bl := b.GetFillColor()
if r != 70 || g != 80 || bl != 90 {
t.Errorf("GetFillColor = (%d,%d,%d), want (70,80,90)", r, g, bl)
}
}
func TestSetGetLineWidth(t *testing.T) {
b := newBuilderWithPage(t)
b.SetLineWidth(2.5)
if got := b.GetLineWidth(); got != 2.5 {
t.Errorf("LineWidth = %v, want 2.5", got)
}
}
func TestSetFont(t *testing.T) {
b := newBuilderWithPage(t)
b.SetFont(FontTimes, Bold, 16)
if b.fontName != FontTimes {
t.Errorf("fontName = %v, want %v", b.fontName, FontTimes)
}
if b.fontStyle != Bold {
t.Errorf("fontStyle = %v, want %v", b.fontStyle, Bold)
}
if got := b.GetFontSize(); got != 16 {
t.Errorf("FontSize = %v, want 16", got)
}
if b.cellHeight <= 0 {
t.Errorf("cellHeight must be >0 after SetFont, got %v", b.cellHeight)
}
}
func TestSetCellSpacing(t *testing.T) {
b := newBuilderWithPage(t)
b.SetCellSpacing(3.5)
if b.cellSpacing != 3.5 {
t.Errorf("cellSpacing = %v, want 3.5", b.cellSpacing)
}
}
func TestSetGetXY(t *testing.T) {
b := newBuilderWithPage(t)
b.SetX(50)
if got := b.GetX(); got != 50 {
t.Errorf("X = %v, want 50", got)
}
b.SetY(60)
if got := b.GetY(); got != 60 {
t.Errorf("Y = %v, want 60", got)
}
b.SetXY(70, 80)
x, y := b.GetXY()
if x != 70 || y != 80 {
t.Errorf("GetXY = (%v,%v), want (70,80)", x, y)
}
}
func TestIncX(t *testing.T) {
b := newBuilderWithPage(t)
b.SetX(10)
b.IncX(5)
if got := b.GetX(); got != 15 {
t.Errorf("after IncX: X = %v, want 15", got)
}
}
func TestDebug(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
if b.debug != false {
t.Errorf("default debug = %v, want false", b.debug)
}
b.Debug(true)
if !b.debug {
t.Errorf("Debug(true) did not enable debug")
}
b.Debug(false)
if b.debug {
t.Errorf("Debug(false) did not disable debug")
}
}
func TestPageNo(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
if got := b.PageNo(); got != 0 {
t.Errorf("PageNo before AddPage = %v, want 0", got)
}
b.AddPage()
if got := b.PageNo(); got != 1 {
t.Errorf("PageNo after first AddPage = %v, want 1", got)
}
b.AddPage()
if got := b.PageNo(); got != 2 {
t.Errorf("PageNo after second AddPage = %v, want 2", got)
}
}
func TestBuildEmpty(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
bin, err := b.Build()
if err != nil {
t.Fatalf("Build() error: %v", err)
}
if len(bin) == 0 {
t.Fatal("Build() returned empty bytes")
}
// PDFs start with "%PDF-"
if !bytes.HasPrefix(bin, []byte("%PDF-")) {
t.Errorf("Build() output does not start with %%PDF- header: %q", bin[:min(10, len(bin))])
}
}
func TestBuildWithContent(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
b.Cell("Hello", NewPDFCellOpt().Width(50))
b.Ln(5)
b.MultiCell("multiline content")
b.Rect(10, 10, RectFill, NewPDFRectOpt().X(20).Y(20).FillColor(255, 0, 0))
b.Line(0, 0, 100, 100, NewPDFLineOpt().LineWidth(0.5))
bin, err := b.Build()
if err != nil {
t.Fatalf("Build() error: %v", err)
}
if !bytes.HasPrefix(bin, []byte("%PDF-")) {
t.Errorf("Build() output does not start with %%PDF- header")
}
}
func TestGetStringWidth(t *testing.T) {
b := newBuilderWithPage(t)
w := b.GetStringWidth("Hello")
if w <= 0 {
t.Errorf("GetStringWidth = %v, want >0", w)
}
wLong := b.GetStringWidth("Hello, this is a longer string")
if wLong <= w {
t.Errorf("longer string should have wider width: short=%v long=%v", w, wLong)
}
}
func TestGetStringWidthWithFontOverride(t *testing.T) {
b := newBuilderWithPage(t)
b.SetFont(FontHelvetica, Normal, 10)
wSmall := b.GetStringWidth("Hello", *NewPDFCellOpt().FontSize(10))
wLarge := b.GetStringWidth("Hello", *NewPDFCellOpt().FontSize(40))
if wLarge <= wSmall {
t.Errorf("larger font should yield wider string: small=%v large=%v", wSmall, wLarge)
}
// Original font must be restored.
if b.fontSize != 10 {
t.Errorf("font size not restored: %v, want 10", b.fontSize)
}
}
func TestSetAutoPageBreak(t *testing.T) {
b := newBuilderWithPage(t)
b.SetAutoPageBreak(true, 10)
enabled, margin := b.FPDF().GetAutoPageBreak()
if !enabled || margin != 10 {
t.Errorf("AutoPageBreak = (%v, %v), want (true, 10)", enabled, margin)
}
b.SetAutoPageBreak(false, 0)
enabled, _ = b.FPDF().GetAutoPageBreak()
if enabled {
t.Errorf("AutoPageBreak should be disabled")
}
}
func TestSetFooterFunc(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
called := false
b.SetFooterFunc(func() { called = true })
b.AddPage()
_, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
if !called {
t.Errorf("footer func was not called")
}
}
func TestBookmark(t *testing.T) {
b := newBuilderWithPage(t)
// Should not panic
b.Bookmark("section 1", 0, b.GetY())
_, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestLnAdvancesY(t *testing.T) {
b := newBuilderWithPage(t)
b.SetXY(20, 50)
b.Ln(10)
if y := b.GetY(); y <= 50 {
t.Errorf("Ln did not advance Y: before=50, after=%v", y)
}
}
func TestDebugAddPage(t *testing.T) {
// Test with debug = true to cover that branch
b := NewPDFBuilder(Portrait, SizeA4, false)
b.Debug(true)
b.AddPage()
// Just verify Build still works
_, err := b.Build()
if err != nil {
t.Fatalf("Build with debug page error: %v", err)
}
}
func TestDebugLn(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
b.Debug(true)
b.SetXY(20, 50)
b.Ln(10)
_, err := b.Build()
if err != nil {
t.Fatalf("Build with debug Ln error: %v", err)
}
}
+265
View File
@@ -0,0 +1,265 @@
package wpdf
import (
"bytes"
"testing"
)
func TestCellWithAllOptions(t *testing.T) {
b := newBuilderWithPage(t)
b.Cell("test",
NewPDFCellOpt().
Width(40).
Height(8).
Border(BorderFull).
LnPos(BreakToNextLine).
Align(AlignHorzCenter).
FillBackground(true).
Font(FontTimes, Bold, 10).
LnAfter(2).
TextColor(255, 0, 0).
BorderColor(0, 255, 0).
FillColor(0, 0, 255).
Alpha(0.5, BlendMultiply))
out, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
if !bytes.HasPrefix(out, []byte("%PDF-")) {
t.Error("output not a PDF")
}
}
func TestCellRestoresFont(t *testing.T) {
b := newBuilderWithPage(t)
b.SetFont(FontHelvetica, Normal, 12)
b.Cell("text", NewPDFCellOpt().Width(50).Font(FontTimes, Bold, 20))
if b.fontName != FontHelvetica {
t.Errorf("fontName not restored: %v", b.fontName)
}
if b.fontStyle != Normal {
t.Errorf("fontStyle not restored: %v", b.fontStyle)
}
if b.fontSize != 12 {
t.Errorf("fontSize not restored: %v", b.fontSize)
}
}
func TestCellRestoresColors(t *testing.T) {
b := newBuilderWithPage(t)
b.SetTextColor(10, 20, 30)
b.SetDrawColor(40, 50, 60)
b.SetFillColor(70, 80, 90)
b.Cell("text",
NewPDFCellOpt().Width(50).
TextColor(1, 2, 3).
BorderColor(4, 5, 6).
FillColor(7, 8, 9))
r, g, bl := b.GetTextColor()
if r != 10 || g != 20 || bl != 30 {
t.Errorf("TextColor not restored: (%d,%d,%d)", r, g, bl)
}
r, g, bl = b.GetDrawColor()
if r != 40 || g != 50 || bl != 60 {
t.Errorf("DrawColor not restored: (%d,%d,%d)", r, g, bl)
}
r, g, bl = b.GetFillColor()
if r != 70 || g != 80 || bl != 90 {
t.Errorf("FillColor not restored: (%d,%d,%d)", r, g, bl)
}
}
func TestCellAutoWidth(t *testing.T) {
b := newBuilderWithPage(t)
startX := b.GetX()
b.Cell("Hello", NewPDFCellOpt().AutoWidth().AutoWidthPaddingX(2).LnPos(BreakToRight))
endX := b.GetX()
if endX <= startX {
t.Errorf("AutoWidth: X did not advance (start=%v end=%v)", startX, endX)
}
}
func TestCellWithExplicitX(t *testing.T) {
b := newBuilderWithPage(t)
b.Cell("text", NewPDFCellOpt().Width(50).X(70).LnPos(BreakToRight))
// X should be 70 + 50 = 120 after cell with BreakToRight
if got := b.GetX(); got < 120-0.01 || got > 120+0.01 {
t.Errorf("X after cell at X=70 with width=50 = %v, want ~120", got)
}
}
func TestCellWithDebugTrueBreakToRight(t *testing.T) {
b := newBuilderWithPage(t)
b.Debug(true)
b.Cell("text", NewPDFCellOpt().Width(20).LnPos(BreakToRight))
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestCellWithDebugTrueBreakToBelow(t *testing.T) {
b := newBuilderWithPage(t)
b.Debug(true)
b.Cell("text", NewPDFCellOpt().Width(20).LnPos(BreakToBelow))
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestMultiCellAllOptions(t *testing.T) {
b := newBuilderWithPage(t)
b.MultiCell("hello\nworld",
NewPDFMultiCellOpt().
Width(50).
Height(6).
Border(BorderFull).
Align(AlignLeft).
FillBackground(true).
Font(FontTimes, Italic, 10).
LnAfter(1).
X(20).
TextColor(1, 2, 3).
BorderColor(4, 5, 6).
FillColor(7, 8, 9).
Alpha(0.5, BlendNormal))
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestMultiCellRestoresFont(t *testing.T) {
b := newBuilderWithPage(t)
b.SetFont(FontHelvetica, Normal, 12)
b.MultiCell("text", NewPDFMultiCellOpt().Width(50).Font(FontTimes, Bold, 20))
if b.fontName != FontHelvetica {
t.Errorf("fontName not restored: %v", b.fontName)
}
if b.fontStyle != Normal {
t.Errorf("fontStyle not restored: %v", b.fontStyle)
}
if b.fontSize != 12 {
t.Errorf("fontSize not restored: %v", b.fontSize)
}
}
func TestMultiCellWithDebug(t *testing.T) {
b := newBuilderWithPage(t)
b.Debug(true)
b.MultiCell("hello world", NewPDFMultiCellOpt().Width(50))
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestRectAllOptions(t *testing.T) {
b := newBuilderWithPage(t)
b.Rect(20, 10, RectFillOutline,
NewPDFRectOpt().
X(15).Y(20).
LineWidth(0.5).
DrawColor(10, 20, 30).
FillColor(100, 110, 120).
Alpha(0.5, BlendNormal).
Rounded(2))
// Drawing color and line width should be restored after Rect.
r, g, bl := b.GetDrawColor()
if r != 0 || g != 0 || bl != 0 {
t.Errorf("draw color not restored: (%d,%d,%d)", r, g, bl)
}
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestRectStyles(t *testing.T) {
b := newBuilderWithPage(t)
b.Rect(10, 10, RectFill, NewPDFRectOpt().X(10).Y(10).FillColor(255, 0, 0))
b.Rect(10, 10, RectOutline, NewPDFRectOpt().X(30).Y(10).DrawColor(0, 255, 0))
b.Rect(10, 10, RectFillOutline, NewPDFRectOpt().X(50).Y(10).FillColor(0, 0, 255).DrawColor(255, 255, 0))
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestRectIndividualRadii(t *testing.T) {
b := newBuilderWithPage(t)
b.Rect(20, 20, RectOutline,
NewPDFRectOpt().
X(10).Y(10).
RadiusTL(2).RadiusTR(3).RadiusBR(4).RadiusBL(5))
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestLineAllOptions(t *testing.T) {
b := newBuilderWithPage(t)
b.Line(10, 10, 100, 100,
NewPDFLineOpt().
LineWidth(0.5).
DrawColor(255, 0, 0).
Alpha(0.7, BlendDarken).
CapRound())
// Line width should be restored after Line.
if got := b.GetLineWidth(); got == 0.5 {
t.Errorf("LineWidth was not restored: %v", got)
}
r, g, bl := b.GetDrawColor()
if r == 255 && g == 0 && bl == 0 {
t.Error("DrawColor not restored")
}
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestLineCapStyles(t *testing.T) {
b := newBuilderWithPage(t)
b.Line(10, 10, 100, 10, NewPDFLineOpt().LineWidth(2).CapButt())
b.Line(10, 20, 100, 20, NewPDFLineOpt().LineWidth(2).CapRound())
b.Line(10, 30, 100, 30, NewPDFLineOpt().LineWidth(2).CapSquare())
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestCellWithExtraLn(t *testing.T) {
b := newBuilderWithPage(t)
yBefore := b.GetY()
b.Cell("text", NewPDFCellOpt().Width(50).LnAfter(5))
yAfter := b.GetY()
if yAfter <= yBefore {
t.Errorf("Y did not advance after Cell with LnAfter")
}
}
func TestMultiCellWithExtraLn(t *testing.T) {
b := newBuilderWithPage(t)
yBefore := b.GetY()
b.MultiCell("multiline\ntext", NewPDFMultiCellOpt().Width(50).LnAfter(5))
yAfter := b.GetY()
if yAfter <= yBefore {
t.Errorf("Y did not advance after MultiCell with LnAfter")
}
}
+125
View File
@@ -0,0 +1,125 @@
package wpdf
import (
"bytes"
"image"
"image/color"
"image/png"
"strings"
"testing"
)
func makeTestPNG(t *testing.T, w, h int) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, w, h))
for y := range h {
for x := range w {
img.Set(x, y, color.RGBA{R: uint8(x), G: uint8(y), B: 128, A: 255})
}
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
t.Fatalf("png encode: %v", err)
}
return buf.Bytes()
}
func TestRegisterImageDetectsPNG(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
bin := makeTestPNG(t, 16, 16)
ref := b.RegisterImage(bin)
if ref == nil {
t.Fatal("RegisterImage returned nil")
}
if !strings.HasPrefix(ref.Name, "fpdf_img_") {
t.Errorf("default name not prefixed: %q", ref.Name)
}
if ref.Mime != "image/png" {
t.Errorf("mime = %q, want image/png", ref.Mime)
}
if ref.Info == nil {
t.Error("info is nil")
}
if !bytes.Equal(ref.Bin, bin) {
t.Error("Bin not stored correctly")
}
}
func TestRegisterImageWithCustomName(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
bin := makeTestPNG(t, 8, 8)
ref := b.RegisterImage(bin, NewPDFImageRegisterOpt().Name("custom_image"))
if ref.Name != "custom_image" {
t.Errorf("custom name not used: %q", ref.Name)
}
}
func TestRegisterImageWithExplicitType(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
bin := makeTestPNG(t, 8, 8)
ref := b.RegisterImage(bin, NewPDFImageRegisterOpt().ImageType("PNG"))
if ref.Info == nil {
t.Error("info is nil")
}
}
func TestRegisterImageLargeBuffer(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
// Larger than 512 bytes to exercise that branch in detection.
bin := makeTestPNG(t, 256, 256)
if len(bin) <= 512 {
t.Fatalf("test png unexpectedly small: %d bytes", len(bin))
}
ref := b.RegisterImage(bin)
if ref.Mime != "image/png" {
t.Errorf("mime = %q, want image/png", ref.Mime)
}
}
func TestImageDrawing(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
bin := makeTestPNG(t, 16, 16)
ref := b.RegisterImage(bin)
b.Image(ref, NewPDFImageOpt().X(10).Y(10).Width(20).Height(20))
out, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
if !bytes.HasPrefix(out, []byte("%PDF-")) {
t.Error("output not a PDF")
}
}
func TestImageDrawingDefaults(t *testing.T) {
b := NewPDFBuilder(Portrait, SizeA4, false)
b.AddPage()
bin := makeTestPNG(t, 16, 16)
ref := b.RegisterImage(bin)
b.Image(ref) // no opts: use info defaults
out, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
if !bytes.HasPrefix(out, []byte("%PDF-")) {
t.Error("output not a PDF")
}
}
+455
View File
@@ -0,0 +1,455 @@
package wpdf
import (
"testing"
)
func TestPDFCellOptBuilders(t *testing.T) {
opt := NewPDFCellOpt().
Width(100).
Height(20).
Border(BorderFull).
LnPos(BreakToRight).
Align(AlignHorzCenter).
FillBackground(true).
Link(5).
LinkStr("https://example.com").
Font(FontTimes, Bold, 14).
LnAfter(2).
X(15).
AutoWidth().
AutoWidthPaddingX(3).
TextColor(1, 2, 3).
BorderColor(4, 5, 6).
FillColor(7, 8, 9).
Alpha(0.5, BlendMultiply).
Debug(true)
if opt.width == nil || *opt.width != 100 {
t.Error("width not set")
}
if opt.height == nil || *opt.height != 20 {
t.Error("height not set")
}
if opt.border == nil || *opt.border != BorderFull {
t.Error("border not set")
}
if opt.ln == nil || *opt.ln != BreakToRight {
t.Error("ln not set")
}
if opt.align == nil || *opt.align != AlignHorzCenter {
t.Error("align not set")
}
if opt.fill == nil || *opt.fill != true {
t.Error("fill not set")
}
if opt.link == nil || *opt.link != 5 {
t.Error("link not set")
}
if opt.linkStr == nil || *opt.linkStr != "https://example.com" {
t.Error("linkStr not set")
}
if opt.fontNameOverride == nil || *opt.fontNameOverride != FontTimes {
t.Error("fontNameOverride not set")
}
if opt.fontStyleOverride == nil || *opt.fontStyleOverride != Bold {
t.Error("fontStyleOverride not set")
}
if opt.fontSizeOverride == nil || *opt.fontSizeOverride != 14 {
t.Error("fontSizeOverride not set")
}
if opt.extraLn == nil || *opt.extraLn != 2 {
t.Error("extraLn not set")
}
if opt.x == nil || *opt.x != 15 {
t.Error("x not set")
}
if opt.autoWidth == nil || *opt.autoWidth != true {
t.Error("autoWidth not set")
}
if opt.autoWidthPaddingX == nil || *opt.autoWidthPaddingX != 3 {
t.Error("autoWidthPaddingX not set")
}
if opt.textColor == nil || *opt.textColor != (PDFColor{R: 1, G: 2, B: 3}) {
t.Error("textColor not set")
}
if opt.borderColor == nil || *opt.borderColor != (PDFColor{R: 4, G: 5, B: 6}) {
t.Error("borderColor not set")
}
if opt.fillColor == nil || *opt.fillColor != (PDFColor{R: 7, G: 8, B: 9}) {
t.Error("fillColor not set")
}
if opt.alphaOverride == nil || opt.alphaOverride.V1 != 0.5 || opt.alphaOverride.V2 != BlendMultiply {
t.Error("alphaOverride not set")
}
if opt.debug == nil || *opt.debug != true {
t.Error("debug not set")
}
}
func TestPDFCellOptHexColors(t *testing.T) {
opt := NewPDFCellOpt().
TextColorHex(0xFF0000).
BorderColorHex(0x00FF00).
FillColorHex(0x0000FF)
if *opt.textColor != (PDFColor{R: 0xFF, G: 0, B: 0}) {
t.Errorf("textColorHex got %+v", opt.textColor)
}
if *opt.borderColor != (PDFColor{R: 0, G: 0xFF, B: 0}) {
t.Errorf("borderColorHex got %+v", opt.borderColor)
}
if *opt.fillColor != (PDFColor{R: 0, G: 0, B: 0xFF}) {
t.Errorf("fillColorHex got %+v", opt.fillColor)
}
}
func TestPDFCellOptBoldItalic(t *testing.T) {
opt := NewPDFCellOpt().Bold()
if opt.fontStyleOverride == nil || *opt.fontStyleOverride != Bold {
t.Error("Bold() should set fontStyleOverride to Bold")
}
opt2 := NewPDFCellOpt().Italic()
if opt2.fontStyleOverride == nil || *opt2.fontStyleOverride != Italic {
t.Error("Italic() should set fontStyleOverride to Italic")
}
}
func TestPDFCellOptIndividualFontSetters(t *testing.T) {
opt := NewPDFCellOpt().FontName(FontCourier).FontStyle(Italic).FontSize(11)
if *opt.fontNameOverride != FontCourier {
t.Error("FontName")
}
if *opt.fontStyleOverride != Italic {
t.Error("FontStyle")
}
if *opt.fontSizeOverride != 11 {
t.Error("FontSize")
}
}
func TestPDFCellOptCopy(t *testing.T) {
orig := NewPDFCellOpt().Width(50).Height(10)
cp := orig.Copy()
if cp == orig {
t.Error("Copy() returned same pointer")
}
if *cp.width != 50 || *cp.height != 10 {
t.Error("copied opt does not match original")
}
cp.Width(99)
if *orig.width != 50 {
t.Errorf("modifying copy affected original: orig.width=%v", *orig.width)
}
}
func TestPDFCellOptToMulti(t *testing.T) {
cell := NewPDFCellOpt().
Width(100).
Height(20).
Border(BorderTop).
Align(AlignRight).
FillBackground(true).
Font(FontTimes, Bold, 14).
LnAfter(3).
X(5).
TextColor(1, 2, 3).
BorderColor(4, 5, 6).
FillColor(7, 8, 9)
multi := cell.ToMulti()
if multi == nil {
t.Fatal("ToMulti returned nil")
}
if *multi.width != 100 {
t.Error("width not transferred")
}
if *multi.height != 20 {
t.Error("height not transferred")
}
if *multi.border != BorderTop {
t.Error("border not transferred")
}
if *multi.align != AlignRight {
t.Error("align not transferred")
}
if !*multi.fill {
t.Error("fill not transferred")
}
if *multi.fontNameOverride != FontTimes {
t.Error("fontName not transferred")
}
if *multi.fontStyleOverride != Bold {
t.Error("fontStyle not transferred")
}
if *multi.fontSizeOverride != 14 {
t.Error("fontSize not transferred")
}
if *multi.extraLn != 3 {
t.Error("extraLn not transferred")
}
if *multi.x != 5 {
t.Error("x not transferred")
}
if *multi.textColor != (PDFColor{R: 1, G: 2, B: 3}) {
t.Error("textColor not transferred")
}
if *multi.borderColor != (PDFColor{R: 4, G: 5, B: 6}) {
t.Error("borderColor not transferred")
}
if *multi.fillColor != (PDFColor{R: 7, G: 8, B: 9}) {
t.Error("fillColor not transferred")
}
}
func TestPDFMultiCellOptBuilders(t *testing.T) {
opt := NewPDFMultiCellOpt().
Width(80).
Height(10).
Border(BorderFull).
Align(AlignLeft).
FillBackground(true).
Font(FontTimes, Italic, 11).
LnAfter(1).
X(5).
TextColor(10, 20, 30).
BorderColor(40, 50, 60).
FillColor(70, 80, 90).
Alpha(0.7, BlendScreen).
Debug(true)
if *opt.width != 80 || *opt.height != 10 || *opt.border != BorderFull {
t.Error("base opts not set")
}
if *opt.align != AlignLeft || !*opt.fill {
t.Error("align/fill not set")
}
if *opt.fontNameOverride != FontTimes || *opt.fontStyleOverride != Italic || *opt.fontSizeOverride != 11 {
t.Error("font not set")
}
if *opt.extraLn != 1 || *opt.x != 5 {
t.Error("layout opts not set")
}
if *opt.textColor != (PDFColor{R: 10, G: 20, B: 30}) {
t.Error("textColor not set")
}
if *opt.borderColor != (PDFColor{R: 40, G: 50, B: 60}) {
t.Error("borderColor not set")
}
if *opt.fillColor != (PDFColor{R: 70, G: 80, B: 90}) {
t.Error("fillColor not set")
}
if opt.alphaOverride.V1 != 0.7 || opt.alphaOverride.V2 != BlendScreen {
t.Error("alpha not set")
}
if !*opt.debug {
t.Error("debug not set")
}
}
func TestPDFMultiCellOptHexColors(t *testing.T) {
opt := NewPDFMultiCellOpt().
TextColorHex(0xAABBCC).
BorderColorHex(0x112233).
FillColorHex(0x445566)
if *opt.textColor != (PDFColor{R: 0xAA, G: 0xBB, B: 0xCC}) {
t.Error("textColorHex")
}
if *opt.borderColor != (PDFColor{R: 0x11, G: 0x22, B: 0x33}) {
t.Error("borderColorHex")
}
if *opt.fillColor != (PDFColor{R: 0x44, G: 0x55, B: 0x66}) {
t.Error("fillColorHex")
}
}
func TestPDFMultiCellOptBoldItalic(t *testing.T) {
if *NewPDFMultiCellOpt().Bold().fontStyleOverride != Bold {
t.Error("MultiCell Bold")
}
if *NewPDFMultiCellOpt().Italic().fontStyleOverride != Italic {
t.Error("MultiCell Italic")
}
}
func TestPDFMultiCellOptIndividualFontSetters(t *testing.T) {
opt := NewPDFMultiCellOpt().FontName(FontCourier).FontStyle(Bold).FontSize(11)
if *opt.fontNameOverride != FontCourier {
t.Error("FontName")
}
if *opt.fontStyleOverride != Bold {
t.Error("FontStyle")
}
if *opt.fontSizeOverride != 11 {
t.Error("FontSize")
}
}
func TestPDFMultiCellOptCopy(t *testing.T) {
orig := NewPDFMultiCellOpt().Width(50)
cp := orig.Copy()
if cp == orig {
t.Error("Copy() returned same pointer")
}
cp.Width(99)
if *orig.width != 50 {
t.Error("modifying copy affected original")
}
}
func TestPDFRectOptBuilders(t *testing.T) {
opt := NewPDFRectOpt().
X(5).Y(6).
LineWidth(0.3).
DrawColor(10, 20, 30).
FillColor(40, 50, 60).
Alpha(0.4, BlendOverlay).
Rounded(2).
Debug(true)
if *opt.x != 5 || *opt.y != 6 {
t.Error("x/y not set")
}
if *opt.lineWidth != 0.3 {
t.Error("lineWidth not set")
}
if *opt.drawColor != (PDFColor{R: 10, G: 20, B: 30}) {
t.Error("drawColor not set")
}
if *opt.fillColor != (PDFColor{R: 40, G: 50, B: 60}) {
t.Error("fillColor not set")
}
if opt.alpha.V1 != 0.4 || opt.alpha.V2 != BlendOverlay {
t.Error("alpha not set")
}
if *opt.radiusTL != 2 || *opt.radiusTR != 2 || *opt.radiusBL != 2 || *opt.radiusBR != 2 {
t.Error("Rounded did not set all four corners")
}
if !*opt.debug {
t.Error("debug not set")
}
}
func TestPDFRectOptIndividualRadii(t *testing.T) {
opt := NewPDFRectOpt().RadiusTL(1).RadiusTR(2).RadiusBR(3).RadiusBL(4)
if *opt.radiusTL != 1 || *opt.radiusTR != 2 || *opt.radiusBR != 3 || *opt.radiusBL != 4 {
t.Errorf("individual radii not set: TL=%v TR=%v BR=%v BL=%v",
*opt.radiusTL, *opt.radiusTR, *opt.radiusBR, *opt.radiusBL)
}
}
func TestPDFRectOptHexColors(t *testing.T) {
opt := NewPDFRectOpt().DrawColorHex(0xABCDEF).FillColorHex(0x123456)
if *opt.drawColor != (PDFColor{R: 0xAB, G: 0xCD, B: 0xEF}) {
t.Error("drawColorHex")
}
if *opt.fillColor != (PDFColor{R: 0x12, G: 0x34, B: 0x56}) {
t.Error("fillColorHex")
}
}
func TestPDFLineOptBuilders(t *testing.T) {
opt := NewPDFLineOpt().
LineWidth(0.5).
DrawColor(10, 20, 30).
Alpha(0.3, BlendDarken).
CapButt().
Debug(true)
if *opt.lineWidth != 0.5 {
t.Error("lineWidth not set")
}
if *opt.drawColor != (PDFColor{R: 10, G: 20, B: 30}) {
t.Error("drawColor not set")
}
if opt.alpha.V1 != 0.3 || opt.alpha.V2 != BlendDarken {
t.Error("alpha not set")
}
if *opt.capStyle != CapButt {
t.Error("capStyle CapButt not set")
}
if !*opt.debug {
t.Error("debug not set")
}
}
func TestPDFLineOptCapStyles(t *testing.T) {
if *NewPDFLineOpt().CapButt().capStyle != CapButt {
t.Error("CapButt")
}
if *NewPDFLineOpt().CapRound().capStyle != CapRound {
t.Error("CapRound")
}
if *NewPDFLineOpt().CapSquare().capStyle != CapSquare {
t.Error("CapSquare")
}
}
func TestPDFLineOptHexColor(t *testing.T) {
opt := NewPDFLineOpt().DrawColorHex(0xFEDCBA)
if *opt.drawColor != (PDFColor{R: 0xFE, G: 0xDC, B: 0xBA}) {
t.Error("drawColorHex")
}
}
func TestPDFImageRegisterOptBuilders(t *testing.T) {
opt := NewPDFImageRegisterOpt().
ImageType("PNG").
ReadDpi(true).
AllowNegativePosition(true).
Name("custom_name")
if *opt.imageType != "PNG" {
t.Error("imageType not set")
}
if !*opt.readDpi {
t.Error("readDpi not set")
}
if !*opt.allowNegativePosition {
t.Error("allowNegativePosition not set")
}
if *opt.name != "custom_name" {
t.Error("name not set")
}
}
func TestPDFImageOptBuilders(t *testing.T) {
opt := NewPDFImageOpt().
X(1).Y(2).Width(30).Height(40).
Flow(false).
Link(7).LinkStr("foo").
ImageType("PNG").
ReadDpi(true).
AllowNegativePosition(true).
Crop(0.1, 0.2, 0.3, 0.4).
Alpha(0.6, BlendMultiply).
Debug(true)
if *opt.x != 1 || *opt.y != 2 || *opt.width != 30 || *opt.height != 40 {
t.Error("position/size not set")
}
if *opt.flow != false {
t.Error("flow not set")
}
if *opt.link != 7 || *opt.linkStr != "foo" {
t.Error("link not set")
}
if *opt.imageType != "PNG" || !*opt.readDpi || !*opt.allowNegativePosition {
t.Error("image options not set")
}
if opt.crop == nil || opt.crop.CropX != 0.1 || opt.crop.CropY != 0.2 || opt.crop.CropWidth != 0.3 || opt.crop.CropHeight != 0.4 {
t.Errorf("crop not set: %+v", opt.crop)
}
if opt.alphaOverride.V1 != 0.6 || opt.alphaOverride.V2 != BlendMultiply {
t.Error("alpha not set")
}
if !*opt.debug {
t.Error("debug not set")
}
}
+553
View File
@@ -0,0 +1,553 @@
package wpdf
import (
"bytes"
"math"
"testing"
)
func TestTableBuilderInitialState(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table()
if tb == nil {
t.Fatal("Table() returned nil")
}
if tb.builder != b {
t.Error("builder back-reference not set")
}
if tb.padx != 2 {
t.Errorf("default padx = %v, want 2", tb.padx)
}
if tb.pady != 2 {
t.Errorf("default pady = %v, want 2", tb.pady)
}
if tb.defaultCellStyle == nil {
t.Error("default cell style is nil")
}
if tb.RowCount() != 0 {
t.Errorf("RowCount = %v, want 0", tb.RowCount())
}
}
func TestTableBuilderConfig(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt()
tb := b.Table().
PadX(5).
PadY(7).
Widths("10", "20", "auto").
DefaultStyle(style).
Debug(true)
if tb.padx != 5 {
t.Errorf("padx = %v, want 5", tb.padx)
}
if tb.pady != 7 {
t.Errorf("pady = %v, want 7", tb.pady)
}
if tb.columnWidths == nil || len(*tb.columnWidths) != 3 {
t.Errorf("columnWidths not set correctly: %v", tb.columnWidths)
}
if tb.defaultCellStyle != style {
t.Error("defaultCellStyle not set")
}
if tb.debug == nil || !*tb.debug {
t.Error("debug not set")
}
}
func TestTableBuilderAddRowDefault(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table().AddRowDefaultStyle("a", "b", "c")
if tb.RowCount() != 1 {
t.Errorf("RowCount = %v, want 1", tb.RowCount())
}
if len(tb.rows[0].cells) != 3 {
t.Errorf("cells = %v, want 3", len(tb.rows[0].cells))
}
if tb.rows[0].cells[0].Content != "a" {
t.Errorf("cell[0].Content = %q, want %q", tb.rows[0].cells[0].Content, "a")
}
}
func TestTableBuilderAddRowWithStyle(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().Bold().FontSize(10)
tb := b.Table().AddRowWithStyle(style, "x", "y")
if tb.RowCount() != 1 {
t.Fatalf("RowCount = %v, want 1", tb.RowCount())
}
if len(tb.rows[0].cells) != 2 {
t.Fatalf("cells = %v, want 2", len(tb.rows[0].cells))
}
for i, c := range tb.rows[0].cells {
if c.Style.fontStyleOverride == nil || *c.Style.fontStyleOverride != Bold {
t.Errorf("cell[%d] style not Bold", i)
}
if c.Style.fontSizeOverride == nil || *c.Style.fontSizeOverride != 10 {
t.Errorf("cell[%d] fontSize not 10", i)
}
}
}
func TestTableBuilderAddRow(t *testing.T) {
b := newBuilderWithPage(t)
tc := TableCell{Content: "hello", Style: TableCellStyleOpt{}}
tb := b.Table().AddRow(tc, tc)
if tb.RowCount() != 1 || len(tb.rows[0].cells) != 2 {
t.Errorf("AddRow did not add expected cells")
}
}
func TestTableBuilderBuildRowFlow(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table().
BuildRow().Cell("a").Cell("b").Cell("c").BuildRow().
BuildRow().Cells("d", "e", "f").BuildRow()
if tb.RowCount() != 2 {
t.Errorf("RowCount = %v, want 2", tb.RowCount())
}
if len(tb.rows[0].cells) != 3 || len(tb.rows[1].cells) != 3 {
t.Errorf("each row should have 3 cells; got %d and %d",
len(tb.rows[0].cells), len(tb.rows[1].cells))
}
if tb.rows[0].cells[0].Content != "a" || tb.rows[1].cells[2].Content != "f" {
t.Error("cell content mismatch")
}
}
func TestTableRowBuilderCellWithStyle(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().Bold()
tb := b.Table().BuildRow().CellWithStyle("x", style).BuildRow()
if tb.rows[0].cells[0].Style.fontStyleOverride == nil ||
*tb.rows[0].cells[0].Style.fontStyleOverride != Bold {
t.Error("cell style not applied")
}
}
func TestTableRowBuilderCellObjects(t *testing.T) {
b := newBuilderWithPage(t)
c1 := TableCell{Content: "alpha"}
c2 := TableCell{Content: "beta"}
tb := b.Table().BuildRow().CellObject(c1).CellObjects(c2).BuildRow()
if len(tb.rows[0].cells) != 2 {
t.Fatalf("cells = %v, want 2", len(tb.rows[0].cells))
}
if tb.rows[0].cells[0].Content != "alpha" || tb.rows[0].cells[1].Content != "beta" {
t.Error("cell objects not added")
}
}
func TestTableRowBuilderRowStyle(t *testing.T) {
b := newBuilderWithPage(t)
rowStyle := NewTableCellStyleOpt().Italic()
tb := b.Table().BuildRow().RowStyle(rowStyle).Cell("x").Cell("y").BuildRow()
for i, c := range tb.rows[0].cells {
if c.Style.fontStyleOverride == nil || *c.Style.fontStyleOverride != Italic {
t.Errorf("cell[%d] should use row style (Italic)", i)
}
}
}
func TestTableMaxFontSize(t *testing.T) {
row := tableRow{
cells: []TableCell{
{Style: *NewTableCellStyleOpt().FontSize(10)},
{Style: *NewTableCellStyleOpt().FontSize(20)},
{Style: *NewTableCellStyleOpt().FontSize(15)},
},
}
got := row.maxFontSize(8)
if got != 20 {
t.Errorf("maxFontSize = %v, want 20", got)
}
rowEmpty := tableRow{cells: []TableCell{{Style: TableCellStyleOpt{}}}}
got = rowEmpty.maxFontSize(12)
if got != 12 {
t.Errorf("maxFontSize default = %v, want 12", got)
}
}
func TestTableBuilderBuildEmpty(t *testing.T) {
b := newBuilderWithPage(t)
// Should not panic when no rows
b.Table().Build()
}
func TestTableBuilderBuildNumeric(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("30", "30", "30").
AddRowDefaultStyle("a", "b", "c").
AddRowDefaultStyle("d", "e", "f").
Build()
bin, err := b.Build()
if err != nil {
t.Fatalf("Build error: %v", err)
}
if !bytes.HasPrefix(bin, []byte("%PDF-")) {
t.Error("output not a PDF")
}
}
func TestTableBuilderBuildAuto(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("auto", "auto", "auto").
AddRowDefaultStyle("a", "b", "c").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderBuildFr(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("1fr", "2fr", "*").
AddRowDefaultStyle("a", "b", "c").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderMixedColumnSpecs(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("auto", "30", "1fr", "*").
AddRowDefaultStyle("a", "b", "c", "d").
AddRowDefaultStyle("longer text here", "x", "y", "z").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderNoColumnsDefined(t *testing.T) {
b := newBuilderWithPage(t)
// When Widths not specified, table uses "*" for each cell of first row
b.Table().
AddRowDefaultStyle("a", "b").
AddRowDefaultStyle("c", "d").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderMismatchedColumnCount(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("10", "10").
AddRowDefaultStyle("a", "b", "c"). // wrong column count
Build()
// Should produce an error in the underlying gofpdf, surfaced by Build()
_, err := b.Build()
if err == nil {
t.Error("expected error for mismatched column count, got nil")
}
}
func TestTableBuilderInvalidColumnWidth(t *testing.T) {
b := newBuilderWithPage(t)
b.Table().
Widths("not-a-number").
AddRowDefaultStyle("a").
Build()
_, err := b.Build()
if err == nil {
t.Error("expected error for invalid column width, got nil")
}
}
func TestTableBuilderMultiCellRow(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().MultiCell(true)
b.Table().
Widths("auto", "1fr").
AddRowWithStyle(style, "a", "Multi line\ntext content").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderEllipsizeRow(t *testing.T) {
b := newBuilderWithPage(t)
style := NewTableCellStyleOpt().MultiCell(false).Ellipsize(true)
b.Table().
Widths("20").
AddRowWithStyle(style, "this is a very long text that should be ellipsized").
Build()
if _, err := b.Build(); err != nil {
t.Fatalf("Build error: %v", err)
}
}
func TestTableBuilderDefaultTableStyle(t *testing.T) {
s := defaultTableStyle()
if s == nil {
t.Fatal("defaultTableStyle returned nil")
}
if s.minWidth == nil || *s.minWidth != 5 {
t.Errorf("default minWidth = %v, want 5", s.minWidth)
}
if s.ellipsize == nil || !*s.ellipsize {
t.Errorf("default ellipsize should be true")
}
if s.multiCell == nil || *s.multiCell {
t.Errorf("default multiCell should be false")
}
if s.fontSizeOverride == nil || *s.fontSizeOverride != 8 {
t.Errorf("default font size = %v, want 8", s.fontSizeOverride)
}
}
func TestTableCellStyleOptBuilders(t *testing.T) {
o := NewTableCellStyleOpt().
MultiCell(true).
Ellipsize(false).
PaddingHorz(3).
MinWidth(7).
FillHeight(true)
if !*o.multiCell {
t.Error("multiCell")
}
if *o.ellipsize {
t.Error("ellipsize")
}
if *o.paddingHorz != 3 {
t.Error("paddingHorz")
}
if *o.minWidth != 7 {
t.Error("minWidth")
}
if !*o.fillHeight {
t.Error("fillHeight")
}
}
func TestTableCellStyleOptCellStyle(t *testing.T) {
cell := *NewPDFCellOpt().Width(50).Bold()
o := NewTableCellStyleOpt().CellStyle(cell)
if o.PDFCellOpt.width == nil || *o.PDFCellOpt.width != 50 {
t.Error("CellStyle did not transfer width")
}
if o.PDFCellOpt.fontStyleOverride == nil || *o.PDFCellOpt.fontStyleOverride != Bold {
t.Error("CellStyle did not transfer style")
}
}
func TestTableCellStyleOptDelegates(t *testing.T) {
o := NewTableCellStyleOpt().
Width(10).
Height(20).
Border(BorderFull).
LnPos(BreakToBelow).
Align(AlignRight).
FillBackground(true).
Link(5).
LinkStr("link").
Font(FontTimes, Bold, 11).
LnAfter(2).
X(3).
AutoWidth().
AutoWidthPaddingX(1).
TextColor(1, 2, 3).
BorderColor(4, 5, 6).
FillColor(7, 8, 9).
Alpha(0.5, BlendNormal).
Debug(true)
if *o.PDFCellOpt.width != 10 {
t.Error("width")
}
if *o.PDFCellOpt.height != 20 {
t.Error("height")
}
if *o.PDFCellOpt.border != BorderFull {
t.Error("border")
}
if *o.PDFCellOpt.ln != BreakToBelow {
t.Error("ln")
}
if *o.PDFCellOpt.align != AlignRight {
t.Error("align")
}
if !*o.PDFCellOpt.fill {
t.Error("fill")
}
if *o.PDFCellOpt.link != 5 {
t.Error("link")
}
if *o.PDFCellOpt.linkStr != "link" {
t.Error("linkStr")
}
if *o.PDFCellOpt.fontNameOverride != FontTimes {
t.Error("fontName")
}
if *o.PDFCellOpt.fontStyleOverride != Bold {
t.Error("fontStyle")
}
if *o.PDFCellOpt.fontSizeOverride != 11 {
t.Error("fontSize")
}
if *o.PDFCellOpt.extraLn != 2 {
t.Error("extraLn")
}
if *o.PDFCellOpt.x != 3 {
t.Error("x")
}
if !*o.PDFCellOpt.autoWidth {
t.Error("autoWidth")
}
if *o.PDFCellOpt.autoWidthPaddingX != 1 {
t.Error("autoWidthPaddingX")
}
if *o.PDFCellOpt.textColor != (PDFColor{R: 1, G: 2, B: 3}) {
t.Error("textColor")
}
if *o.PDFCellOpt.borderColor != (PDFColor{R: 4, G: 5, B: 6}) {
t.Error("borderColor")
}
if *o.PDFCellOpt.fillColor != (PDFColor{R: 7, G: 8, B: 9}) {
t.Error("fillColor")
}
if o.PDFCellOpt.alphaOverride.V1 != 0.5 || o.PDFCellOpt.alphaOverride.V2 != BlendNormal {
t.Error("alpha")
}
if !*o.PDFCellOpt.debug {
t.Error("debug")
}
}
func TestTableCellStyleOptBoldItalic(t *testing.T) {
if *NewTableCellStyleOpt().Bold().PDFCellOpt.fontStyleOverride != Bold {
t.Error("Bold")
}
if *NewTableCellStyleOpt().Italic().PDFCellOpt.fontStyleOverride != Italic {
t.Error("Italic")
}
}
func TestTableCellStyleOptHexColors(t *testing.T) {
o := NewTableCellStyleOpt().
TextColorHex(0x010203).
BorderColorHex(0x040506).
FillColorHex(0x070809)
if *o.PDFCellOpt.textColor != (PDFColor{R: 1, G: 2, B: 3}) {
t.Error("text hex")
}
if *o.PDFCellOpt.borderColor != (PDFColor{R: 4, G: 5, B: 6}) {
t.Error("border hex")
}
if *o.PDFCellOpt.fillColor != (PDFColor{R: 7, G: 8, B: 9}) {
t.Error("fill hex")
}
}
func TestTableCellStyleOptIndividualFontSetters(t *testing.T) {
o := NewTableCellStyleOpt().FontName(FontCourier).FontStyle(Italic).FontSize(9)
if *o.PDFCellOpt.fontNameOverride != FontCourier {
t.Error("FontName")
}
if *o.PDFCellOpt.fontStyleOverride != Italic {
t.Error("FontStyle")
}
if *o.PDFCellOpt.fontSizeOverride != 9 {
t.Error("FontSize")
}
}
func TestTableCalculateColumnsNumeric(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table().
Widths("30", "20", "40").
AddRowDefaultStyle("a", "b", "c")
w := tb.calculateColumns()
if len(w) != 3 {
t.Fatalf("widths = %v, want 3", len(w))
}
if w[0] != 30 || w[1] != 20 || w[2] != 40 {
t.Errorf("widths = %v, want [30, 20, 40]", w)
}
}
func TestTableCalculateColumnsFrSplit(t *testing.T) {
b := newBuilderWithPage(t)
b.SetMargins(PDFMargins{Left: 0, Top: 0, Right: 0})
tb := b.Table().
PadX(0).
Widths("1fr", "1fr").
AddRowDefaultStyle("a", "b")
pageW := b.GetPageWidth()
w := tb.calculateColumns()
if len(w) != 2 {
t.Fatalf("widths = %v, want 2", len(w))
}
// fr columns are bounded by autoWidths (max content); since "a" and "b"
// are very narrow strings, both columns get the same auto-bounded width.
if math.Abs(w[0]-w[1]) > 0.01 {
t.Errorf("expected fr split widths roughly equal, got %v and %v", w[0], w[1])
}
// Total should not exceed available page width.
if w[0]+w[1] > pageW+0.01 {
t.Errorf("total width %v exceeds pageW %v", w[0]+w[1], pageW)
}
}
func TestTableCalculateColumnsAutoUsesMinWidth(t *testing.T) {
b := newBuilderWithPage(t)
style := *NewTableCellStyleOpt()
mw := 50.0
style.minWidth = &mw
tb := b.Table().
Widths("auto").
AddRowWithStyle(&style, "x")
w := tb.calculateColumns()
if len(w) != 1 {
t.Fatalf("widths = %v, want 1", len(w))
}
if w[0] < 50 {
t.Errorf("width %v should respect minWidth=50", w[0])
}
}
func TestTableCalculateColumnsNoRows(t *testing.T) {
b := newBuilderWithPage(t)
tb := b.Table()
w := tb.calculateColumns()
if len(w) != 0 {
t.Errorf("widths for empty table = %v, want []", w)
}
}