From 01c17b9f9676ea0f4897bae1abb2dd507797bfd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20K=C3=BCnzel?= Date: Mon, 6 Jan 2025 20:31:18 +0100 Subject: [PATCH] images.Text: Add "alignx" option for horizontal alignment 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 --- docs/content/en/functions/images/Text.md | 4 ++ resources/images/filters.go | 7 +++ resources/images/text.go | 59 ++++++++++++++++++------ 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/docs/content/en/functions/images/Text.md b/docs/content/en/functions/images/Text.md index 8c6670d422d..6a18468b746 100644 --- a/docs/content/en/functions/images/Text.md +++ b/docs/content/en/functions/images/Text.md @@ -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 diff --git a/resources/images/filters.go b/resources/images/filters.go index 0a620716d99..99fecdf20d7 100644 --- a/resources/images/filters.go +++ b/resources/images/filters.go @@ -69,6 +69,7 @@ func (*Filters) Text(text string, options ...any) gift.Filter { size: 20, x: 10, y: 10, + alignx: "left", linespacing: 2, } @@ -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) + 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": diff --git a/resources/images/text.go b/resources/images/text.go index c1abc60bd43..4d77034f16a 100644 --- a/resources/images/text.go +++ b/resources/images/text.go @@ -35,6 +35,7 @@ type textFilter struct { text string color color.Color x, y int + alignx string size float64 linespacing int fontSource hugio.ReadSeekCloserProvider @@ -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) } }