diff --git a/internal/examples/pdfs/certificate.pdf b/internal/examples/pdfs/certificate.pdf index 9f45eab0..e174b054 100644 Binary files a/internal/examples/pdfs/certificate.pdf and b/internal/examples/pdfs/certificate.pdf differ diff --git a/internal/examples/pdfs/sample1.pdf b/internal/examples/pdfs/sample1.pdf index 8a2a309e..c6dbf550 100644 Binary files a/internal/examples/pdfs/sample1.pdf and b/internal/examples/pdfs/sample1.pdf differ diff --git a/internal/examples/pdfs/zpl.pdf b/internal/examples/pdfs/zpl.pdf index b0e2828e..551db543 100644 Binary files a/internal/examples/pdfs/zpl.pdf and b/internal/examples/pdfs/zpl.pdf differ diff --git a/internal/examples/sample1/main.go b/internal/examples/sample1/main.go index 5ff7812b..53b89641 100644 --- a/internal/examples/sample1/main.go +++ b/internal/examples/sample1/main.go @@ -3,6 +3,7 @@ package main import ( "encoding/base64" "fmt" + "github.com/johnfercher/maroto/pkg/color" "github.com/johnfercher/maroto/pkg/consts" "github.com/johnfercher/maroto/pkg/pdf" "github.com/johnfercher/maroto/pkg/props" @@ -28,7 +29,6 @@ func main() { headerMedium, mediumContent := getMediumContent() m.RegisterHeader(func() { - m.Row(20, func() { m.Col(func() { m.Base64Image(base64, consts.Jpg, props.Rect{ @@ -144,7 +144,11 @@ func main() { }) m.TableList(headerSmall, smallContent, props.TableList{ - Line: true, + AlternatedBackground: &color.Color{ + Red: 200, + Green: 200, + Blue: 200, + }, }) m.Row(15, func() { @@ -158,6 +162,7 @@ func main() { m.TableList(headerMedium, mediumContent, props.TableList{ Align: consts.Center, + Line: true, HeaderProp: props.Font{ Family: consts.Courier, Style: consts.BoldItalic, @@ -204,22 +209,6 @@ func getSmallContent() ([]string, [][]string) { contents = append(contents, []string{"Natal", "Santo André", "", "R$ 198,00"}) contents = append(contents, []string{"Rio Grande do Norte", "Sorocaba", "", "R$ 42,00"}) contents = append(contents, []string{"Campinas", "Recife", "", "R$ 58,00"}) - contents = append(contents, []string{"São Vicente", "Juiz de Fora", "", "R$ 39,00"}) - contents = append(contents, []string{"Taubaté", "Rio de Janeiro", "", "R$ 77,00"}) - contents = append(contents, []string{"Suzano", "Petrópolis", "", "R$ 64,00"}) - contents = append(contents, []string{"Jundiaí", "Florianópolis", "", "R$ 20,00"}) - contents = append(contents, []string{"Natal", "Jundiaí", "", "R$ 18,00"}) - contents = append(contents, []string{"Niterói", "Itapevi", "", "R$ 24,00"}) - contents = append(contents, []string{"Jundiaí", "Florianópolis", "", "R$ 23,00"}) - contents = append(contents, []string{"Natal", "Jundiaí", "", "R$ 11,00"}) - contents = append(contents, []string{"Niterói", "Itapevi", "", "R$ 28,00"}) - contents = append(contents, []string{"São Paulo", "Rio de Janeiro", "", "R$ 19,00"}) - contents = append(contents, []string{"São Carlos", "Petrópolis", "", "R$ 23,00"}) - contents = append(contents, []string{"Florianópolis", "Osasco", "", "R$ 21,00"}) - contents = append(contents, []string{"Osasco", "São Paulo", "", "R$ 6,00"}) - contents = append(contents, []string{"Congonhas", "Fortaleza", "", "R$ 109,00"}) - contents = append(contents, []string{"Natal", "Santo André", "", "R$ 244,00"}) - contents = append(contents, []string{"São Carlos", "Petrópolis", "", "R$ 34,00"}) contents = append(contents, []string{"Florianópolis", "Osasco", "", "R$ 21,00"}) return header, contents diff --git a/internal/mocks/maroto.go b/internal/mocks/maroto.go index ca44edf3..b4c9a931 100644 --- a/internal/mocks/maroto.go +++ b/internal/mocks/maroto.go @@ -3,6 +3,7 @@ package mocks import bytes "bytes" +import color "github.com/johnfercher/maroto/pkg/color" import consts "github.com/johnfercher/maroto/pkg/consts" import mock "github.com/stretchr/testify/mock" @@ -133,6 +134,41 @@ func (_m *Maroto) GetCurrentPage() int { return r0 } +// GetPageMargins provides a mock function with given fields: +func (_m *Maroto) GetPageMargins() (float64, float64, float64, float64) { + ret := _m.Called() + + var r0 float64 + if rf, ok := ret.Get(0).(func() float64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(float64) + } + + var r1 float64 + if rf, ok := ret.Get(1).(func() float64); ok { + r1 = rf() + } else { + r1 = ret.Get(1).(float64) + } + + var r2 float64 + if rf, ok := ret.Get(2).(func() float64); ok { + r2 = rf() + } else { + r2 = ret.Get(2).(float64) + } + + var r3 float64 + if rf, ok := ret.Get(3).(func() float64); ok { + r3 = rf() + } else { + r3 = ret.Get(3).(float64) + } + + return r0, r1, r2, r3 +} + // GetPageSize provides a mock function with given fields: func (_m *Maroto) GetPageSize() (float64, float64) { ret := _m.Called() @@ -221,11 +257,21 @@ func (_m *Maroto) Row(height float64, closure func()) { _m.Called(height, closure) } +// SetBackgroundColor provides a mock function with given fields: _a0 +func (_m *Maroto) SetBackgroundColor(_a0 color.Color) { + _m.Called(_a0) +} + // SetBorder provides a mock function with given fields: on func (_m *Maroto) SetBorder(on bool) { _m.Called(on) } +// SetPageMargins provides a mock function with given fields: left, top, right +func (_m *Maroto) SetPageMargins(left float64, top float64, right float64) { + _m.Called(left, top, right) +} + // Signature provides a mock function with given fields: label, prop func (_m *Maroto) Signature(label string, prop ...props.Font) { _va := make([]interface{}, len(prop)) diff --git a/internal/tablelist.go b/internal/tablelist.go index c6d1cb93..39add10f 100644 --- a/internal/tablelist.go +++ b/internal/tablelist.go @@ -1,6 +1,7 @@ package internal import ( + "github.com/johnfercher/maroto/pkg/color" "github.com/johnfercher/maroto/pkg/props" ) @@ -12,6 +13,7 @@ type MarotoGridPart interface { ColSpace() // Helpers + SetBackgroundColor(color color.Color) GetCurrentOffset() float64 // Outside Col/Row Components @@ -55,6 +57,7 @@ func (s *tableList) Create(header []string, contents [][]string, prop ...props.T } tableProp := props.TableList{} + if len(prop) > 0 { tableProp = prop[0] } @@ -96,10 +99,14 @@ func (s *tableList) Create(header []string, contents [][]string, prop ...props.T contentMarginTop := 2.0 // Draw contents - for _, content := range contents { + for index, content := range contents { contentTextProp := tableProp.ContentProp.ToTextProp(tableProp.Align, 0.0, false, 1.0) contentHeight := s.calcLinesHeight(content, contentTextProp, qtdCols) + if tableProp.AlternatedBackground != nil && index%2 == 0 { + s.pdf.SetBackgroundColor(*tableProp.AlternatedBackground) + } + s.pdf.Row(contentHeight, func() { for j, c := range content { cs := c @@ -113,6 +120,10 @@ func (s *tableList) Create(header []string, contents [][]string, prop ...props.T } }) + if tableProp.AlternatedBackground != nil && index%2 == 0 { + s.pdf.SetBackgroundColor(color.NewWhite()) + } + if tableProp.Line { s.pdf.Line(1.0) } diff --git a/internal/tablelist_test.go b/internal/tablelist_test.go index cd49ffc6..a2c91f89 100644 --- a/internal/tablelist_test.go +++ b/internal/tablelist_test.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/johnfercher/maroto/internal" "github.com/johnfercher/maroto/internal/mocks" + "github.com/johnfercher/maroto/pkg/color" "github.com/johnfercher/maroto/pkg/consts" "github.com/johnfercher/maroto/pkg/props" "github.com/stretchr/testify/assert" @@ -92,6 +93,7 @@ func TestTableList_Create_Happy(t *testing.T) { marotoGrid := &mocks.Maroto{} marotoGrid.On("Row", mock.Anything, mock.Anything).Return(nil) marotoGrid.On("Line", mock.Anything).Return(nil) + marotoGrid.On("SetBackgroundColor", mock.Anything).Return(nil) sut := internal.NewTableList(text, font) sut.BindGrid(marotoGrid) @@ -113,6 +115,52 @@ func TestTableList_Create_Happy(t *testing.T) { marotoGrid.AssertCalled(t, "Row", mock.Anything, mock.Anything) marotoGrid.AssertNumberOfCalls(t, "Row", 22) marotoGrid.AssertNumberOfCalls(t, "Line", 20) + marotoGrid.AssertNotCalled(t, "SetBackgroundColor") +} + +func TestTableList_Create_HappyWithBackgroundColor(t *testing.T) { + // Arrange + text := &mocks.Text{} + text.On("GetLinesQuantity", mock.Anything, mock.Anything, mock.Anything).Return(1) + + font := &mocks.Font{} + font.On("GetFont").Return(consts.Arial, consts.Bold, 1.0) + font.On("GetScaleFactor").Return(1.5) + + marotoGrid := &mocks.Maroto{} + marotoGrid.On("Row", mock.Anything, mock.Anything).Return(nil) + marotoGrid.On("Line", mock.Anything).Return(nil) + marotoGrid.On("SetBackgroundColor", mock.Anything).Return(nil) + + sut := internal.NewTableList(text, font) + sut.BindGrid(marotoGrid) + + headers, contents := getContents() + color := color.Color{ + Red: 200, + Green: 200, + Blue: 200, + } + + // Act + sut.Create(headers, contents, props.TableList{ + AlternatedBackground: &color, + }) + + // Assert + text.AssertNotCalled(t, "GetLinesQuantity") + text.AssertNumberOfCalls(t, "GetLinesQuantity", 84) + + font.AssertCalled(t, "GetFont") + font.AssertNumberOfCalls(t, "GetFont", 21) + + marotoGrid.AssertCalled(t, "Row", mock.Anything, mock.Anything) + marotoGrid.AssertNumberOfCalls(t, "Row", 22) + + marotoGrid.AssertNotCalled(t, "Line") + + marotoGrid.AssertCalled(t, "SetBackgroundColor", color) + marotoGrid.AssertNumberOfCalls(t, "SetBackgroundColor", 20) } func TestTableList_Create_Happy_Without_Line(t *testing.T) { diff --git a/pkg/color/color.go b/pkg/color/color.go new file mode 100644 index 00000000..28bb9703 --- /dev/null +++ b/pkg/color/color.go @@ -0,0 +1,28 @@ +package color + +// Color represents a color in the RGB (Red, Green, Blue) space, +// is possible mix values, when all values are 0 the result color is black +// when all values are 255 the result color is white +type Color struct { + // Red is the amount of red + Red int + // Green is the amount of red + Green int + // Blue is the amount of red + Blue int +} + +// IsWhite from Color will return true if all components of color +// are in the maximum value +func (s *Color) IsWhite() bool { + return s.Red == 255 && s.Green == 255 && s.Blue == 255 +} + +// NewWhite return a Color with all components (red, green and blue) as 255 +func NewWhite() Color { + return Color{ + Red: 255, + Green: 255, + Blue: 255, + } +} diff --git a/pkg/color/color_test.go b/pkg/color/color_test.go new file mode 100644 index 00000000..61c50319 --- /dev/null +++ b/pkg/color/color_test.go @@ -0,0 +1,25 @@ +package color_test + +import ( + "github.com/johnfercher/maroto/pkg/color" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewWhite(t *testing.T) { + // Act + white := color.NewWhite() + + // Assert + assert.Equal(t, 255, white.Red) + assert.Equal(t, 255, white.Green) + assert.Equal(t, 255, white.Blue) +} + +func TestColor_IsWhite(t *testing.T) { + // Act + white := color.NewWhite() + + // Assert + assert.True(t, white.IsWhite()) +} diff --git a/pkg/pdf/example_test.go b/pkg/pdf/example_test.go index 372fdb04..a67f7d17 100644 --- a/pkg/pdf/example_test.go +++ b/pkg/pdf/example_test.go @@ -1,12 +1,24 @@ package pdf_test import ( + "fmt" + "github.com/johnfercher/maroto/pkg/color" "github.com/johnfercher/maroto/pkg/consts" "github.com/johnfercher/maroto/pkg/pdf" "github.com/johnfercher/maroto/pkg/props" "time" ) +// ExampleNewMaroto demonstratos how to create maroto +func ExampleNewMaroto() { + m := pdf.NewMaroto(consts.Portrait, consts.A4) + + // Do things + m.GetPageMargins() + + // Do more things and save... +} + // ExamplePdfMaroto_Line demonstrates how to draw a line // separator. func ExamplePdfMaroto_Line() { @@ -87,6 +99,36 @@ func ExamplePdfMaroto_SetBorder() { // Do more things and save... } +// ExamplePdfMaroto_SetBackgroundColor demonstrates how +// to use the SetBackgroundColor method. +func ExamplePdfMaroto_SetBackgroundColor() { + m := pdf.NewMaroto(consts.Portrait, consts.A4) + + m.SetBackgroundColor(color.Color{ + Red: 100, + Green: 20, + Blue: 30, + }) + + // This Row will be filled with the color + m.Row(20, func() { + m.Col(func() { + // Add components + }) + }) + + m.SetBackgroundColor(color.NewWhite()) + + // This Row will not be filled with the color + m.Row(20, func() { + m.Col(func() { + // Add components + }) + }) + + // Do more things and save... +} + // ExamplePdfMaroto_GetBorder demonstrates how to // obtain the actual borders status func ExamplePdfMaroto_GetBorder() { @@ -117,11 +159,13 @@ func ExamplePdfMaroto_Text() { m.Row(rowHeight, func() { m.Col(func() { m.Text("TextContent", props.Text{ - Size: 12.0, - Style: consts.BoldItalic, - Family: consts.Courier, - Align: consts.Center, - Top: 1.0, + Size: 12.0, + Style: consts.BoldItalic, + Family: consts.Courier, + Align: consts.Center, + Top: 1.0, + Extrapolate: false, + VerticalPadding: 1.0, }) }) }) @@ -167,7 +211,24 @@ func ExamplePdfMaroto_TableList() { // 2 Rows of contents // Each row have 2 columns m.TableList(headers, contents, props.TableList{ - Line: false, + HeaderProp: props.Font{ + Family: consts.Arial, + Style: consts.Bold, + Size: 11.0, + }, + ContentProp: props.Font{ + Family: consts.Courier, + Style: consts.Normal, + Size: 10.0, + }, + Align: consts.Center, + AlternatedBackground: &color.Color{ + Red: 100, + Green: 20, + Blue: 255, + }, + HeaderContentSpace: 10.0, + Line: false, }) // Do more things and save... @@ -365,5 +426,70 @@ func ExamplePdfMaroto_RegisterHeader() { // ExamplePdfMaroto_SetPageMargins demonstrates how to set custom page margins. func ExamplePdfMaroto_SetPageMargins() { m := pdf.NewMaroto(consts.Portrait, consts.A4) + m.SetPageMargins(10, 60, 10) + + // Do more things or not and save... +} + +// ExamplePdfMaroto_GetPageSize demonstrates how to obtain the current page size (width and height) +func ExamplePdfMaroto_GetPageSize() { + m := pdf.NewMaroto(consts.Portrait, consts.A4) + + // Get + width, height := m.GetPageSize() + fmt.Println(width) + fmt.Println(height) + + // Do more things and save... +} + +// ExamplePdfMaroto_GetCurrentPage demonstrates how to obtain the current page index +func ExamplePdfMaroto_GetCurrentPage() { + m := pdf.NewMaroto(consts.Portrait, consts.A4) + + // Index here will be 0 + _ = m.GetCurrentPage() + + // Add Rows, Cols and Components + + // Index here will not be 0 + _ = m.GetCurrentPage() + + // Do more things and save... +} + +// ExamplePdfMaroto_GetCurrentOffset demonstrates how to obtain the current write offset +// i.e the height of cursor adding content in the pdf +func ExamplePdfMaroto_GetCurrentOffset() { + m := pdf.NewMaroto(consts.Portrait, consts.A4) + + // Offset here will be 0 + _ = m.GetCurrentOffset() + + // Add Rows, Cols and Components until maroto add a new page + + // Offset here will not be 0 + _ = m.GetCurrentOffset() + + // Add Rows, Cols and Components to maroto add a new page + + // Offset here will be 0 + _ = m.GetCurrentOffset() + + // Do more things and save... +} + +// ExamplePdfMaroto_GetPageMargins demonstrates how to obtain the current page margins +func ExamplePdfMaroto_GetPageMargins() { + m := pdf.NewMaroto(consts.Portrait, consts.A4) + + // Get + left, top, right, bottom := m.GetPageMargins() + fmt.Println(left) + fmt.Println(top) + fmt.Println(right) + fmt.Println(bottom) + + // Do more things and save... } diff --git a/pkg/pdf/pdf.go b/pkg/pdf/pdf.go index b8fadc3a..a7b03383 100644 --- a/pkg/pdf/pdf.go +++ b/pkg/pdf/pdf.go @@ -2,6 +2,7 @@ package pdf import ( "bytes" + "github.com/johnfercher/maroto/pkg/color" "github.com/johnfercher/maroto/internal" "github.com/johnfercher/maroto/pkg/consts" @@ -23,6 +24,7 @@ type Maroto interface { // Helpers SetBorder(on bool) + SetBackgroundColor(color color.Color) GetBorder() bool GetPageSize() (float64, float64) GetCurrentPage() int @@ -61,13 +63,14 @@ type PdfMaroto struct { offsetY float64 rowHeight float64 rowColCount float64 + backgroundColor color.Color colsClosures []func() headerClosure func() footerClosure func() footerHeight float64 headerFooterContextActive bool calculationMode bool - DebugMode bool + debugMode bool orientation consts.Orientation pageSize consts.PageSize } @@ -102,6 +105,7 @@ func NewMaroto(orientation consts.Orientation, pageSize consts.PageSize) Maroto pageSize: pageSize, orientation: orientation, calculationMode: false, + backgroundColor: color.NewWhite(), } maroto.TableListHelper.BindGrid(maroto) @@ -109,7 +113,7 @@ func NewMaroto(orientation consts.Orientation, pageSize consts.PageSize) Maroto maroto.Font.SetFamily(consts.Arial) maroto.Font.SetStyle(consts.Bold) maroto.Font.SetSize(16) - maroto.DebugMode = false + maroto.debugMode = false maroto.Pdf.AddPage() @@ -185,12 +189,19 @@ func (s *PdfMaroto) TableList(header []string, contents [][]string, prop ...prop // SetBorder enable the draw of lines in every cell. // Draw borders in all columns created. func (s *PdfMaroto) SetBorder(on bool) { - s.DebugMode = on + s.debugMode = on +} + +// SetBackgroundColor define the background color of the PDF. +// This method can be used to toggle background from rows +func (s *PdfMaroto) SetBackgroundColor(color color.Color) { + s.backgroundColor = color + s.Pdf.SetFillColor(s.backgroundColor.Red, s.backgroundColor.Green, s.backgroundColor.Blue) } // GetBorder return the actual border value. func (s *PdfMaroto) GetBorder() bool { - return s.DebugMode + return s.debugMode } // GetPageSize return the actual page size @@ -398,11 +409,11 @@ func (s *PdfMaroto) QrCode(code string, prop ...props.Rect) { func (s *PdfMaroto) createColSpace(actualWidthPerCol float64) { border := "" - if s.DebugMode { + if s.debugMode { border = "1" } - s.Pdf.CellFormat(actualWidthPerCol, s.rowHeight, "", border, 0.0, "C", false, 0.0, "") + s.Pdf.CellFormat(actualWidthPerCol, s.rowHeight, "", border, 0.0, "C", !s.backgroundColor.IsWhite(), 0.0, "") } func (s *PdfMaroto) drawLastFooter() { diff --git a/pkg/pdf/pdf_test.go b/pkg/pdf/pdf_test.go index 3d12034b..b9e0d0c5 100644 --- a/pkg/pdf/pdf_test.go +++ b/pkg/pdf/pdf_test.go @@ -3,6 +3,7 @@ package pdf_test import ( "bytes" "fmt" + "github.com/johnfercher/maroto/pkg/color" "testing" "github.com/johnfercher/maroto/internal/mocks" @@ -1528,6 +1529,8 @@ func newMarotoTest(fpdf *mocks.Pdf, math *mocks.Math, font *mocks.Font, text *mo TableListHelper: tableList, } + m.SetBackgroundColor(color.NewWhite()) + return m } @@ -1539,6 +1542,7 @@ func basePdfTest(left, top, right float64) *mocks.Pdf { pdf.On("Ln", mock.Anything) pdf.On("GetFontSize").Return(1.0, 1.0) pdf.On("SetMargins", mock.AnythingOfType("float64"), mock.AnythingOfType("float64"), mock.AnythingOfType("float64")) + pdf.On("SetFillColor", mock.Anything, mock.Anything, mock.Anything) return pdf } @@ -1882,3 +1886,17 @@ func TestPdfMaroto_SetPageMargins(t *testing.T) { c.assert(t, left, top, right) } } + +func TestPdfMaroto_SetBackgroundColor(t *testing.T) { + // Arrange + pdf := basePdfTest(12.3, 19.3, 0) + m := newMarotoTest(pdf, nil, nil, nil, nil, nil, nil, nil) + white := color.NewWhite() + + // Act + m.SetBackgroundColor(white) + + // Assert + pdf.AssertCalled(t, "SetFillColor", white.Red, white.Green, white.Blue) + pdf.AssertNumberOfCalls(t, "SetFillColor", 2) +} diff --git a/pkg/props/prop.go b/pkg/props/prop.go index 97d4645a..5d223d16 100644 --- a/pkg/props/prop.go +++ b/pkg/props/prop.go @@ -1,6 +1,9 @@ package props -import "github.com/johnfercher/maroto/pkg/consts" +import ( + "github.com/johnfercher/maroto/pkg/color" + "github.com/johnfercher/maroto/pkg/consts" +) // Proportion represents a proportion from a rectangle, example: 16x9, 4x3... type Proportion struct { @@ -80,13 +83,17 @@ type TableList struct { ContentProp Font // Align is the align of the text (header and content) inside the columns Align consts.Align + // AlternatedBackground define the background color from even rows + // i.e rows with index (0, 2, 4, ..., N) will have background colorized, + // rows with index (1, 3, 5, ..., N) will not + AlternatedBackground *color.Color // HeaderContentSpace is the space between the header and the contents HeaderContentSpace float64 // Line adds a line after every content-row to separate rows. The line's spaceHeight is set to 1.0 Line bool } -// MakeValid from Rect means will make the properties from a rectangle reliable to fit inside a cell +// MakeValid from Rect will make the properties from a rectangle reliable to fit inside a cell // and define default values for a rectangle func (s *Rect) MakeValid() { if s.Percent <= 0.0 || s.Percent > 100.0 { @@ -107,7 +114,7 @@ func (s *Rect) MakeValid() { } } -// MakeValid from Barcode means will make the properties from a barcode reliable to fit inside a cell +// MakeValid from Barcode will make the properties from a barcode reliable to fit inside a cell // and define default values for a barcode func (s *Barcode) MakeValid() { if s.Percent <= 0.0 || s.Percent > 100.0 { diff --git a/pull_request_template.md b/pull_request_template.md index 6ea055f5..d8870403 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -22,5 +22,5 @@ - [ ] Updated pkg/pdf/example_test.go - [ ] Updated README.md - [ ] Updated all examples inside internal/examples -- [ ] New public methods has comments upside them explaining what it does +- [ ] New public methods/structs/interfaces has comments upside them explaining they responsibilities - [ ] Executed `go fmt github.com/johnfercher/maroto/...` to format all files \ No newline at end of file