Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ body:
id: gpdf-version
attributes:
label: gpdf Version
placeholder: "v1.0.7"
placeholder: "v1.0.8"
validations:
required: true

Expand Down
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [1.0.8] - 2026-05-06

### Added
- Padding support on Text elements following the CSS box model (#23)
- Builder: `template.TextPadding(Edges)`
- JSON / GoTemplate schema: `text.style.padding` (uniform, e.g. `"10mm"`) and `text.style.paddings` (CSS shorthand `[top, right, bottom, left]`, 1–4 values)
- Layout engine: `Style.Padding` and `Style.Border` participate in text flow — wrap width shrinks to the inner content area, lines are offset by the top/left inset, and `BgColor` fills the padded box (`document/layout/flow.go`)
- Example tests: `_examples/{builder,json,gotemplate}/36_text_padding_test.go` with shared golden

## [1.0.7] - 2026-04-29

### Added
Expand Down Expand Up @@ -150,7 +159,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Reed-Solomon coefficient order in QR code encoder
- binary.Write return value handling for errcheck lint

[Unreleased]: https://github.com/gpdf-dev/gpdf/compare/v1.0.7...HEAD
[Unreleased]: https://github.com/gpdf-dev/gpdf/compare/v1.0.8...HEAD
[1.0.8]: https://github.com/gpdf-dev/gpdf/compare/v1.0.7...v1.0.8
[1.0.7]: https://github.com/gpdf-dev/gpdf/compare/v1.0.6...v1.0.7
[1.0.6]: https://github.com/gpdf-dev/gpdf/compare/v1.0.5...v1.0.6
[1.0.5]: https://github.com/gpdf-dev/gpdf/compare/v1.0.4...v1.0.5
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Go Reference](https://pkg.go.dev/badge/github.com/gpdf-dev/gpdf.svg)](https://pkg.go.dev/github.com/gpdf-dev/gpdf)
[![CI](https://github.com/gpdf-dev/gpdf/actions/workflows/check-code.yml/badge.svg)](https://github.com/gpdf-dev/gpdf/actions/workflows/check-code.yml)
![coverage](https://img.shields.io/badge/coverage-84.4%25-green)
![coverage](https://img.shields.io/badge/coverage-84.1%25-green)
[![Go Report Card](https://goreportcard.com/badge/github.com/gpdf-dev/gpdf)](https://goreportcard.com/report/github.com/gpdf-dev/gpdf)
[![Go Version](https://img.shields.io/badge/Go-%3E%3D1.22-blue)](https://go.dev/)
[![Website](https://img.shields.io/badge/Website-gpdf.dev-blue)](https://gpdf.dev/)
Expand Down
115 changes: 115 additions & 0 deletions _examples/builder/36_text_padding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package builder_test

import (
"testing"

"github.com/gpdf-dev/gpdf/_examples/testutil"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/pdf"
"github.com/gpdf-dev/gpdf/template"
)

// TestExample_36_TextPadding exercises [template.TextPadding] (issue #23).
// Before the fix, Padding set on Style via TextOption was silently ignored
// because LayoutText only consumed FontSize / LineHeight / TextIndent.
// The page lays out matched pairs of Text cells — one without padding,
// one with — so that any regression where padding is dropped would change
// the rendered heights and the resulting golden bytes. The same shared
// golden is consumed by TestJSON_36_TextPadding and TestTmpl_36_TextPadding
// to ensure all three entry points produce byte-identical output.
func TestExample_36_TextPadding(t *testing.T) {
doc := template.New(
template.WithPageSize(document.A4),
template.WithMargins(document.UniformEdges(document.Mm(20))),
)

page := doc.AddPage()

page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Text padding (issue #23)", template.FontSize(18), template.Bold())
c.Spacer(document.Mm(5))
})
})

// Pair A: uniform padding around a single line.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("A. Uniform 10mm padding fills the BgColor area",
template.FontSize(11), template.Bold())
c.Spacer(document.Mm(2))
})
})
page.AutoRow(func(r *template.RowBuilder) {
r.Col(6, func(c *template.ColBuilder) {
c.Text("No padding",
template.BgColor(pdf.Gray(0.85)))
})
r.Col(6, func(c *template.ColBuilder) {
c.Text("Should have 10 mm padding",
template.BgColor(pdf.Gray(0.85)),
template.TextPadding(document.UniformEdges(document.Mm(10))))
})
})

page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) { c.Spacer(document.Mm(8)) })
})

// Pair B: asymmetric padding — top/bottom only — to confirm horizontal
// wrap width is unchanged when only vertical padding is applied.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("B. Asymmetric padding (top/bottom only)",
template.FontSize(11), template.Bold())
c.Spacer(document.Mm(2))
})
})
page.AutoRow(func(r *template.RowBuilder) {
r.Col(6, func(c *template.ColBuilder) {
c.Text("Plain wrapped text that spans across multiple lines so that "+
"line wrapping is exercised in this column.",
template.BgColor(pdf.RGBHex(0xE3F2FD)))
})
r.Col(6, func(c *template.ColBuilder) {
c.Text("Plain wrapped text that spans across multiple lines so that "+
"line wrapping is exercised in this column.",
template.BgColor(pdf.RGBHex(0xE3F2FD)),
template.TextPadding(document.Edges{
Top: document.Mm(6),
Bottom: document.Mm(6),
}))
})
})

page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) { c.Spacer(document.Mm(8)) })
})

// Pair C: horizontal padding — narrows wrap width, forcing extra lines.
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("C. Horizontal padding narrows the wrap width",
template.FontSize(11), template.Bold())
c.Spacer(document.Mm(2))
})
})
page.AutoRow(func(r *template.RowBuilder) {
r.Col(6, func(c *template.ColBuilder) {
c.Text("Lorem ipsum dolor sit amet consectetur adipiscing elit "+
"sed do eiusmod tempor incididunt ut labore et dolore.",
template.BgColor(pdf.RGBHex(0xFFF3E0)))
})
r.Col(6, func(c *template.ColBuilder) {
c.Text("Lorem ipsum dolor sit amet consectetur adipiscing elit "+
"sed do eiusmod tempor incididunt ut labore et dolore.",
template.BgColor(pdf.RGBHex(0xFFF3E0)),
template.TextPadding(document.Edges{
Left: document.Mm(12),
Right: document.Mm(12),
}))
})
})

testutil.GeneratePDFSharedGolden(t, "36_text_padding.pdf", doc)
}
98 changes: 98 additions & 0 deletions _examples/gotemplate/36_text_padding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package gotemplate_test

import (
"testing"

"github.com/gpdf-dev/gpdf/_examples/testutil"
"github.com/gpdf-dev/gpdf/template"
)

// TestTmpl_36_TextPadding mirrors TestExample_36_TextPadding /
// TestJSON_36_TextPadding so the shared golden 36_text_padding.pdf
// compares byte-identically across all three entry points (issue #23).
func TestTmpl_36_TextPadding(t *testing.T) {
schema := []byte(`{
"page": {"size": "A4", "margins": "20mm"},
"body": [
{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "{{.Title}}", "style": {"size": 18, "bold": true}},
{"type": "spacer", "height": "5mm"}
]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "A. Uniform 10mm padding fills the BgColor area", "style": {"size": 11, "bold": true}},
{"type": "spacer", "height": "2mm"}
]}
]}},
{"row": {"cols": [
{"span": 6, "elements": [
{"type": "text", "content": "No padding", "style": {"background": "{{.BgA}}"}}
]},
{"span": 6, "elements": [
{"type": "text", "content": "Should have 10 mm padding", "style": {"background": "{{.BgA}}", "padding": "{{.PadA}}"}}
]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [{"type": "spacer", "height": "8mm"}]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "B. Asymmetric padding (top/bottom only)", "style": {"size": 11, "bold": true}},
{"type": "spacer", "height": "2mm"}
]}
]}},
{"row": {"cols": [
{"span": 6, "elements": [
{"type": "text", "content": "{{.LongB}}", "style": {"background": "{{.BgB}}"}}
]},
{"span": 6, "elements": [
{"type": "text", "content": "{{.LongB}}", "style": {"background": "{{.BgB}}", "paddings": {{toJSON .PadsB}}}}
]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [{"type": "spacer", "height": "8mm"}]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "C. Horizontal padding narrows the wrap width", "style": {"size": 11, "bold": true}},
{"type": "spacer", "height": "2mm"}
]}
]}},
{"row": {"cols": [
{"span": 6, "elements": [
{"type": "text", "content": "{{.LongC}}", "style": {"background": "{{.BgC}}"}}
]},
{"span": 6, "elements": [
{"type": "text", "content": "{{.LongC}}", "style": {"background": "{{.BgC}}", "paddings": {{toJSON .PadsC}}}}
]}
]}}
]
}`)

data := map[string]any{
"Title": "Text padding (issue #23)",
"BgA": "gray(0.85)",
"PadA": "10mm",
"LongB": "Plain wrapped text that spans across multiple lines so that " +
"line wrapping is exercised in this column.",
"BgB": "#E3F2FD",
"PadsB": []string{"6mm", "0", "6mm", "0"},
"LongC": "Lorem ipsum dolor sit amet consectetur adipiscing elit " +
"sed do eiusmod tempor incididunt ut labore et dolore.",
"BgC": "#FFF3E0",
"PadsC": []string{"0", "12mm", "0", "12mm"},
}

doc, err := template.FromJSON(schema, data)
if err != nil {
t.Fatalf("FromJSON error: %v", err)
}
testutil.GeneratePDFSharedGolden(t, "36_text_padding.pdf", doc)
}
84 changes: 84 additions & 0 deletions _examples/json/36_text_padding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package json_test

import (
"testing"

"github.com/gpdf-dev/gpdf/_examples/testutil"
"github.com/gpdf-dev/gpdf/template"
)

// TestJSON_36_TextPadding mirrors TestExample_36_TextPadding so the shared
// golden file ../testdata/golden/36_text_padding.pdf compares byte-identically
// across Builder, JSON, and Go-template entry points (issue #23).
func TestJSON_36_TextPadding(t *testing.T) {
schema := []byte(`{
"page": {"size": "A4", "margins": "20mm"},
"body": [
{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "Text padding (issue #23)", "style": {"size": 18, "bold": true}},
{"type": "spacer", "height": "5mm"}
]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "A. Uniform 10mm padding fills the BgColor area", "style": {"size": 11, "bold": true}},
{"type": "spacer", "height": "2mm"}
]}
]}},
{"row": {"cols": [
{"span": 6, "elements": [
{"type": "text", "content": "No padding", "style": {"background": "gray(0.85)"}}
]},
{"span": 6, "elements": [
{"type": "text", "content": "Should have 10 mm padding", "style": {"background": "gray(0.85)", "padding": "10mm"}}
]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [{"type": "spacer", "height": "8mm"}]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "B. Asymmetric padding (top/bottom only)", "style": {"size": 11, "bold": true}},
{"type": "spacer", "height": "2mm"}
]}
]}},
{"row": {"cols": [
{"span": 6, "elements": [
{"type": "text", "content": "Plain wrapped text that spans across multiple lines so that line wrapping is exercised in this column.", "style": {"background": "#E3F2FD"}}
]},
{"span": 6, "elements": [
{"type": "text", "content": "Plain wrapped text that spans across multiple lines so that line wrapping is exercised in this column.", "style": {"background": "#E3F2FD", "paddings": ["6mm", "0", "6mm", "0"]}}
]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [{"type": "spacer", "height": "8mm"}]}
]}},

{"row": {"cols": [
{"span": 12, "elements": [
{"type": "text", "content": "C. Horizontal padding narrows the wrap width", "style": {"size": 11, "bold": true}},
{"type": "spacer", "height": "2mm"}
]}
]}},
{"row": {"cols": [
{"span": 6, "elements": [
{"type": "text", "content": "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore.", "style": {"background": "#FFF3E0"}}
]},
{"span": 6, "elements": [
{"type": "text", "content": "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore.", "style": {"background": "#FFF3E0", "paddings": ["0", "12mm", "0", "12mm"]}}
]}
]}}
]
}`)

doc, err := template.FromJSON(schema, nil)
if err != nil {
t.Fatalf("FromJSON error: %v", err)
}
testutil.GeneratePDFSharedGolden(t, "36_text_padding.pdf", doc)
}
Binary file added _examples/testdata/golden/36_text_padding.pdf
Binary file not shown.
Loading
Loading