diff --git a/Makefile b/Makefile index 90fe1b1a..41f121fb 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,7 @@ examples: go run docs/assets/examples/barcodegrid/v2/main.go go run docs/assets/examples/billing/v2/main.go go run docs/assets/examples/cellstyle/v2/main.go + go run docs/assets/examples/checkbox/v2/main.go go run docs/assets/examples/compression/v2/main.go go run docs/assets/examples/customdimensions/v2/main.go go run docs/assets/examples/customfont/v2/main.go diff --git a/docs/assets/examples/checkbox/v2/main.go b/docs/assets/examples/checkbox/v2/main.go new file mode 100644 index 00000000..8a68736d --- /dev/null +++ b/docs/assets/examples/checkbox/v2/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "github.com/johnfercher/maroto/v2/pkg/components/checkbox" + "github.com/johnfercher/maroto/v2/pkg/props" + "log" + + "github.com/johnfercher/maroto/v2/pkg/core" + + "github.com/johnfercher/maroto/v2" + + "github.com/johnfercher/maroto/v2/pkg/config" +) + +func main() { + m := GetMaroto() + document, err := m.Generate() + if err != nil { + log.Fatal(err.Error()) + } + + err = document.Save("docs/assets/pdf/checkboxv2.pdf") + if err != nil { + log.Fatal(err.Error()) + } + + err = document.GetReport().Save("docs/assets/text/checkboxv2.txt") + if err != nil { + log.Fatal(err.Error()) + } +} + +func GetMaroto() core.Maroto { + cfg := config.NewBuilder(). + WithDebug(true). + Build() + + mrt := maroto.New(cfg) + m := maroto.NewMetricsDecorator(mrt) + + m.AddRow(40, + checkbox.NewCol(2, "label 1"), + checkbox.NewCol(3, "label 2", props.Checkbox{ + Checked: true, + }), + checkbox.NewCol(4, "label 3", props.Checkbox{ + BoxSize: 10, + }), + checkbox.NewCol(3, "label 4", props.Checkbox{ + Left: 10, + }), + ) + + return m +} diff --git a/docs/assets/examples/checkbox/v2/main_test.go b/docs/assets/examples/checkbox/v2/main_test.go new file mode 100644 index 00000000..6633222a --- /dev/null +++ b/docs/assets/examples/checkbox/v2/main_test.go @@ -0,0 +1,15 @@ +package main + +import ( + "testing" + + "github.com/johnfercher/maroto/v2/pkg/test" +) + +func TestGetMaroto(t *testing.T) { + // Act + sut := GetMaroto() + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("examples/barcodegrid.json") +} diff --git a/docs/assets/pdf/checkboxv2.pdf b/docs/assets/pdf/checkboxv2.pdf new file mode 100755 index 00000000..871bd837 Binary files /dev/null and b/docs/assets/pdf/checkboxv2.pdf differ diff --git a/docs/assets/text/checkboxv2.txt b/docs/assets/text/checkboxv2.txt new file mode 100644 index 00000000..f0fa677d --- /dev/null +++ b/docs/assets/text/checkboxv2.txt @@ -0,0 +1,3 @@ +generate -> avg: 1.37ms, executions: [1.37ms] +add_row -> avg: 15709.00ns, executions: [15.71μs] +file_size -> 2.84Kb diff --git a/docs/v2/features/checkbox.md b/docs/v2/features/checkbox.md new file mode 100644 index 00000000..437b3f57 --- /dev/null +++ b/docs/v2/features/checkbox.md @@ -0,0 +1,22 @@ +# Checkbox + +## GoDoc +* [constructor : NewBar](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/code#NewBar) +* [constructor : NewBarCol](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/code#NewBarCol) +* [constructor : NewBarRow](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/code#NewBarRow) +* [props : Barcode](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/props#Barcode) +* [component : Barcode](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/code#Barcode) + +## Code Example +[filename](../../assets/examples/barcodegrid/v2/main.go ':include :type=code') + +## PDF Generated +```pdf + assets/pdf/barcodegridv2.pdf +``` + +## Time Execution +[filename](../../assets/text/barcodegridv2.txt ':include :type=code') + +## Test File +[filename](https://raw.githubusercontent.com/johnfercher/maroto/master/test/maroto/examples/barcodegrid.json ':include :type=code') \ No newline at end of file diff --git a/internal/providers/gofpdf/builder.go b/internal/providers/gofpdf/builder.go index b53a22a2..44db552a 100644 --- a/internal/providers/gofpdf/builder.go +++ b/internal/providers/gofpdf/builder.go @@ -20,6 +20,7 @@ type Dependencies struct { Code core.Code Image core.Image Line core.Line + Checkbox *checkbox Cache cache.Cache CellWriter cellwriter.CellWriter Cfg *entity.Config @@ -68,6 +69,7 @@ func (b *builder) Build(cfg *entity.Config, cache cache.Cache) *Dependencies { text := NewText(fpdf, math, font) image := NewImage(fpdf, math) line := NewLine(fpdf) + checkbox := NewCheckbox(fpdf, text) cellWriter := cellwriter.NewBuilder(). Build(fpdf) @@ -78,6 +80,7 @@ func (b *builder) Build(cfg *entity.Config, cache cache.Cache) *Dependencies { Code: code, Image: image, Line: line, + Checkbox: checkbox, CellWriter: cellWriter, Cfg: cfg, Cache: cache, diff --git a/internal/providers/gofpdf/checkbox.go b/internal/providers/gofpdf/checkbox.go new file mode 100644 index 00000000..7c4e32e9 --- /dev/null +++ b/internal/providers/gofpdf/checkbox.go @@ -0,0 +1,98 @@ +package gofpdf + +import ( + "github.com/johnfercher/maroto/v2/internal/providers/gofpdf/gofpdfwrapper" + "github.com/johnfercher/maroto/v2/pkg/core/entity" + "github.com/johnfercher/maroto/v2/pkg/props" +) + +type checkbox struct { + pdf gofpdfwrapper.Fpdf + text *text + defaultColor *props.Color +} + +// NewCheckbox creates a new checkbox drawer. +func NewCheckbox(pdf gofpdfwrapper.Fpdf, txt *text) *checkbox { + return &checkbox{ + pdf: pdf, + text: txt, + defaultColor: &props.BlackColor, + } +} + +// Add draws a checkbox with label in the PDF. +func (c *checkbox) Add(label string, cell *entity.Cell, prop *props.Checkbox) { + // Get margins to calculate absolute positions + left, top, _, _ := c.pdf.GetMargins() + + // Calculate checkbox box position (left side of cell with padding) + boxX := left + cell.X + prop.Left + boxY := top + cell.Y + prop.Top + boxSize := prop.BoxSize + + // Set draw color for the checkbox + if prop.Color != nil { + c.pdf.SetDrawColor(prop.Color.Red, prop.Color.Green, prop.Color.Blue) + } else { + c.pdf.SetDrawColor(c.defaultColor.Red, c.defaultColor.Green, c.defaultColor.Blue) + } + + // Draw checkbox box (empty square) + c.pdf.SetLineWidth(0.3) // Thin line for checkbox border + c.pdf.Rect(boxX, boxY, boxSize, boxSize, "D") // "D" for draw (outline only) + + // If checked, draw checkmark + if prop.Checked { + // Draw checkmark as two lines forming a "✓" shape + c.pdf.SetLineWidth(0.5) // Slightly thicker for checkmark + + // Start coordinates for checkmark (relative to box) + padding := boxSize * 0.2 // 20% padding inside the box + + // First line: bottom-left to middle + x1 := boxX + padding + y1 := boxY + boxSize/2 + x2 := boxX + boxSize*0.4 + y2 := boxY + boxSize - padding + c.pdf.Line(x1, y1, x2, y2) + + // Second line: middle to top-right + x3 := x2 + y3 := y2 + x4 := boxX + boxSize - padding + y4 := boxY + padding + c.pdf.Line(x3, y3, x4, y4) + } + + // Reset draw color + c.pdf.SetDrawColor(c.defaultColor.Red, c.defaultColor.Green, c.defaultColor.Blue) + c.pdf.SetLineWidth(0.2) // Reset to default line width + + // Calculate text position (to the right of the checkbox with spacing) + spacing := 2.0 // Space between checkbox and label + textX := boxX + boxSize + spacing - left // Relative to cell + + // Create a modified cell for the text + textCell := &entity.Cell{ + X: textX, + Y: cell.Y, + Width: cell.Width - textX, + Height: cell.Height, + } + + // Create text properties from checkbox properties + textProp := &props.Text{ + Family: prop.Family, + Style: prop.Style, + Size: prop.Size, + Color: prop.Color, + Top: prop.Top, + Left: 0, // Already adjusted in textCell.X + Right: prop.Right, + Bottom: prop.Bottom, + } + + // Render the label + c.text.Add(label, textCell, textProp) +} diff --git a/internal/providers/gofpdf/provider.go b/internal/providers/gofpdf/provider.go index 8073abf6..3fecfaae 100644 --- a/internal/providers/gofpdf/provider.go +++ b/internal/providers/gofpdf/provider.go @@ -26,6 +26,7 @@ type provider struct { code core.Code image core.Image line core.Line + checkbox core.Checkbox cache cache.Cache cellWriter cellwriter.CellWriter cfg *entity.Config @@ -40,6 +41,7 @@ func New(dep *Dependencies) core.Provider { code: dep.Code, image: dep.Image, line: dep.Line, + checkbox: dep.Checkbox, cellWriter: dep.CellWriter, cfg: dep.Cfg, cache: dep.Cache, @@ -62,6 +64,10 @@ func (g *provider) AddLine(cell *entity.Cell, prop *props.Line) { g.line.Add(cell, prop) } +func (g *provider) AddCheckbox(label string, cell *entity.Cell, prop *props.Checkbox) { + g.checkbox.Add(label, cell, prop) +} + func (g *provider) AddMatrixCode(code string, cell *entity.Cell, prop *props.Rect) { img, err := g.loadCode(code, "matrix-code-", g.code.GenDataMatrix) if err != nil { diff --git a/mocks/Provider.go b/mocks/Provider.go index e86bebea..c756ec26 100644 --- a/mocks/Provider.go +++ b/mocks/Provider.go @@ -95,6 +95,41 @@ func (_c *Provider_AddBarCode_Call) RunAndReturn(run func(string, *entity.Cell, return _c } +// AddCheckbox provides a mock function with given fields: label, cell, prop +func (_m *Provider) AddCheckbox(label string, cell *entity.Cell, prop *props.Checkbox) { + _m.Called(label, cell, prop) +} + +// Provider_AddCheckbox_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddCheckbox' +type Provider_AddCheckbox_Call struct { + *mock.Call +} + +// AddCheckbox is a helper method to define mock.On call +// - label string +// - cell *entity.Cell +// - prop *props.Checkbox +func (_e *Provider_Expecter) AddCheckbox(label interface{}, cell interface{}, prop interface{}) *Provider_AddCheckbox_Call { + return &Provider_AddCheckbox_Call{Call: _e.mock.On("AddCheckbox", label, cell, prop)} +} + +func (_c *Provider_AddCheckbox_Call) Run(run func(label string, cell *entity.Cell, prop *props.Checkbox)) *Provider_AddCheckbox_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(*entity.Cell), args[2].(*props.Checkbox)) + }) + return _c +} + +func (_c *Provider_AddCheckbox_Call) Return() *Provider_AddCheckbox_Call { + _c.Call.Return() + return _c +} + +func (_c *Provider_AddCheckbox_Call) RunAndReturn(run func(string, *entity.Cell, *props.Checkbox)) *Provider_AddCheckbox_Call { + _c.Run(run) + return _c +} + // AddImageFromBytes provides a mock function with given fields: bytes, cell, prop, _a3 func (_m *Provider) AddImageFromBytes(bytes []byte, cell *entity.Cell, prop *props.Rect, _a3 extension.Type) { _m.Called(bytes, cell, prop, _a3) diff --git a/pkg/components/checkbox/checkbox.go b/pkg/components/checkbox/checkbox.go new file mode 100644 index 00000000..38be803d --- /dev/null +++ b/pkg/components/checkbox/checkbox.go @@ -0,0 +1,93 @@ +// Package checkbox implements creation of checkboxes. +package checkbox + +import ( + "github.com/johnfercher/go-tree/node" + + "github.com/johnfercher/maroto/v2/pkg/components/col" + "github.com/johnfercher/maroto/v2/pkg/components/row" + "github.com/johnfercher/maroto/v2/pkg/core" + "github.com/johnfercher/maroto/v2/pkg/core/entity" + "github.com/johnfercher/maroto/v2/pkg/props" +) + +type Checkbox struct { + label string + prop props.Checkbox + config *entity.Config +} + +// New is responsible to create an instance of a Checkbox. +func New(label string, ps ...props.Checkbox) core.Component { + checkboxProp := props.Checkbox{} + if len(ps) > 0 { + checkboxProp = ps[0] + } + + return &Checkbox{ + label: label, + prop: checkboxProp, + } +} + +// NewCol is responsible to create an instance of a Checkbox wrapped in a Col. +func NewCol(size int, label string, ps ...props.Checkbox) core.Col { + checkbox := New(label, ps...) + return col.New(size).Add(checkbox) +} + +// NewAutoRow is responsible for creating an instance of Checkbox grouped in a Row with automatic height. +func NewAutoRow(label string, ps ...props.Checkbox) core.Row { + c := New(label, ps...) + column := col.New().Add(c) + return row.New().Add(column) +} + +// NewRow is responsible to create an instance of a Checkbox wrapped in a Row. +func NewRow(height float64, label string, ps ...props.Checkbox) core.Row { + c := New(label, ps...) + column := col.New().Add(c) + return row.New(height).Add(column) +} + +// GetStructure returns the Structure of a Checkbox. +func (c *Checkbox) GetStructure() *node.Node[core.Structure] { + str := core.Structure{ + Type: "checkbox", + Value: c.label, + Details: c.prop.ToMap(), + } + + return node.New(str) +} + +// GetHeight returns the height that the checkbox will have in the PDF +func (c *Checkbox) GetHeight(provider core.Provider, cell *entity.Cell) float64 { + fontHeight := provider.GetFontHeight(&props.Font{ + Family: c.prop.Family, + Style: c.prop.Style, + Size: c.prop.Size, + Color: c.prop.Color, + }) + + // Height is the maximum of box size and font height, plus padding + height := c.prop.BoxSize + if fontHeight > height { + height = fontHeight + } + + return height + c.prop.Top + c.prop.Bottom +} + +// SetConfig sets the config. +func (c *Checkbox) SetConfig(config *entity.Config) { + c.config = config + if c.config != nil { + c.prop.MakeValid(c.config.DefaultFont) + } +} + +// Render renders a Checkbox into a PDF context. +func (c *Checkbox) Render(provider core.Provider, cell *entity.Cell) { + provider.AddCheckbox(c.label, cell, &c.prop) +} diff --git a/pkg/components/checkbox/checkbox_test.go b/pkg/components/checkbox/checkbox_test.go new file mode 100644 index 00000000..de3f1537 --- /dev/null +++ b/pkg/components/checkbox/checkbox_test.go @@ -0,0 +1,156 @@ +package checkbox_test + +import ( + "testing" + + "github.com/johnfercher/maroto/v2/mocks" + "github.com/johnfercher/maroto/v2/pkg/components/checkbox" + "github.com/johnfercher/maroto/v2/pkg/core/entity" + "github.com/johnfercher/maroto/v2/pkg/props" + "github.com/johnfercher/maroto/v2/pkg/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestNew(t *testing.T) { + t.Run("when prop is not sent, should use default", func(t *testing.T) { + // Act + sut := checkbox.New("Male") + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_default_prop.json") + }) + t.Run("when prop is sent, should use the provided", func(t *testing.T) { + // Arrange + prop := props.Checkbox{ + Checked: true, + BoxSize: 5.0, + } + + // Act + sut := checkbox.New("Male", prop) + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_custom_prop.json") + }) +} + +func TestNewCol(t *testing.T) { + t.Run("when prop is not sent, should use default", func(t *testing.T) { + // Act + sut := checkbox.NewCol(12, "Male") + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_col_default_prop.json") + }) + t.Run("when prop is sent, should use the provided", func(t *testing.T) { + // Arrange + prop := props.Checkbox{ + Checked: true, + BoxSize: 5.0, + } + + // Act + sut := checkbox.NewCol(12, "Male", prop) + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_col_custom_prop.json") + }) +} + +func TestNewRow(t *testing.T) { + t.Run("when prop is not sent, should use default", func(t *testing.T) { + // Act + sut := checkbox.NewRow(10, "Male") + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_row_default_prop.json") + }) + t.Run("when prop is sent, should use the provided", func(t *testing.T) { + // Arrange + prop := props.Checkbox{ + Checked: true, + BoxSize: 5.0, + } + + // Act + sut := checkbox.NewRow(10, "Male", prop) + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_row_custom_prop.json") + }) +} + +func TestNewAutoRow(t *testing.T) { + t.Run("when prop is not sent, should use default", func(t *testing.T) { + // Act + sut := checkbox.NewAutoRow("Male") + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_auto_row_default_prop.json") + }) + t.Run("when prop is sent, should use the provided", func(t *testing.T) { + // Arrange + prop := props.Checkbox{ + Checked: true, + BoxSize: 5.0, + } + + // Act + sut := checkbox.NewAutoRow("Male", prop) + + // Assert + test.New(t).Assert(sut.GetStructure()).Equals("components/checkboxes/new_checkbox_auto_row_custom_prop.json") + }) +} + +func TestCheckbox_Render(t *testing.T) { + t.Run("should call provider.AddCheckbox with correct arguments", func(t *testing.T) { + // Arrange + prop := props.Checkbox{Checked: true} + sut := checkbox.New("Male", prop) + provider := &mocks.Provider{} + cell := &entity.Cell{} + + provider.On("AddCheckbox", "Male", cell, mock.Anything).Return() + + // Act + sut.Render(provider, cell) + + // Assert + provider.AssertNumberOfCalls(t, "AddCheckbox", 1) + }) +} + +func TestCheckbox_SetConfig(t *testing.T) { + t.Run("should call correctly", func(t *testing.T) { + // Arrange + prop := props.Checkbox{ + Checked: true, + BoxSize: 5.0, + } + sut := checkbox.New("Male", prop) + + // Act + sut.SetConfig(nil) + }) +} + +func TestCheckbox_GetHeight(t *testing.T) { + t.Run("should return correct height based on provider font height", func(t *testing.T) { + // Arrange + prop := props.Checkbox{BoxSize: 5.0, Top: 1, Bottom: 1} + sut := checkbox.New("Male", prop) + provider := &mocks.Provider{} + cell := &entity.Cell{} + + // Return a font height larger than box size to test max logic + provider.On("GetFontHeight", mock.Anything).Return(10.0) + + // Act + height := sut.GetHeight(provider, cell) + + // Assert + assert.Equal(t, 12.0, height) // 10.0 + 1 + 1 + }) +} diff --git a/pkg/components/checkbox/example_test.go b/pkg/components/checkbox/example_test.go new file mode 100644 index 00000000..11013192 --- /dev/null +++ b/pkg/components/checkbox/example_test.go @@ -0,0 +1,58 @@ +package checkbox_test + +import ( + "github.com/johnfercher/maroto/v2" + "github.com/johnfercher/maroto/v2/pkg/components/checkbox" + "github.com/johnfercher/maroto/v2/pkg/components/col" + "github.com/johnfercher/maroto/v2/pkg/props" +) + +// ExampleNew demonstrates how to create a checkbox component. +func ExampleNew() { + m := maroto.New() + + checkedProps := props.Checkbox{Checked: true} + uncheckedProps := props.Checkbox{Checked: false} + + maleCheckbox := checkbox.New("Male", checkedProps) + femaleCheckbox := checkbox.New("Female", uncheckedProps) + + maleCol := col.New(12).Add(maleCheckbox) + femaleCol := col.New(12).Add(femaleCheckbox) + + m.AddRow(7, maleCol) + m.AddRow(7, femaleCol) + + // generate document +} + +// ExampleNewCol demonstrates how to create a checkbox component wrapped into a column. +func ExampleNewCol() { + m := maroto.New() + + checkedProps := props.Checkbox{Checked: true} + uncheckedProps := props.Checkbox{Checked: false} + + maleCheckboxCol := checkbox.NewCol(12, "Male", checkedProps) + femaleCheckboxCol := checkbox.NewCol(12, "Female", uncheckedProps) + + m.AddRow(7, maleCheckboxCol) + m.AddRow(7, femaleCheckboxCol) + + // generate document +} + +// ExampleNewRow demonstrates how to create a checkbox component wrapped into a row. +func ExampleNewRow() { + m := maroto.New() + + checkedProps := props.Checkbox{Checked: true} + uncheckedProps := props.Checkbox{Checked: false} + + maleCheckboxRow := checkbox.NewRow(7, "Male", checkedProps) + femaleCheckboxRow := checkbox.NewRow(7, "Female", uncheckedProps) + + m.AddRows(maleCheckboxRow, femaleCheckboxRow) + + // generate document +} diff --git a/pkg/core/components.go b/pkg/core/components.go index f96ef65d..60d37b30 100644 --- a/pkg/core/components.go +++ b/pkg/core/components.go @@ -52,3 +52,8 @@ type Font interface { SetColor(color *props.Color) GetColor() *props.Color } + +// Checkbox is the abstraction which writes checkboxes on pdf. +type Checkbox interface { + Add(label string, cell *entity.Cell, prop *props.Checkbox) +} diff --git a/pkg/core/provider.go b/pkg/core/provider.go index 72ce633b..5e36d7a9 100644 --- a/pkg/core/provider.go +++ b/pkg/core/provider.go @@ -27,6 +27,7 @@ type Provider interface { AddImageFromFile(value string, cell *entity.Cell, prop *props.Rect) AddImageFromBytes(bytes []byte, cell *entity.Cell, prop *props.Rect, extension extension.Type) AddBackgroundImageFromBytes(bytes []byte, cell *entity.Cell, prop *props.Rect, extension extension.Type) + AddCheckbox(label string, cell *entity.Cell, prop *props.Checkbox) // General GenerateBytes() ([]byte, error) diff --git a/pkg/props/checkbox.go b/pkg/props/checkbox.go new file mode 100644 index 00000000..da32218d --- /dev/null +++ b/pkg/props/checkbox.go @@ -0,0 +1,87 @@ +package props + +// Checkbox represents properties from a checkbox inside a cell. +type Checkbox struct { + Font + // Checked indicates whether the checkbox is checked. + Checked bool + // BoxSize is the size of the checkbox box in PDF units. + BoxSize float64 + // Left is the space between the left cell boundary to the checkbox. + Left float64 + // Top is the space between the upper cell limit to the checkbox. + Top float64 + // Right is the space between the checkbox to the right cell boundary. + Right float64 + // Bottom is the space between the checkbox to the bottom cell boundary. + Bottom float64 +} + +// ToMap returns a map representation of Checkbox properties. +func (c *Checkbox) ToMap() map[string]interface{} { + m := make(map[string]interface{}) + + if c.Checked { + m["prop_checked"] = c.Checked + } + + if c.BoxSize != 0 { + m["prop_box_size"] = c.BoxSize + } + + if c.Left != 0 { + m["prop_left"] = c.Left + } + + if c.Top != 0 { + m["prop_top"] = c.Top + } + + if c.Right != 0 { + m["prop_right"] = c.Right + } + + if c.Bottom != 0 { + m["prop_bottom"] = c.Bottom + } + + m = c.Font.AppendMap(m) + + return m +} + +// MakeValid ensures Checkbox properties are valid and sets defaults. +func (c *Checkbox) MakeValid(defaultFont *Font) { + const ( + defaultBoxSize = 4.0 + minBoxSize = 1.0 + maxBoxSize = 20.0 + minValue = 0.0 + ) + + if c.BoxSize < minBoxSize || c.BoxSize > maxBoxSize { + c.BoxSize = defaultBoxSize + } + + if c.Left < minValue { + c.Left = minValue + } + + if c.Top < minValue { + c.Top = minValue + } + + if c.Right < minValue { + c.Right = minValue + } + + if c.Bottom < minValue { + c.Bottom = minValue + } + + if defaultFont != nil { + c.Font.MakeValid(defaultFont.Family) + } else { + c.Font.MakeValid("") + } +} diff --git a/pkg/props/checkbox_test.go b/pkg/props/checkbox_test.go new file mode 100644 index 00000000..31da4368 --- /dev/null +++ b/pkg/props/checkbox_test.go @@ -0,0 +1,130 @@ +package props_test + +import ( + "testing" + + "github.com/johnfercher/maroto/v2/pkg/props" + "github.com/stretchr/testify/assert" +) + +func TestCheckbox_ToMap(t *testing.T) { + t.Run("when all properties are set, should return complete map", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{ + Checked: true, + BoxSize: 5.0, + Left: 2.0, + Top: 3.0, + Right: 4.0, + Bottom: 5.0, + } + + // Act + m := sut.ToMap() + + // Assert + assert.Equal(t, true, m["prop_checked"]) + assert.Equal(t, 5.0, m["prop_box_size"]) + assert.Equal(t, 2.0, m["prop_left"]) + assert.Equal(t, 3.0, m["prop_top"]) + assert.Equal(t, 4.0, m["prop_right"]) + assert.Equal(t, 5.0, m["prop_bottom"]) + }) + + t.Run("when properties are default, should return minimal map", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{} + + // Act + m := sut.ToMap() + + // Assert + _, hasChecked := m["prop_checked"] + assert.False(t, hasChecked) + _, hasBoxSize := m["prop_box_size"] + assert.False(t, hasBoxSize) + }) +} + +func TestCheckbox_MakeValid(t *testing.T) { + t.Run("when box size is too small, should set to default", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{ + BoxSize: 0.5, + } + + // Act + sut.MakeValid(nil) + + // Assert + assert.Equal(t, 4.0, sut.BoxSize) + }) + + t.Run("when box size is too large, should set to default", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{ + BoxSize: 25.0, + } + + // Act + sut.MakeValid(nil) + + // Assert + assert.Equal(t, 4.0, sut.BoxSize) + }) + + t.Run("when box size is valid, should keep it", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{ + BoxSize: 6.0, + } + + // Act + sut.MakeValid(nil) + + // Assert + assert.Equal(t, 6.0, sut.BoxSize) + }) + + t.Run("when negative padding, should set to zero", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{ + Left: -1.0, + Top: -2.0, + Right: -3.0, + Bottom: -4.0, + } + + // Act + sut.MakeValid(nil) + + // Assert + assert.Equal(t, 0.0, sut.Left) + assert.Equal(t, 0.0, sut.Top) + assert.Equal(t, 0.0, sut.Right) + assert.Equal(t, 0.0, sut.Bottom) + }) + + t.Run("when all properties are valid, should keep them", func(t *testing.T) { + // Arrange + sut := &props.Checkbox{ + Checked: true, + BoxSize: 5.0, + Left: 1.0, + Top: 2.0, + Right: 3.0, + Bottom: 4.0, + } + + // Act + sut.MakeValid(nil) + + // Assert + assert.Equal(t, true, sut.Checked) + assert.Equal(t, 5.0, sut.BoxSize) + assert.Equal(t, 1.0, sut.Left) + assert.Equal(t, 2.0, sut.Top) + assert.Equal(t, 3.0, sut.Right) + assert.Equal(t, 4.0, sut.Bottom) + }) +} diff --git a/test/maroto/components/checkboxes/new_checkbox_auto_row_custom_prop.json b/test/maroto/components/checkboxes/new_checkbox_auto_row_custom_prop.json new file mode 100644 index 00000000..9ef368be --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_auto_row_custom_prop.json @@ -0,0 +1,23 @@ +{ + "value": 0, + "type": "row", + "nodes": [ + { + "value": 0, + "type": "col", + "details": { + "is_max": true + }, + "nodes": [ + { + "value": "Male", + "type": "checkbox", + "details": { + "prop_box_size": 5, + "prop_checked": true + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/maroto/components/checkboxes/new_checkbox_auto_row_default_prop.json b/test/maroto/components/checkboxes/new_checkbox_auto_row_default_prop.json new file mode 100644 index 00000000..39a72b79 --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_auto_row_default_prop.json @@ -0,0 +1,19 @@ +{ + "value": 0, + "type": "row", + "nodes": [ + { + "value": 0, + "type": "col", + "details": { + "is_max": true + }, + "nodes": [ + { + "value": "Male", + "type": "checkbox" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/maroto/components/checkboxes/new_checkbox_col_custom_prop.json b/test/maroto/components/checkboxes/new_checkbox_col_custom_prop.json new file mode 100644 index 00000000..4aa2030c --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_col_custom_prop.json @@ -0,0 +1,14 @@ +{ + "value": 12, + "type": "col", + "nodes": [ + { + "value": "Male", + "type": "checkbox", + "details": { + "prop_box_size": 5, + "prop_checked": true + } + } + ] +} \ No newline at end of file diff --git a/test/maroto/components/checkboxes/new_checkbox_col_default_prop.json b/test/maroto/components/checkboxes/new_checkbox_col_default_prop.json new file mode 100644 index 00000000..ed7f8ae5 --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_col_default_prop.json @@ -0,0 +1,10 @@ +{ + "value": 12, + "type": "col", + "nodes": [ + { + "value": "Male", + "type": "checkbox" + } + ] +} \ No newline at end of file diff --git a/test/maroto/components/checkboxes/new_checkbox_custom_prop.json b/test/maroto/components/checkboxes/new_checkbox_custom_prop.json new file mode 100644 index 00000000..6741c963 --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_custom_prop.json @@ -0,0 +1,8 @@ +{ + "value": "Male", + "type": "checkbox", + "details": { + "prop_box_size": 5, + "prop_checked": true + } +} \ No newline at end of file diff --git a/test/maroto/components/checkboxes/new_checkbox_default_prop.json b/test/maroto/components/checkboxes/new_checkbox_default_prop.json new file mode 100644 index 00000000..10188654 --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_default_prop.json @@ -0,0 +1 @@ +{"value":"Male","type":"checkbox"} diff --git a/test/maroto/components/checkboxes/new_checkbox_row_custom_prop.json b/test/maroto/components/checkboxes/new_checkbox_row_custom_prop.json new file mode 100644 index 00000000..f0ba3eb0 --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_row_custom_prop.json @@ -0,0 +1,23 @@ +{ + "value": 10, + "type": "row", + "nodes": [ + { + "value": 0, + "type": "col", + "details": { + "is_max": true + }, + "nodes": [ + { + "value": "Male", + "type": "checkbox", + "details": { + "prop_box_size": 5, + "prop_checked": true + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/maroto/components/checkboxes/new_checkbox_row_default_prop.json b/test/maroto/components/checkboxes/new_checkbox_row_default_prop.json new file mode 100644 index 00000000..a78d4576 --- /dev/null +++ b/test/maroto/components/checkboxes/new_checkbox_row_default_prop.json @@ -0,0 +1,19 @@ +{ + "value": 10, + "type": "row", + "nodes": [ + { + "value": 0, + "type": "col", + "details": { + "is_max": true + }, + "nodes": [ + { + "value": "Male", + "type": "checkbox" + } + ] + } + ] +} \ No newline at end of file