text.go (2521B)
1 // Copyright 2021 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package images 15 16 import ( 17 "image" 18 "image/draw" 19 "io" 20 "strings" 21 22 "github.com/disintegration/gift" 23 "github.com/gohugoio/hugo/common/hugio" 24 25 "golang.org/x/image/font" 26 "golang.org/x/image/font/gofont/goregular" 27 "golang.org/x/image/font/opentype" 28 "golang.org/x/image/math/fixed" 29 ) 30 31 var _ gift.Filter = (*textFilter)(nil) 32 33 type textFilter struct { 34 text, color string 35 x, y int 36 size float64 37 linespacing int 38 fontSource hugio.ReadSeekCloserProvider 39 } 40 41 func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) { 42 color, err := hexStringToColor(f.color) 43 if err != nil { 44 panic(err) 45 } 46 47 // Load and parse font 48 ttf := goregular.TTF 49 if f.fontSource != nil { 50 rs, err := f.fontSource.ReadSeekCloser() 51 if err != nil { 52 panic(err) 53 } 54 defer rs.Close() 55 ttf, err = io.ReadAll(rs) 56 if err != nil { 57 panic(err) 58 } 59 } 60 otf, err := opentype.Parse(ttf) 61 if err != nil { 62 panic(err) 63 } 64 65 // Set font options 66 face, err := opentype.NewFace(otf, &opentype.FaceOptions{ 67 Size: f.size, 68 DPI: 72, 69 Hinting: font.HintingNone, 70 }) 71 if err != nil { 72 panic(err) 73 } 74 75 d := font.Drawer{ 76 Dst: dst, 77 Src: image.NewUniform(color), 78 Face: face, 79 } 80 81 gift.New().Draw(dst, src) 82 83 // Draw text, consider and include linebreaks 84 maxWidth := dst.Bounds().Dx() - 20 85 fontHeight := face.Metrics().Ascent.Ceil() 86 87 // Correct y position based on font and size 88 f.y = f.y + fontHeight 89 90 // Start position 91 y := f.y 92 d.Dot = fixed.P(f.x, f.y) 93 94 // Draw text and break line at max width 95 parts := strings.Fields(f.text) 96 for _, str := range parts { 97 strWith := font.MeasureString(face, str) 98 if (d.Dot.X.Ceil() + strWith.Ceil()) >= maxWidth { 99 y = y + fontHeight + f.linespacing 100 d.Dot = fixed.P(f.x, y) 101 } 102 d.DrawString(str + " ") 103 } 104 } 105 106 func (f textFilter) Bounds(srcBounds image.Rectangle) image.Rectangle { 107 return image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy()) 108 }