github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/resources/images/text.go (about)

     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  }