Add custom font support to images.Text
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 7 Dec 2021 11:49:53 +0000 (12:49 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 7 Dec 2021 15:53:02 +0000 (16:53 +0100)
Fixes #9253

14 files changed:
common/hugio/readers.go
docs/content/en/functions/images/index.md
resources/image_test.go
resources/images/filters.go
resources/images/text.go
resources/resource/resourcetypes.go
resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_3.png
resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_60c098f0ca6626668d9e3ad6bfb38b5b.png
resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_8166ccaf22bdabb94c9bb90bffe64133.png
resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9a8d95423df65a9c230a4cc88056c13a.png
resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d87fd348ad697a9b16399709441d9d56.png
resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png
resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png
resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png

index c93e05e8650e71ad4049894bba10c7a8fae8ce8a..60bd97992fc731c7fce52ce07a43fb1c1c8984f5 100644 (file)
@@ -31,6 +31,11 @@ type ReadSeekCloser interface {
        io.Closer
 }
 
+// ReadSeekCloserProvider provides a ReadSeekCloser.
+type ReadSeekCloserProvider interface {
+       ReadSeekCloser() (ReadSeekCloser, error)
+}
+
 // 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.
 type ReadSeekerNoOpCloser struct {
index 274072dba73541e1d64cd474c78c74497aaaa421..1cf69ee7851768155077ecc753fc3ae231db75f9 100644 (file)
@@ -42,6 +42,8 @@ The above will overlay `$logo` in the upper left corner of `$img` (at position `
 
 ### Text
 
+{{< new-in "0.90.0" >}}
+
 Using the `Text` filter, you can add text to an image.
 
 {{% 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.
 
-```
+```go-html-template
 {{ $img := resources.Get "/images/background.png"}}
 {{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
     "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
 
 {{% funcsig %}}
index 41558a7a59aad838fba4f1262e002952c1e85901..ad8c42bd7b7d13c6b43d298c12cb3e2a28ba864e 100644 (file)
@@ -597,6 +597,8 @@ func TestImageOperationsGolden(t *testing.T) {
        c := qt.New(t)
        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
 
        testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"}
index 63caefdd15fcf8b0650bbba32e3a2730fd56ca0d..e166a0f9d983a37ba75f4a0c4e6646d9876a3e11 100644 (file)
 package images
 
 import (
+       "fmt"
+
+       "github.com/gohugoio/hugo/common/hugio"
        "github.com/gohugoio/hugo/common/maps"
+       "github.com/gohugoio/hugo/resources/resource"
 
        "github.com/disintegration/gift"
        "github.com/spf13/cast"
@@ -61,6 +65,21 @@ func (*Filters) Text(text string, options ...interface{}) gift.Filter {
                                tf.y = cast.ToInt(v)
                        case "linespacing":
                                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()
+
                        }
                }
        }
index a90f252729278a1c5f84a58d8c9a8991b79662cb..cc67a5d1d46d5fcd412b754da48494847bfa3057 100644 (file)
@@ -16,9 +16,11 @@ package images
 import (
        "image"
        "image/draw"
+       "io"
        "strings"
 
        "github.com/disintegration/gift"
+       "github.com/gohugoio/hugo/common/hugio"
 
        "golang.org/x/image/font"
        "golang.org/x/image/font/gofont/goregular"
@@ -33,6 +35,7 @@ type textFilter struct {
        x, y        int
        size        float64
        linespacing int
+       fontSource  hugio.ReadSeekCloserProvider
 }
 
 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
        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)
        if err != nil {
                panic(err)
index 206ce8de8d041a1258c74be22a863973e8eb4c0f..8ab77e436d3657ae000557db3034f1e8b2613ea8 100644 (file)
@@ -155,11 +155,7 @@ type OpenReadSeekCloser func() (hugio.ReadSeekCloser, error)
 // ReadSeekCloserResource is a Resource that supports loading its content.
 type ReadSeekCloserResource interface {
        MediaType() media.Type
-       ReadSeekCloserProvider
-}
-
-type ReadSeekCloserProvider interface {
-       ReadSeekCloser() (hugio.ReadSeekCloser, error)
+       hugio.ReadSeekCloserProvider
 }
 
 // LengthProvider is a Resource that provides a length
index 8dd7342ec22bf9cb61b24ac6c3f08625ce622d01..4ef633564b050a42b5791b0ab29f135e6e5dcbbd 100644 (file)
Binary files a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_3.png and b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_300x200_fill_gaussian_smart1_3.png differ
index 30161204c6a5e8bb8973b4150b58f57a577bbc08..46fa3fd1b22b7543bb7d0d3fda3bc280c26104d3 100644 (file)
Binary files a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_60c098f0ca6626668d9e3ad6bfb38b5b.png and b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_60c098f0ca6626668d9e3ad6bfb38b5b.png differ
index f1968edba2805069a43ea02761ddb740a1285fa2..2fece780415dd0bdfde25e32bf77553fe43bf79b 100644 (file)
Binary files a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_8166ccaf22bdabb94c9bb90bffe64133.png and b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_8166ccaf22bdabb94c9bb90bffe64133.png differ
index 723e78ce1a589a0a7eca915de6cb24cbf374674d..32c5b49d8e77b5b3471b37cd2e34a340e37999b7 100644 (file)
Binary files a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9a8d95423df65a9c230a4cc88056c13a.png and b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_9a8d95423df65a9c230a4cc88056c13a.png differ
index c1b60fa2a1ecb8974c37d9cb4bcb21584f48e2e2..174649232d276b3eb8c678c594cff5bc1b8d7a30 100644 (file)
Binary files a/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d87fd348ad697a9b16399709441d9d56.png and b/resources/testdata/golden/gohugoio24_huc57dd738f4724f4b341121e66fd85555_267952_d87fd348ad697a9b16399709441d9d56.png differ
index f7d0edb310f69205b8fe49810810b76e9becc610..51f6cfa7ee2cc6daaf879e1532ccad7145298d0a 100644 (file)
Binary files a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png and b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png differ
index 789912a6d3e54d49138516f47572c5ab5a638da3..c8f7825981e8f0208da3c5934e95e4cff0fffdaa 100644 (file)
Binary files a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png and b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png differ
index f5c9ec0d1669318a0106f63aa4fa33f180449b78..2def214c8d4be5a6576f1307895a560c17956ac3 100644 (file)
Binary files a/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png and b/resources/testdata/golden/gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png differ