Skip to content

Commit

Permalink
images.Text: Add "alignx" option for horizontal alignment
Browse files Browse the repository at this point in the history
Add an "alignx" option to the images.Text to control whether the value of the "x" option is the left border of the text (current behaviour), the center of each line or the right border.

Fixes #10849
  • Loading branch information
jlskuz committed Jan 6, 2025
1 parent b7b49fb commit 0f351a8
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 14 deletions.
4 changes: 4 additions & 0 deletions docs/content/en/functions/images/Text.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ x
y
: (`int`) The vertical offset, in pixels, relative to the top of the image. Default is `10`.

alignx
{{< new-in 0.141.0 >}}
: (`string`) The horizontal alignment of the text relative to the `x` position. One of `left`, `center`, or `right`. Default is `left`.

[global resource]: /getting-started/glossary/#global-resource
[page resource]: /getting-started/glossary/#page-resource
[remote resource]: /getting-started/glossary/#remote-resource
Expand Down
3 changes: 3 additions & 0 deletions resources/images/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (*Filters) Text(text string, options ...any) gift.Filter {
size: 20,
x: 10,
y: 10,
alignx: "left",
linespacing: 2,
}

Expand All @@ -87,6 +88,8 @@ func (*Filters) Text(text string, options ...any) gift.Filter {
tf.x = cast.ToInt(v)
case "y":
tf.y = cast.ToInt(v)
case "alignx":
tf.alignx = cast.ToString(v)
case "linespacing":
tf.linespacing = cast.ToInt(v)
case "font":
Expand Down
63 changes: 49 additions & 14 deletions resources/images/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type textFilter struct {
text string
color color.Color
x, y int
alignx string
size float64
linespacing int
fontSource hugio.ReadSeekCloserProvider
Expand Down Expand Up @@ -77,33 +78,67 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)

gift.New().Draw(dst, src)

// Draw text, consider and include linebreaks
maxWidth := dst.Bounds().Dx() - 20
fontHeight := face.Metrics().Ascent.Ceil()

// Correct y position based on font and size
f.y = f.y + fontHeight
// Default left alignment
availableWidth := maxWidth - f.x
if f.alignx == "center" {
availableWidth = min((maxWidth-f.x), f.x) * 2
} else if f.alignx == "right" {
availableWidth = f.x
}

// Start position
y := f.y
d.Dot = fixed.P(f.x, f.y)
fontHeight := face.Metrics().Ascent.Ceil()

// Draw text line by line, breaking each line at the maximum width.
// Calculate lines, consider and include linebreaks
finalLines := []string{}
f.text = strings.ReplaceAll(f.text, "\r", "")
for _, line := range strings.Split(f.text, "\n") {
currentLine := ""
// Break each line at the maximum width.
for _, str := range strings.Fields(line) {
strWidth := font.MeasureString(face, str)
if (d.Dot.X.Ceil() + strWidth.Ceil()) >= maxWidth {
y = y + fontHeight + f.linespacing
d.Dot = fixed.P(f.x, y)
fieldStrWidth := font.MeasureString(face, str)
currentLineStrWidth := font.MeasureString(face, currentLine)

if (currentLineStrWidth.Ceil() + fieldStrWidth.Ceil()) >= availableWidth {
finalLines = append(finalLines, currentLine)
currentLine = ""
}
d.DrawString(str + " ")
currentLine += str + " "
}
finalLines = append(finalLines, currentLine)
}

// Correct y position based on font and size
f.y = f.y + fontHeight

// Start position
y := f.y

// Draw text line by line
for _, line := range finalLines {
line = strings.TrimSpace(line)
strWidth := font.MeasureString(face, line)
x := calcX(f.alignx, f.x, strWidth)
d.Dot = fixed.P(x, y)
d.DrawString(line)
y = y + fontHeight + f.linespacing
d.Dot = fixed.P(f.x, y)
}
}

func (f textFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
return image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
}

func calcX(alignX string, x int, strWidth fixed.Int26_6) (rightX int) {

if alignX == "center" {
rightX = x - (strWidth.Ceil() / 2)
} else if alignX == "right" {
rightX = x - strWidth.Ceil()
} else {
// Default left alignment
rightX = x
}
return
}

0 comments on commit 0f351a8

Please sign in to comment.