-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtextdrawer.go
139 lines (125 loc) · 3.34 KB
/
textdrawer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"image"
"image/color"
"image/draw"
"io/ioutil"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type TextDrawer interface {
Draw(img image.Image) image.Image
}
type TextDrawerConfig struct {
FontFile string
FontSize float64
OffsetX, OffsetY int
LineSpacing int
TextWidth int
TextColor color.RGBA
BackgroundColor color.RGBA
BackgroundPadding int
}
type WordWrappingTextDrawer struct {
Config TextDrawerConfig
Text string
}
func (d WordWrappingTextDrawer) Draw(img image.Image) image.Image {
fontFile := d.Config.FontFile
fontSize := d.Config.FontSize
width := d.Config.TextWidth
lineSpacing := d.Config.LineSpacing
bgColor := d.Config.BackgroundColor
bgPadding := d.Config.BackgroundPadding
textColor := d.Config.TextColor
x := d.Config.OffsetX
y := d.Config.OffsetY
canvas := image.NewRGBA(img.Bounds())
draw.Draw(canvas, img.Bounds(), img, image.ZP, draw.Src)
ff := fontFace(fontFile, fontSize)
lines := wordWrap(d.Text, width, ff)
height := paragraphHeight(lines, ff, lineSpacing)
drawBackground(canvas, bgColor, image.Rect(x-bgPadding, y-bgPadding,
x+bgPadding+width, y+bgPadding+height))
drawTextWordWrap(canvas, lines, ff, textColor, lineSpacing, x, y)
return canvas
}
func fontFace(filename string, size float64) font.Face {
b, err := ioutil.ReadFile(filename)
if err != nil {
panic("Read file error " + filename + ":" + err.Error())
}
f, err := freetype.ParseFont(b)
if err != nil {
panic(err)
}
return truetype.NewFace(f, &truetype.Options{Size: size})
}
func drawBackground(canvas draw.Image, c color.Color, rect image.Rectangle) {
bg := image.NewUniform(c)
draw.Draw(canvas, rect, bg, image.ZP, draw.Over)
}
func paragraphHeight(text []string, ff font.Face, lineSpacing int) int {
return ff.Metrics().Ascent.Floor() +
(ff.Metrics().Height.Floor()+lineSpacing)*len(text) - lineSpacing
}
func wordWrap(text string, width int, ff font.Face) []string {
lineWidth := fixed.I(0)
rs := []rune(text)
line := ""
lines := make([]string, 0)
for i := 0; i < len(rs); i++ {
r := rs[i]
advance, ok := ff.GlyphAdvance(r)
if !ok {
// skipping unknown character
continue
}
if lineWidth+advance < fixed.I(width) {
line += string(r)
lineWidth += advance
if r == '\n' { // handle line breakers
// remove line breakers to prevent showing its glyph in some fonts
line = line[:len(line)-1]
lines = append(lines, line)
line = ""
lineWidth = fixed.I(0)
}
if i == len(rs)-1 { // last loop
lines = append(lines, line)
}
} else {
lines = append(lines, line)
line = ""
lineWidth = fixed.I(0)
i--
}
}
return lines
}
func drawTextWordWrap(canvas draw.Image, lines []string,
ff font.Face, tc color.Color, lineSpacing, x, y int) {
point := fixed.Point26_6{
// X offset
X: fixed.I(x),
// Y offset of glyph
// This value is accepted by font.Drawer as the Y value of baseline,
// so Ascent value must be added
Y: ff.Metrics().Ascent + fixed.I(y),
}
drawer := &font.Drawer{
Src: image.NewUniform(tc),
Dst: canvas,
// Note that this is the baseline location
Dot: point,
Face: ff,
}
for _, line := range lines {
drawer.DrawString(line)
point.Y += ff.Metrics().Height
point.Y += fixed.I(lineSpacing)
drawer.Dot = point
}
}