1
2
3
4
5 package text
6
7 import (
8 "image/color"
9 "math"
10
11 "gonum.org/v1/plot/font"
12 "gonum.org/v1/plot/vg"
13 )
14
15
16 type Handler interface {
17
18 Cache() *font.Cache
19
20
21 Extents(fnt font.Font) font.Extents
22
23
24 Lines(txt string) []string
25
26
27
28
29
30 Box(txt string, fnt font.Font) (width, height, depth vg.Length)
31
32
33
34 Draw(c vg.Canvas, txt string, sty Style, pt vg.Point)
35 }
36
37
38
39
40 type XAlignment float64
41
42 const (
43
44 XLeft XAlignment = 0
45
46 XCenter XAlignment = -0.5
47
48 XRight XAlignment = -1
49 )
50
51
52
53
54 type YAlignment float64
55
56 const (
57
58 YTop YAlignment = -1
59
60 YCenter YAlignment = -0.5
61
62 YBottom YAlignment = 0
63 )
64
65
66 const (
67 PosLeft = -1
68 PosBottom = -1
69 PosCenter = 0
70 PosTop = +1
71 PosRight = +1
72 )
73
74
75 type Style struct {
76
77 Color color.Color
78
79
80 Font font.Font
81
82
83
84 Rotation float64
85
86
87 XAlign XAlignment
88 YAlign YAlignment
89
90
91
92
93 Handler Handler
94 }
95
96
97 func (s Style) FontExtents() font.Extents {
98 return s.Handler.Extents(s.Font)
99 }
100
101
102
103 func (s Style) Width(txt string) (max vg.Length) {
104 w, _ := s.box(txt)
105 return w
106 }
107
108
109
110 func (s Style) Height(txt string) vg.Length {
111 _, h := s.box(txt)
112 return h
113 }
114
115
116 func (s Style) box(txt string) (w, h vg.Length) {
117 var (
118 lines = s.Handler.Lines(txt)
119 e = s.FontExtents()
120 linegap = (e.Height - e.Ascent - e.Descent)
121 )
122 for i, line := range lines {
123 ww, hh, dd := s.Handler.Box(line, s.Font)
124 if ww > w {
125 w = ww
126 }
127 h += hh + dd
128 if i > 0 {
129 h += linegap
130 }
131 }
132
133 return w, h
134 }
135
136
137
138 func (s Style) Rectangle(txt string) vg.Rectangle {
139 e := s.Handler.Extents(s.Font)
140 w, h := s.box(txt)
141 desc := vg.Length(e.Height - e.Ascent)
142 xoff := vg.Length(s.XAlign) * w
143 yoff := vg.Length(s.YAlign)*h - desc
144
145
146 p1 := rotatePoint(s.Rotation, vg.Point{X: xoff, Y: yoff})
147
148 p2 := rotatePoint(s.Rotation, vg.Point{X: xoff, Y: h + yoff})
149
150 p3 := rotatePoint(s.Rotation, vg.Point{X: w + xoff, Y: yoff})
151
152 p4 := rotatePoint(s.Rotation, vg.Point{X: w + xoff, Y: h + yoff})
153
154 return vg.Rectangle{
155 Max: vg.Point{
156 X: max(p1.X, p2.X, p3.X, p4.X),
157 Y: max(p1.Y, p2.Y, p3.Y, p4.Y),
158 },
159 Min: vg.Point{
160 X: min(p1.X, p2.X, p3.X, p4.X),
161 Y: min(p1.Y, p2.Y, p3.Y, p4.Y),
162 },
163 }
164 }
165
166
167 func rotatePoint(theta float64, p vg.Point) vg.Point {
168 if theta == 0 {
169 return p
170 }
171 x := float64(p.X)
172 y := float64(p.Y)
173
174 sin, cos := math.Sincos(theta)
175
176 return vg.Point{
177 X: vg.Length(x*cos - y*sin),
178 Y: vg.Length(y*cos + x*sin),
179 }
180 }
181
182 func max(d ...vg.Length) vg.Length {
183 o := vg.Length(math.Inf(-1))
184 for _, dd := range d {
185 if dd > o {
186 o = dd
187 }
188 }
189 return o
190 }
191
192 func min(d ...vg.Length) vg.Length {
193 o := vg.Length(math.Inf(1))
194 for _, dd := range d {
195 if dd < o {
196 o = dd
197 }
198 }
199 return o
200 }
201
View as plain text