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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ A Maroto way to create PDFs. Maroto is inspired in Bootstrap and uses [Gofpdf](h
You can write your PDFs like you are creating a site using Bootstrap. A Row may have many Cols, and a Col may have many components.
Besides that, pages will be added when content may extrapolate the useful area. You can define a header which will be added
always when a new page appear, in this case, a header may have many rows, lines or tablelist.
Text components support multiple line break strategies, including character-based wrapping when a text should break without spaces and without hyphenation.

#### Maroto `v2.4.0` is here! Try out:

Expand Down
148 changes: 148 additions & 0 deletions docs/assets/examples/richtextgrid/v2/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package main

import (
"log"

"github.com/johnfercher/maroto/v2"
"github.com/johnfercher/maroto/v2/pkg/components/richtext"
"github.com/johnfercher/maroto/v2/pkg/components/text"
"github.com/johnfercher/maroto/v2/pkg/config"
"github.com/johnfercher/maroto/v2/pkg/consts/align"
"github.com/johnfercher/maroto/v2/pkg/consts/fontstyle"
"github.com/johnfercher/maroto/v2/pkg/core"
"github.com/johnfercher/maroto/v2/pkg/props"
)

func main() {
m := GetMaroto()
document, err := m.Generate()
if err != nil {
log.Fatal(err.Error())
}

err = document.Save("docs/assets/pdf/richtextgridv2.pdf")
if err != nil {
log.Fatal(err.Error())
}

err = document.GetReport().Save("docs/assets/text/richtextgridv2.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.AddRows(text.NewRow(10, "Bold word in a sentence", props.Text{
Top: 3,
Left: 2,
Bottom: 2,
Style: fontstyle.Bold,
Size: 9,
}))
m.AddAutoRow(
richtext.NewCol(12,
richtext.NewChunk("This sentence has a ", props.Text{Top: 2, Left: 2, Bottom: 2}),
richtext.NewChunk("bold", props.Text{Style: fontstyle.Bold}),
richtext.NewChunk(" word in the middle."),
),
)

m.AddRows(text.NewRow(10, "Mixed styles and colors", props.Text{
Top: 3,
Left: 2,
Bottom: 2,
Style: fontstyle.Bold,
Size: 9,
}))
m.AddAutoRow(
richtext.NewCol(12,
richtext.NewChunk("Normal, ", props.Text{Top: 2, Left: 2, Bottom: 2}),
richtext.NewChunk("bold, ", props.Text{Style: fontstyle.Bold}),
richtext.NewChunk("italic, ", props.Text{Style: fontstyle.Italic}),
richtext.NewChunk("red", props.Text{Style: fontstyle.Bold, Color: &props.RedColor}),
richtext.NewChunk(", "),
richtext.NewChunk("green", props.Text{Style: fontstyle.Bold, Color: &props.GreenColor}),
richtext.NewChunk(", and "),
richtext.NewChunk("blue", props.Text{Style: fontstyle.Bold, Color: &props.BlueColor}),
richtext.NewChunk(" in one flowing paragraph."),
),
)

m.AddRows(text.NewRow(10, "Mixed font sizes", props.Text{
Top: 3,
Left: 2,
Bottom: 2,
Style: fontstyle.Bold,
Size: 9,
}))
m.AddAutoRow(
richtext.NewCol(12,
richtext.NewChunk("Small ", props.Text{Top: 2, Left: 2, Bottom: 2, Size: 8}),
richtext.NewChunk("Medium ", props.Text{Size: 12}),
richtext.NewChunk("Large ", props.Text{Size: 16, Style: fontstyle.Bold}),
richtext.NewChunk("back to small.", props.Text{Size: 8}),
),
)

m.AddRows(text.NewRow(10, "Word wrapping across styles", props.Text{
Top: 3,
Left: 2,
Bottom: 2,
Style: fontstyle.Bold,
Size: 9,
}))
m.AddAutoRow(
richtext.NewCol(12,
richtext.NewChunk("This is a longer paragraph that demonstrates how ", props.Text{
Top: 2,
Left: 2,
Right: 2,
}),
richtext.NewChunk("rich text", props.Text{Style: fontstyle.Bold}),
richtext.NewChunk(" handles "),
richtext.NewChunk("word wrapping", props.Text{Style: fontstyle.Italic}),
richtext.NewChunk(" across multiple lines while "),
richtext.NewChunk("preserving each chunk style", props.Text{Style: fontstyle.Bold, Color: &props.RedColor}),
richtext.NewChunk(" inside the same paragraph."),
),
)

m.AddRows(text.NewRow(10, "Alignment and line breaks", props.Text{
Top: 3,
Left: 2,
Bottom: 2,
Style: fontstyle.Bold,
Size: 9,
}))
m.AddAutoRow(
richtext.NewCol(6,
richtext.NewChunk("Centered rich text\nwith explicit line breaks", props.Text{
Top: 2,
Left: 2,
Right: 2,
Align: align.Center,
PreserveLineBreaks: true,
}),
richtext.NewChunk(" and "),
richtext.NewChunk("bold emphasis", props.Text{Style: fontstyle.Bold}),
richtext.NewChunk("."),
),
richtext.NewCol(6,
richtext.NewChunk("Justified text keeps the paragraph flowing while distributing the available width between words.", props.Text{
Top: 2,
Left: 2,
Right: 2,
Align: align.Justify,
}),
),
)

return m
}
16 changes: 16 additions & 0 deletions docs/assets/examples/richtextgrid/v2/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"testing"

"github.com/johnfercher/maroto/v2/pkg/test"
)

func TestGetMaroto(t *testing.T) {
t.Parallel()
// Act
sut := GetMaroto()

// Assert
test.New(t).Assert(sut.GetStructure()).Equals("examples/richtextgrid.json")
}
56 changes: 56 additions & 0 deletions docs/assets/examples/textgrid/v2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ package main
import (
"log"

"github.com/johnfercher/maroto/v2/pkg/components/col"
"github.com/johnfercher/maroto/v2/pkg/consts/fontstyle"

"github.com/johnfercher/maroto/v2/pkg/core"

"github.com/johnfercher/maroto/v2"

"github.com/johnfercher/maroto/v2/pkg/consts/border"
"github.com/johnfercher/maroto/v2/pkg/consts/breakline"

"github.com/johnfercher/maroto/v2/pkg/components/text"
Expand Down Expand Up @@ -47,6 +49,8 @@ func GetMaroto() core.Maroto {

longText := "This is a longer sentence that will be broken into multiple lines " +
"as it does not fit into the column otherwise."
longWord := "CharacterStrategyBreaksLongTextWithoutAddingHyphens"
paragraphText := "First paragraph line 1.\nFirst paragraph line 2.\n\nSecond paragraph starts here."

m.AddRow(40,
text.NewCol(2, "Red text", props.Text{Color: &props.RedColor}),
Expand Down Expand Up @@ -125,5 +129,57 @@ func GetMaroto() core.Maroto {
},
),
)

m.AddRows(text.NewRow(10, "Character break line strategy"))

m.AddAutoRow(
text.NewCol(6, longWord,
props.Text{
Left: 3,
Right: 3,
Align: align.Left,
BreakLineStrategy: breakline.DashStrategy,
},
),
text.NewCol(6, longWord,
props.Text{
Left: 3,
Right: 3,
Align: align.Left,
BreakLineStrategy: breakline.CharacterStrategy,
},
),
)

m.AddRows(text.NewRow(10, "Explicit line breaks", props.Text{Style: fontstyle.Bold}))

m.AddRow(8,
text.NewCol(6, "Default behavior", props.Text{Align: align.Center, Style: fontstyle.Bold}),
text.NewCol(6, "PreserveLineBreaks", props.Text{Align: align.Center, Style: fontstyle.Bold}),
)

textCellStyle := &props.Cell{
BorderType: border.Full,
BorderColor: &props.BlackColor,
BorderThickness: 0.1,
}

m.AddAutoRow(
col.New(6).Add(
text.New(paragraphText, props.Text{
Top: 2,
Left: 2,
Right: 2,
}),
).WithStyle(textCellStyle),
col.New(6).Add(
text.New(paragraphText, props.Text{
Top: 2,
Left: 2,
Right: 2,
PreserveLineBreaks: true,
}),
).WithStyle(textCellStyle),
)
return m
}
Binary file added docs/assets/pdf/richtextgridv2.pdf
Binary file not shown.
Binary file modified docs/assets/pdf/textgridv2.pdf
Binary file not shown.
3 changes: 3 additions & 0 deletions docs/assets/text/richtextgridv2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
generate -> avg: 14.22ms, executions: [14.22ms]
add_rows -> avg: 3716.60ns, executions: [17.92μs, 0.21μs, 0.21μs, 0.08μs, 0.17μs]
file_size -> 51.02Kb
8 changes: 4 additions & 4 deletions docs/assets/text/textgridv2.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
generate -> avg: 7.76ms, executions: [7.76ms]
add_row -> avg: 3375.17ns, executions: [18.38μs, 0.42μs, 0.21μs, 0.17μs, 0.21μs, 0.88μs]
add_rows -> avg: 138.83ns, executions: [208.00ns, 125.00ns, 83.00ns, 209.00ns, 42.00ns, 166.00ns]
file_size -> 33.35Kb
generate -> avg: 10.22ms, executions: [10.22ms]
add_row -> avg: 2702.57ns, executions: [16.88μs, 0.38μs, 0.17μs, 0.17μs, 0.21μs, 0.88μs, 0.25μs]
add_rows -> avg: 114.38ns, executions: [166.00ns, 83.00ns, 41.00ns, 167.00ns, 83.00ns, 125.00ns, 125.00ns, 125.00ns]
file_size -> 37.60Kb
1 change: 1 addition & 0 deletions docs/v2/features/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* [Parallelism Mode](v2/features/parallelism.md?id=parallelism)
* [Protection](v2/features/protection.md?id=protection)
* [QR Code](v2/features/qrcode.md?id=qrcode)
* [Rich Text](v2/features/richtext.md?id=rich-text)
* [Signature](v2/features/signature.md?id=signature)
* [Text](v2/features/text.md?id=text)
* [Unit Testing](v2/features/unittests.md?id=unit-testing)
27 changes: 27 additions & 0 deletions docs/v2/features/richtext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Rich Text

The Rich Text component renders multiple styled chunks inside a single flowing paragraph. It is useful when one sentence needs partial emphasis such as bold words, mixed colors, italic fragments, or different font sizes without splitting the content across multiple columns.

`richtext` keeps wrapping behavior across chunk boundaries, so the paragraph still behaves like one text block instead of several disconnected components.

## GoDoc
* [constructor : New](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/richtext#New)
* [constructor : NewCol](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/richtext#NewCol)
* [constructor : NewRow](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/richtext#NewRow)
* [constructor : NewAutoRow](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/richtext#NewAutoRow)
* [constructor : NewChunk](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/richtext#NewChunk)
* [type : Chunk](https://pkg.go.dev/github.com/johnfercher/maroto/v2/pkg/components/richtext#Chunk)

## Code Example
[filename](../../assets/examples/richtextgrid/v2/main.go ':include :type=code')

## PDF Generated
```pdf
assets/pdf/richtextgridv2.pdf
```

## Time Execution
[filename](../../assets/text/richtextgridv2.txt ':include :type=code')
Comment on lines +23 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a clearer section title: Execution Time.

Time Execution reads awkwardly in user-facing docs.

✏️ Suggested doc tweak
-## Time Execution
+## Execution Time
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Time Execution
[filename](../../assets/text/richtextgridv2.txt ':include :type=code')
## Execution Time
[filename](../../assets/text/richtextgridv2.txt ':include :type=code')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/v2/features/richtext.md` around lines 23 - 24, Replace the awkward
section header "## Time Execution" with the clearer title "## Execution Time" in
the markdown (look for the exact header string "## Time Execution"); keep all
following content and the included code block reference unchanged so only the
heading text is updated.


## Test File
[filename](https://raw.githubusercontent.com/johnfercher/maroto/master/test/maroto/examples/richtextgrid.json ':include :type=code')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Avoid linking fixtures from master in versioned docs.

Using master can drift as the branch evolves; pin to a tag/commit (or local artifact) to keep docs reproducible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/v2/features/richtext.md` at line 27, The doc currently links the
external fixture at
'https://raw.githubusercontent.com/johnfercher/maroto/master/test/maroto/examples/richtextgrid.json',
which points to master and can drift; update this link to a pinned artifact by
replacing 'master' with a specific tag or commit SHA (or alternatively copy the
fixture into the repo and reference it as a local relative path) so the example
remains reproducible.

5 changes: 4 additions & 1 deletion docs/v2/features/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,18 @@ Text can be created as a standalone `Component`, wrapped directly into a `Col`,
| `Bottom` | `float64` | `0` | Bottom offset — used by auto rows only (mm) |
| `Left` | `float64` | `0` | Left margin inside the cell (mm) |
| `Right` | `float64` | `0` | Right margin inside the cell (mm) |
| `BreakLineStrategy` | `breakline.Strategy` | `EmptySpaceStrategy` | `EmptySpaceStrategy` breaks on spaces; `DashStrategy` breaks mid-word with a hyphen |
| `BreakLineStrategy` | `breakline.Strategy` | `EmptySpaceStrategy` | `EmptySpaceStrategy` breaks on spaces; `DashStrategy` breaks mid-word with a hyphen; `CharacterStrategy` breaks at character boundaries without adding symbols |
| `VerticalPadding` | `float64` | `0` | Extra spacing between lines (mm) |
| `PreserveLineBreaks` | `bool` | `false` | Treat explicit `\n` as hard line breaks instead of collapsing them into spaces |
| `Hyperlink` | `*string` | `nil` | URL — makes the text a clickable link (rendered in blue) |

## Usage notes

- When `Hyperlink` is set, the text color is overridden with blue regardless of `Color`.
- `Top` and `Left`/`Right` are clamped to the cell dimensions if they exceed it.
- `BreakLineStrategy` only applies when the text does not fit on a single line.
- Use `CharacterStrategy` when a text should wrap without spaces and without inserting trailing hyphens.
- Set `PreserveLineBreaks: true` when you want explicit `\n` to become hard line breaks; use `\n\n` to create a blank paragraph gap inside the same text component.
- For justified text on the last line, spacing may revert to default space width to avoid stretching a few characters across the full width.

## GoDoc
Expand Down
14 changes: 7 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/f-amaral/go-async v0.3.0
github.com/google/uuid v1.6.0
github.com/johnfercher/go-tree v1.1.0
github.com/pdfcpu/pdfcpu v0.11.1
github.com/pdfcpu/pdfcpu v0.12.0
github.com/phpdave11/gofpdf v1.4.3
github.com/stretchr/testify v1.11.1
gopkg.in/yaml.v3 v3.0.1
Expand All @@ -17,14 +17,14 @@ require (
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/hhrutter/pkcs7 v0.2.2 // indirect
github.com/hhrutter/tiff v1.0.3 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/image v0.38.0 // indirect
golang.org/x/text v0.35.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/image v0.39.0 // indirect
golang.org/x/text v0.36.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading