text.go - hugo - [fork] hugo port for 9front
HTML git clone https://git.drkhsh.at/hugo.git
DIR Log
DIR Files
DIR Refs
DIR Submodules
DIR README
DIR LICENSE
---
text.go (3646B)
---
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/color"
19 "image/draw"
20 "io"
21 "strings"
22
23 "github.com/disintegration/gift"
24 "github.com/gohugoio/hugo/common/hugio"
25
26 "golang.org/x/image/font"
27 "golang.org/x/image/font/gofont/goregular"
28 "golang.org/x/image/font/opentype"
29 "golang.org/x/image/math/fixed"
30 )
31
32 var _ gift.Filter = (*textFilter)(nil)
33
34 type textFilter struct {
35 text string
36 color color.Color
37 x, y int
38 alignx string
39 aligny string
40 size float64
41 linespacing int
42 fontSource hugio.ReadSeekCloserProvider
43 }
44
45 func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
46 // Load and parse font
47 ttf := goregular.TTF
48 if f.fontSource != nil {
49 rs, err := f.fontSource.ReadSeekCloser()
50 if err != nil {
51 panic(err)
52 }
53 defer rs.Close()
54 ttf, err = io.ReadAll(rs)
55 if err != nil {
56 panic(err)
57 }
58 }
59 otf, err := opentype.Parse(ttf)
60 if err != nil {
61 panic(err)
62 }
63
64 // Set font options
65 face, err := opentype.NewFace(otf, &opentype.FaceOptions{
66 Size: f.size,
67 DPI: 72,
68 Hinting: font.HintingNone,
69 })
70 if err != nil {
71 panic(err)
72 }
73
74 d := font.Drawer{
75 Dst: dst,
76 Src: image.NewUniform(f.color),
77 Face: face,
78 }
79
80 gift.New().Draw(dst, src)
81
82 maxWidth := dst.Bounds().Dx() - 20
83
84 var availableWidth int
85 switch f.alignx {
86 case "right":
87 availableWidth = f.x
88 case "center":
89 availableWidth = min((maxWidth-f.x), f.x) * 2
90 case "left":
91 availableWidth = maxWidth - f.x
92 }
93
94 fontHeight := face.Metrics().Ascent.Ceil()
95
96 // Calculate lines, consider and include linebreaks
97 finalLines := []string{}
98 f.text = strings.ReplaceAll(f.text, "\r", "")
99 for _, line := range strings.Split(f.text, "\n") {
100 currentLine := ""
101 // Break each line at the maximum width.
102 for _, str := range strings.Fields(line) {
103 fieldStrWidth := font.MeasureString(face, str)
104 currentLineStrWidth := font.MeasureString(face, currentLine)
105
106 if (currentLineStrWidth.Ceil() + fieldStrWidth.Ceil()) >= availableWidth {
107 finalLines = append(finalLines, currentLine)
108 currentLine = ""
109 }
110 currentLine += str + " "
111 }
112 finalLines = append(finalLines, currentLine)
113 }
114 // Total height of the text from the top of the first line to the baseline of the last line
115 totalHeight := len(finalLines)*fontHeight + (len(finalLines)-1)*f.linespacing
116
117 // Correct y position based on font and size
118 y := f.y + fontHeight
119 switch f.aligny {
120 case "top":
121 // Do nothing
122 case "center":
123 y = y - totalHeight/2
124 case "bottom":
125 y = y - totalHeight
126 }
127
128 // Draw text line by line
129 for _, line := range finalLines {
130 line = strings.TrimSpace(line)
131 strWidth := font.MeasureString(face, line)
132 var x int
133 switch f.alignx {
134 case "right":
135 x = f.x - strWidth.Ceil()
136 case "center":
137 x = f.x - (strWidth.Ceil() / 2)
138
139 case "left":
140 x = f.x
141 }
142 d.Dot = fixed.P(x, y)
143 d.DrawString(line)
144 y = y + fontHeight + f.linespacing
145 }
146 }
147
148 func (f textFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
149 return image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
150 }