diff --git a/goextVersion.go b/goextVersion.go index 40ae010..b9f1d8a 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.495" +const GoextVersion = "0.0.496" -const GoextVersionTimestamp = "2024-08-07T14:00:02+0200" +const GoextVersionTimestamp = "2024-08-07T15:34:06+0200" diff --git a/langext/array.go b/langext/array.go index 9ab975b..b8f98d6 100644 --- a/langext/array.go +++ b/langext/array.go @@ -323,6 +323,16 @@ func ArrMap[T1 any, T2 any](arr []T1, conv func(v T1) T2) []T2 { return r } +func ArrDeRef[T1 any](arr []*T1) []T1 { + r := make([]T1, 0, len(arr)) + for _, v := range arr { + if v != nil { + r = append(r, *v) + } + } + return r +} + func MapMap[TK comparable, TV any, TR any](inmap map[TK]TV, conv func(k TK, v TV) TR) []TR { r := make([]TR, 0, len(inmap)) for k, v := range inmap { diff --git a/wpdf/.gitignore b/wpdf/.gitignore new file mode 100644 index 0000000..9b1706d --- /dev/null +++ b/wpdf/.gitignore @@ -0,0 +1 @@ +wpdf_test.pdf \ No newline at end of file diff --git a/wpdf/logo.png b/wpdf/logo.png new file mode 100644 index 0000000..ff9d6a2 Binary files /dev/null and b/wpdf/logo.png differ diff --git a/wpdf/wpdf.go b/wpdf/wpdf.go index b3c7db0..fe7b534 100644 --- a/wpdf/wpdf.go +++ b/wpdf/wpdf.go @@ -14,6 +14,7 @@ type WPDFBuilder struct { fontName PDFFontFamily fontStyle PDFFontStyle fontSize float64 + debug bool } type PDFMargins struct { @@ -62,6 +63,19 @@ func (b *WPDFBuilder) SetMargins(v PDFMargins) { func (b *WPDFBuilder) AddPage() { b.b.AddPage() + + if b.debug { + + ml, mt, mr, mb := b.GetMargins() + pw, ph := b.GetPageSize() + + b.Rect(pw-ml-mr, ph-mt-mb, RectOutline, NewPDFRectOpt().X(ml).Y(mt).LineWidth(0.25).DrawColor(0, 0, 128)) + + b.Rect(pw, mt, RectFill, NewPDFRectOpt().X(0).Y(0).FillColor(0, 0, 255).Alpha(0.2, BlendNormal)) + b.Rect(ml, ph-mt-mb, RectFill, NewPDFRectOpt().X(0).Y(mt).FillColor(0, 0, 255).Alpha(0.2, BlendNormal)) + b.Rect(mr, ph-mt-mb, RectFill, NewPDFRectOpt().X(pw-mr).Y(mt).FillColor(0, 0, 255).Alpha(0.2, BlendNormal)) + b.Rect(pw, mb, RectFill, NewPDFRectOpt().X(0).Y(ph-mb).FillColor(0, 0, 255).Alpha(0.2, BlendNormal)) + } } func (b *WPDFBuilder) SetTextColor(cr, cg, cb int) { @@ -123,7 +137,21 @@ func (b *WPDFBuilder) SetCellSpacing(h float64) { } func (b *WPDFBuilder) Ln(h float64) { + xBefore, yBefore := b.GetXY() + b.b.Ln(h) + + yAfter := b.GetY() + + if b.debug { + + _, _, mr, _ := b.GetMargins() + pw, _ := b.GetPageSize() + + b.Rect(pw-mr-xBefore, yAfter-yBefore, RectOutline, NewPDFRectOpt().X(xBefore).Y(yBefore).LineWidth(0.25).DrawColor(128, 128, 0).Alpha(0.5, BlendNormal)) + b.Rect(pw-mr-xBefore, yAfter-yBefore, RectFill, NewPDFRectOpt().X(xBefore).Y(yBefore).LineWidth(0.25).FillColor(128, 128, 0).Alpha(0.1, BlendNormal)) + b.Line(xBefore, yBefore, pw-mr, yAfter, NewPDFLineOpt().LineWidth(0.25).DrawColor(128, 128, 0)) + } } func (b *WPDFBuilder) Build() ([]byte, error) { @@ -242,3 +270,7 @@ func (b *WPDFBuilder) GetStringWidth(str string, opts ...PDFCellOpt) float64 { return b.b.GetStringWidth(str) } + +func (b *WPDFBuilder) Debug(v bool) { + b.debug = v +} diff --git a/wpdf/wpdfCell.go b/wpdf/wpdfCell.go index 5835277..cf2d806 100644 --- a/wpdf/wpdfCell.go +++ b/wpdf/wpdfCell.go @@ -25,6 +25,7 @@ type PDFCellOpt struct { borderColor *PDFColor fillColor *PDFColor autoWidthPaddingX *float64 + debug *bool } func NewPDFCellOpt() *PDFCellOpt { @@ -158,6 +159,11 @@ func (opt *PDFCellOpt) Alpha(alpha float64, blendMode PDFBlendMode) *PDFCellOpt return opt } +func (opt *PDFCellOpt) Debug(v bool) *PDFCellOpt { + opt.debug = &v + return opt +} + func (opt *PDFCellOpt) Copy() *PDFCellOpt { c := *opt return &c @@ -186,7 +192,7 @@ func (b *WPDFBuilder) Cell(txt string, opts ...*PDFCellOpt) { txtTR := b.tr(txt) width := float64(0) - height := b.cellHeight + b.cellSpacing + var height *float64 = nil border := BorderNone ln := BreakToNextLine align := AlignLeft @@ -204,10 +210,11 @@ func (b *WPDFBuilder) Cell(txt string, opts ...*PDFCellOpt) { var borderColor *PDFColor var fillColor *PDFColor autoWidthPaddingX := float64(0) + debug := b.debug for _, opt := range opts { width = langext.Coalesce(opt.width, width) - height = langext.Coalesce(opt.height, height) + height = langext.CoalesceOpt(opt.height, height) border = langext.Coalesce(opt.border, border) ln = langext.Coalesce(opt.ln, ln) align = langext.Coalesce(opt.align, align) @@ -225,6 +232,7 @@ func (b *WPDFBuilder) Cell(txt string, opts ...*PDFCellOpt) { borderColor = langext.CoalesceOpt(opt.borderColor, borderColor) fillColor = langext.CoalesceOpt(opt.fillColor, fillColor) autoWidthPaddingX = langext.Coalesce(opt.autoWidthPaddingX, autoWidthPaddingX) + debug = langext.Coalesce(opt.debug, debug) } if fontNameOverride != nil || fontStyleOverride != nil || fontSizeOverride != nil { @@ -238,6 +246,11 @@ func (b *WPDFBuilder) Cell(txt string, opts ...*PDFCellOpt) { defer func() { b.SetFont(oldFontName, oldFontStyle, oldFontSize) }() } + if height == nil { + // (do after SetFont, so that b.cellHeight is correctly set to fontOverride) + height = langext.Ptr(b.cellHeight + b.cellSpacing) + } + if textColor != nil { oldColorR, oldColorG, oldColorB := b.b.GetTextColor() b.SetTextColor(textColor.R, textColor.G, textColor.B) @@ -267,10 +280,22 @@ func (b *WPDFBuilder) Cell(txt string, opts ...*PDFCellOpt) { } if autoWidth { - width = b.b.GetStringWidth(txtTR) + autoWidthPaddingX + width = b.GetStringWidth(txtTR, langext.ArrDeRef(opts)...) + autoWidthPaddingX } - b.b.CellFormat(width, height, txtTR, string(border), int(ln), string(align), fill, link, linkStr) + xBefore, yBefore := b.b.GetXY() + + b.b.CellFormat(width, *height, txtTR, string(border), int(ln), string(align), fill, link, linkStr) + + if debug { + if ln == BreakToNextLine { + b.Rect(b.GetPageWidth()-xBefore-b.GetMarginRight(), *height, RectOutline, NewPDFRectOpt().X(xBefore).Y(yBefore).LineWidth(0.25).DrawColor(0, 128, 0)) + } else if ln == BreakToRight { + b.Rect(b.GetX()-xBefore, *height, RectOutline, NewPDFRectOpt().X(xBefore).Y(yBefore).LineWidth(0.25).DrawColor(0, 128, 0)) + } else if ln == BreakToBelow { + b.Rect(b.GetPageWidth(), *height, RectOutline, NewPDFRectOpt().X(xBefore).Y(yBefore).LineWidth(0.25).DrawColor(0, 128, 0)) + } + } if extraLn != 0 { b.b.Ln(extraLn) diff --git a/wpdf/wpdfImage.go b/wpdf/wpdfImage.go index c3547c0..80d589a 100644 --- a/wpdf/wpdfImage.go +++ b/wpdf/wpdfImage.go @@ -247,7 +247,7 @@ func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) { var imageFit *imageext.ImageFit = nil var fillColor color.Color = color.Transparent compression := imageext.CompressionPNGSpeed - debug := false + debug := b.debug var crop *imageext.ImageCrop = nil var alphaOverride *dataext.Tuple[float64, PDFBlendMode] @@ -271,6 +271,10 @@ func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) { alphaOverride = langext.CoalesceOpt(opt.alphaOverride, alphaOverride) } + if flow { + y = b.GetY() + } + regName := img.Name var subImageBounds *imageext.PercentageRectangle = nil @@ -355,13 +359,14 @@ func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) { b.b.ImageOptions(regName, x, y, w, h, flow, fpdfOpt, link, linkStr) if debug { - b.Rect(w, h, RectOutline, NewPDFRectOpt().X(x).Y(y).LineWidth(2).DrawColor(255, 0, 0)) + b.Rect(w, h, RectOutline, NewPDFRectOpt().X(x).Y(y).LineWidth(0.25).DrawColor(255, 0, 0)) if subImageBounds != nil { r := subImageBounds.Of(imageext.Rectangle{X: x, Y: y, W: w, H: h}) + b.Rect(r.W, r.H, RectOutline, NewPDFRectOpt().X(r.X).Y(r.Y).LineWidth(0.25).DrawColor(255, 0, 0)) b.Rect(r.W, r.H, RectFill, NewPDFRectOpt().X(r.X).Y(r.Y).FillColor(255, 0, 0).Alpha(0.2, BlendNormal)) - b.Line(r.X, r.Y, r.X+r.W, r.Y+r.H, NewPDFLineOpt().LineWidth(2).DrawColor(255, 0, 0)) - b.Line(r.X+r.W, r.Y, r.X, r.Y+r.H, NewPDFLineOpt().LineWidth(2).DrawColor(255, 0, 0)) + b.Line(r.X, r.Y, r.X+r.W, r.Y+r.H, NewPDFLineOpt().LineWidth(0.25).DrawColor(255, 0, 0)) + b.Line(r.X+r.W, r.Y, r.X, r.Y+r.H, NewPDFLineOpt().LineWidth(0.25).DrawColor(255, 0, 0)) } } } diff --git a/wpdf/wpdfLine.go b/wpdf/wpdfLine.go index c280792..a3390ec 100644 --- a/wpdf/wpdfLine.go +++ b/wpdf/wpdfLine.go @@ -10,6 +10,7 @@ type PDFLineOpt struct { drawColor *PDFColor alpha *dataext.Tuple[float64, PDFBlendMode] capStyle *PDFLineCapStyle + debug *bool } func NewPDFLineOpt() *PDFLineOpt { @@ -51,17 +52,24 @@ func (opt *PDFLineOpt) CapRound() *PDFLineOpt { return opt } +func (opt *PDFLineOpt) Debug(v bool) *PDFLineOpt { + opt.debug = &v + return opt +} + func (b *WPDFBuilder) Line(x1 float64, y1 float64, x2 float64, y2 float64, opts ...*PDFLineOpt) { var lineWidth *float64 var drawColor *PDFColor var alphaOverride *dataext.Tuple[float64, PDFBlendMode] capStyle := CapButt + debug := b.debug for _, opt := range opts { lineWidth = langext.CoalesceOpt(opt.lineWidth, lineWidth) drawColor = langext.CoalesceOpt(opt.drawColor, drawColor) alphaOverride = langext.CoalesceOpt(opt.alpha, alphaOverride) capStyle = langext.Coalesce(opt.capStyle, capStyle) + debug = langext.Coalesce(opt.debug, debug) } if lineWidth != nil { diff --git a/wpdf/wpdfMultiCell.go b/wpdf/wpdfMultiCell.go index c03e52c..0300f76 100644 --- a/wpdf/wpdfMultiCell.go +++ b/wpdf/wpdfMultiCell.go @@ -20,6 +20,7 @@ type PDFMultiCellOpt struct { textColor *PDFColor borderColor *PDFColor fillColor *PDFColor + debug *bool } func NewPDFMultiCellOpt() *PDFMultiCellOpt { @@ -128,6 +129,16 @@ func (opt *PDFMultiCellOpt) Alpha(alpha float64, blendMode PDFBlendMode) *PDFMul return opt } +func (opt *PDFMultiCellOpt) Debug(v bool) *PDFMultiCellOpt { + opt.debug = &v + return opt +} + +func (opt *PDFMultiCellOpt) Copy() *PDFMultiCellOpt { + c := *opt + return &c +} + func (b *WPDFBuilder) MultiCell(txt string, opts ...*PDFMultiCellOpt) { txtTR := b.tr(txt) @@ -146,6 +157,7 @@ func (b *WPDFBuilder) MultiCell(txt string, opts ...*PDFMultiCellOpt) { var textColor *PDFColor var borderColor *PDFColor var fillColor *PDFColor + debug := b.debug for _, opt := range opts { width = langext.Coalesce(opt.width, width) @@ -162,6 +174,7 @@ func (b *WPDFBuilder) MultiCell(txt string, opts ...*PDFMultiCellOpt) { textColor = langext.CoalesceOpt(opt.textColor, textColor) borderColor = langext.CoalesceOpt(opt.borderColor, borderColor) fillColor = langext.CoalesceOpt(opt.fillColor, fillColor) + debug = langext.Coalesce(opt.debug, debug) } if fontNameOverride != nil || fontStyleOverride != nil || fontSizeOverride != nil { @@ -203,8 +216,14 @@ func (b *WPDFBuilder) MultiCell(txt string, opts ...*PDFMultiCellOpt) { b.b.SetX(*x) } + xBefore, yBefore := b.b.GetXY() + b.b.MultiCell(width, height, txtTR, string(border), string(align), fill) + if debug { + b.Rect(b.GetPageWidth()-xBefore-b.GetMarginRight(), b.GetY()-yBefore, RectOutline, NewPDFRectOpt().X(xBefore).Y(yBefore).LineWidth(0.25).DrawColor(0, 128, 0)) + } + if extraLn != 0 { b.b.Ln(extraLn) } diff --git a/wpdf/wpdfRect.go b/wpdf/wpdfRect.go index 30b251f..0ec70a9 100644 --- a/wpdf/wpdfRect.go +++ b/wpdf/wpdfRect.go @@ -16,6 +16,7 @@ type PDFRectOpt struct { radiusTR *float64 radiusBR *float64 radiusBL *float64 + debug *bool } func NewPDFRectOpt() *PDFRectOpt { @@ -90,6 +91,11 @@ func (opt *PDFRectOpt) Alpha(alpha float64, blendMode PDFBlendMode) *PDFRectOpt return opt } +func (opt *PDFRectOpt) Debug(v bool) *PDFRectOpt { + opt.debug = &v + return opt +} + func (b *WPDFBuilder) Rect(w float64, h float64, styleStr PDFRectStyle, opts ...*PDFRectOpt) { x := b.GetX() y := b.GetY() @@ -101,6 +107,7 @@ func (b *WPDFBuilder) Rect(w float64, h float64, styleStr PDFRectStyle, opts ... radiusTR := float64(0) radiusBR := float64(0) radiusBL := float64(0) + debug := b.debug for _, opt := range opts { x = langext.Coalesce(opt.x, x) @@ -113,6 +120,7 @@ func (b *WPDFBuilder) Rect(w float64, h float64, styleStr PDFRectStyle, opts ... radiusTR = langext.Coalesce(opt.radiusTR, radiusTR) radiusBR = langext.Coalesce(opt.radiusBR, radiusBR) radiusBL = langext.Coalesce(opt.radiusBL, radiusBL) + debug = langext.Coalesce(opt.debug, debug) } if lineWidth != nil { diff --git a/wpdf/wpdfTable.go b/wpdf/wpdfTable.go index dc77fc9..6d71a75 100644 --- a/wpdf/wpdfTable.go +++ b/wpdf/wpdfTable.go @@ -35,6 +35,7 @@ type TableBuilder struct { rows []tableRow defaultCellStyle *TableCellStyleOpt columnWidths *[]string + debug *bool } type TableCell struct { @@ -85,31 +86,38 @@ func (b *TableBuilder) PadY(v float64) *TableBuilder { return b } -func (b *TableBuilder) AddRow(cells ...TableCell) { +func (b *TableBuilder) AddRow(cells ...TableCell) *TableBuilder { b.rows = append(b.rows, tableRow{cells: cells}) + return b } -func (b *TableBuilder) AddRowWithStyle(style TableCellStyleOpt, cells ...string) { +func (b *TableBuilder) AddRowWithStyle(style TableCellStyleOpt, cells ...string) *TableBuilder { tcels := make([]TableCell, 0, len(cells)) for _, cell := range cells { tcels = append(tcels, TableCell{Content: cell, Style: style}) } b.rows = append(b.rows, tableRow{cells: tcels}) + + return b } -func (b *TableBuilder) AddRowDefaultStyle(cells ...string) { +func (b *TableBuilder) AddRowDefaultStyle(cells ...string) *TableBuilder { tcels := make([]TableCell, 0, len(cells)) for _, cell := range cells { tcels = append(tcels, TableCell{Content: cell, Style: langext.Coalesce(b.defaultCellStyle, TableCellStyleOpt{})}) } b.rows = append(b.rows, tableRow{cells: tcels}) + + return b } func (b *TableBuilder) Build() { builder := b.builder + debug := langext.Coalesce(b.debug, b.builder.debug) + if len(b.rows) == 0 { return // nothing to do } @@ -150,7 +158,7 @@ func (b *TableBuilder) Build() { if langext.Coalesce(style.MultiCell, true) { - builder.MultiCell(str, style.PDFCellOpt.Copy().ToMulti().Width(cellWidth)) + builder.MultiCell(str, style.PDFCellOpt.Copy().ToMulti().Width(cellWidth).Debug(debug)) } else { @@ -163,7 +171,7 @@ func (b *TableBuilder) Build() { } } - builder.Cell(str, style.PDFCellOpt.Copy().Width(cellWidth)) + builder.Cell(str, style.PDFCellOpt.Copy().Width(cellWidth).Debug(debug)) } @@ -307,6 +315,11 @@ func (b *TableBuilder) RowCount() int { return len(b.rows) } +func (b *TableBuilder) Debug(v bool) *TableBuilder { + b.debug = &v + return b +} + func (b *WPDFBuilder) Table() *TableBuilder { return &TableBuilder{ builder: b, @@ -321,10 +334,11 @@ func defaultTableStyle() *TableCellStyleOpt { return &TableCellStyleOpt{ PDFCellOpt: *NewPDFCellOpt(). FontSize(float64(8)). - BorderColorHex(uint32(0x888888)). - FillColorHex(uint32(0xFFFFFF)). + Border(BorderFull). + BorderColorHex(uint32(0x666666)). + FillColorHex(uint32(0xF0F0F0)). TextColorHex(uint32(0x000000)). - FillBackground(false), + FillBackground(true), MinWidth: langext.Ptr(float64(5)), Ellipsize: langext.PTrue, MultiCell: langext.PFalse, diff --git a/wpdf/wpdf_test.go b/wpdf/wpdf_test.go new file mode 100644 index 0000000..d00fde3 --- /dev/null +++ b/wpdf/wpdf_test.go @@ -0,0 +1,88 @@ +package wpdf + +import ( + _ "embed" + "gogs.mikescher.com/BlackForestBytes/goext/imageext" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "os" + "testing" +) + +//go:embed logo.png +var logoData []byte + +func TestPDFBuilder(t *testing.T) { + builder := NewPDFBuilder(Portrait, SizeA4, true) + + builder.Debug(true) + + logoRef := builder.RegisterImage(logoData) + + builder.SetMargins(PDFMargins{Left: 15, Top: 40, Right: 10}) + builder.AddPage() + + builder.SetFont(FontHelvetica, Normal, 10) + builder.Cell("Neueinrichtung deiner Entgeltumwandlung", NewPDFCellOpt().Bold().FontSize(20)) + builder.Ln(10) + + builder.SetFont(FontHelvetica, Normal, 10) + builder.Cell("Hello World", NewPDFCellOpt().Width(50).Align(AlignHorzCenter).LnPos(BreakToRight)) + builder.IncX(10) + builder.Cell("Second Text", NewPDFCellOpt().AutoWidth().AutoWidthPaddingX(2).LnPos(BreakToRight)) + builder.Ln(10) + + builder.MultiCell("Im Fall einer individuellen Entgeltumwandlung ist die Zuschussverpflichtung auf der Grundlage des Betriebsrentenstärkungsgesetzes in der gesetzlich vorgeschriebenen Höhe (§ 1a Abs. 1a BetrAVG), über den arbeitgeberfinanzierten Zuschuss erfüllt.") + builder.Ln(4) + + builder.Image(logoRef, NewPDFImageOpt().X(90).Y(160).Width(70).Height(30).ImageFit(imageext.ImageFitContainCenter)) + + builder.Ln(4) + + cellStyleHeader := TableCellStyleOpt{ + PDFCellOpt: *NewPDFCellOpt(). + FontSize(float64(8)). + BorderColorHex(uint32(0x666666)). + Border(BorderFull). + FillColorHex(uint32(0xC0C0C0)). + FillBackground(true). + TextColorHex(uint32(0x000000)). + Align(AlignHorzCenter). + Bold(), + MinWidth: langext.Ptr(float64(5)), + Ellipsize: langext.PTrue, + MultiCell: langext.PFalse, + } + + cellStyleMulti := TableCellStyleOpt{ + PDFCellOpt: *NewPDFCellOpt(). + FontSize(float64(8)). + BorderColorHex(uint32(0x666666)). + Border(BorderFull). + FillColorHex(uint32(0xC060C0)). + FillBackground(true). + TextColorHex(uint32(0x000000)), + MinWidth: langext.Ptr(float64(5)), + Ellipsize: langext.PFalse, + MultiCell: langext.PTrue, + } + + builder.Table(). + Widths("auto", "20", "1fr", "20"). + PadX(2). + PadY(2). + AddRowWithStyle(cellStyleHeader, "test", "hello", "123", "end"). + AddRowDefaultStyle("test", "hello", "123", "end"). + AddRowDefaultStyle("123", "helasdsalo", "a", "enwqad"). + AddRowDefaultStyle("123asd", "TrimMeTrimMeTrimMeTrimMe", "a", "enwqad"). + AddRowWithStyle(cellStyleMulti, "123", "helasdsalo", "a", "MultiCell: enwqad enw\nqad enwqad enwqad enwqad enwqad enwqad enwqad enwqad"). + AddRowDefaultStyle("123", "helasdsalo", "a", "enwqad"). + Debug(false). + Build() + + bin, err := builder.Build() + if err != nil { + t.Fatal(err) + } + + _ = os.WriteFile("wpdf_test.pdf", bin, 0644) +}