Add custom font support to images.Text

Fixes #9253
This commit is contained in:
Bjørn Erik Pedersen 2021-12-07 12:49:53 +01:00
parent e61cdf335f
commit e71d715b9b
14 changed files with 56 additions and 6 deletions

View file

@ -31,6 +31,11 @@ type ReadSeekCloser interface {
io.Closer io.Closer
} }
// ReadSeekCloserProvider provides a ReadSeekCloser.
type ReadSeekCloserProvider interface {
ReadSeekCloser() (ReadSeekCloser, error)
}
// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close. // ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
// TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense. // TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
type ReadSeekerNoOpCloser struct { type ReadSeekerNoOpCloser struct {

View file

@ -42,6 +42,8 @@ The above will overlay `$logo` in the upper left corner of `$img` (at position `
### Text ### Text
{{< new-in "0.90.0" >}}
Using the `Text` filter, you can add text to an image. Using the `Text` filter, you can add text to an image.
{{% funcsig %}} {{% funcsig %}}
@ -50,7 +52,7 @@ images.Text TEXT DICT)
The following example will add the text `Hugo rocks!` to the image with the specified color, size and position. The following example will add the text `Hugo rocks!` to the image with the specified color, size and position.
``` ```go-html-template
{{ $img := resources.Get "/images/background.png"}} {{ $img := resources.Get "/images/background.png"}}
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict {{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
"color" "#ffffff" "color" "#ffffff"
@ -61,6 +63,18 @@ The following example will add the text `Hugo rocks!` to the image with the spec
))}} ))}}
``` ```
You can load a custom font if needed. Load the font as a Hugo `Resource` and set it as an option:
```go-html-template
{{ $font := resources.Get "https://github.com/google/fonts/raw/main/apache/roboto/static/Roboto-Black.ttf" }}
{{ $img := resources.Get "/images/background.png"}}
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
"font" $font
))}}
```
### Brightness ### Brightness
{{% funcsig %}} {{% funcsig %}}

View file

@ -597,6 +597,8 @@ func TestImageOperationsGolden(t *testing.T) {
c := qt.New(t) c := qt.New(t)
c.Parallel() c.Parallel()
// Note, if you're enabling this on a MacOS M1 (ARM) you need to run the test with GOARCH=amd64.
// GOARCH=amd64 go test -timeout 30s -run "^TestImageOperationsGolden$" ./resources -v
devMode := false devMode := false
testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"} testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"}

View file

@ -15,7 +15,11 @@
package images package images
import ( import (
"fmt"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/resources/resource"
"github.com/disintegration/gift" "github.com/disintegration/gift"
"github.com/spf13/cast" "github.com/spf13/cast"
@ -61,6 +65,21 @@ func (*Filters) Text(text string, options ...interface{}) gift.Filter {
tf.y = cast.ToInt(v) tf.y = cast.ToInt(v)
case "linespacing": case "linespacing":
tf.linespacing = cast.ToInt(v) tf.linespacing = cast.ToInt(v)
case "font":
fontSource, ok1 := v.(hugio.ReadSeekCloserProvider)
identifier, ok2 := v.(resource.Identifier)
if !(ok1 && ok2) {
panic(fmt.Sprintf("invalid text font source: %T", v))
}
tf.fontSource = fontSource
// The input value isn't hashable and will not make a stable key.
// Replace it with a string in the map used as basis for the
// hash string.
opt["font"] = identifier.Key()
} }
} }
} }

View file

@ -16,9 +16,11 @@ package images
import ( import (
"image" "image"
"image/draw" "image/draw"
"io"
"strings" "strings"
"github.com/disintegration/gift" "github.com/disintegration/gift"
"github.com/gohugoio/hugo/common/hugio"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/font/gofont/goregular" "golang.org/x/image/font/gofont/goregular"
@ -33,6 +35,7 @@ type textFilter struct {
x, y int x, y int
size float64 size float64
linespacing int linespacing int
fontSource hugio.ReadSeekCloserProvider
} }
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) { func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
@ -43,6 +46,17 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
// Load and parse font // Load and parse font
ttf := goregular.TTF ttf := goregular.TTF
if f.fontSource != nil {
rs, err := f.fontSource.ReadSeekCloser()
if err != nil {
panic(err)
}
defer rs.Close()
ttf, err = io.ReadAll(rs)
if err != nil {
panic(err)
}
}
otf, err := opentype.Parse(ttf) otf, err := opentype.Parse(ttf)
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -155,11 +155,7 @@ type OpenReadSeekCloser func() (hugio.ReadSeekCloser, error)
// ReadSeekCloserResource is a Resource that supports loading its content. // ReadSeekCloserResource is a Resource that supports loading its content.
type ReadSeekCloserResource interface { type ReadSeekCloserResource interface {
MediaType() media.Type MediaType() media.Type
ReadSeekCloserProvider hugio.ReadSeekCloserProvider
}
type ReadSeekCloserProvider interface {
ReadSeekCloser() (hugio.ReadSeekCloser, error)
} }
// LengthProvider is a Resource that provides a length // LengthProvider is a Resource that provides a length

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB