Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions docs/assets/examples/checkbox/v2/main.go
Original file line number Diff line number Diff line change
@@ -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
}
15 changes: 15 additions & 0 deletions docs/assets/examples/checkbox/v2/main_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
Binary file added docs/assets/pdf/checkboxv2.pdf
Binary file not shown.
3 changes: 3 additions & 0 deletions docs/assets/text/checkboxv2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generate -> avg: 1.37ms, executions: [1.37ms]
add_row -> avg: 15709.00ns, executions: [15.71μs]
file_size -> 2.84Kb
22 changes: 22 additions & 0 deletions docs/v2/features/checkbox.md
Original file line number Diff line number Diff line change
@@ -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')
3 changes: 3 additions & 0 deletions internal/providers/gofpdf/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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,
Expand Down
98 changes: 98 additions & 0 deletions internal/providers/gofpdf/checkbox.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 6 additions & 0 deletions internal/providers/gofpdf/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions mocks/Provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 93 additions & 0 deletions pkg/components/checkbox/checkbox.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading
Loading