Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

images.Text: Add option for horizontal alignment #13223

Merged
merged 1 commit into from
Jan 8, 2025
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
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
7 changes: 7 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,12 @@ 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)
jlskuz marked this conversation as resolved.
Show resolved Hide resolved
if tf.alignx != "left" && tf.alignx != "center" && tf.alignx != "right" {
panic("alignx must be one of left, center, right")
}

case "linespacing":
tf.linespacing = cast.ToInt(v)
case "font":
Expand Down
59 changes: 46 additions & 13 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,30 +78,62 @@ 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

var availableWidth int
switch f.alignx {
case "right":
availableWidth = f.x
case "center":
availableWidth = min((maxWidth-f.x), f.x) * 2
case "left":
availableWidth = maxWidth - f.x
}

fontHeight := face.Metrics().Ascent.Ceil()

// 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) {
fieldStrWidth := font.MeasureString(face, str)
currentLineStrWidth := font.MeasureString(face, currentLine)

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

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

// Start position
y := f.y
d.Dot = fixed.P(f.x, f.y)

// Draw text line by line, breaking each line at the maximum width.
f.text = strings.ReplaceAll(f.text, "\r", "")
for _, line := range strings.Split(f.text, "\n") {
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)
}
d.DrawString(str + " ")
// Draw text line by line
for _, line := range finalLines {
line = strings.TrimSpace(line)
strWidth := font.MeasureString(face, line)
var x int
switch f.alignx {
case "right":
x = f.x - strWidth.Ceil()
case "center":
x = f.x - (strWidth.Ceil() / 2)

case "left":
x = f.x
}
d.Dot = fixed.P(x, y)
d.DrawString(line)
y = y + fontHeight + f.linespacing
d.Dot = fixed.P(f.x, y)
}
}

Expand Down
Loading