1
2
3
4
5 package text
6
7 import (
8 "fmt"
9 "image/color"
10 "math"
11 "strings"
12
13 "github.com/go-latex/latex/drawtex"
14 "github.com/go-latex/latex/font/ttf"
15 "github.com/go-latex/latex/mtex"
16 "github.com/go-latex/latex/tex"
17 stdfnt "golang.org/x/image/font"
18
19 "gonum.org/v1/plot/font"
20 "gonum.org/v1/plot/vg"
21 )
22
23
24 type Latex struct {
25
26 Fonts *font.Cache
27
28
29
30 DPI float64
31 }
32
33 var _ Handler = (*Latex)(nil)
34
35
36 func (hdlr Latex) Cache() *font.Cache {
37 return hdlr.Fonts
38 }
39
40
41 func (hdlr Latex) Extents(fnt font.Font) font.Extents {
42 face := hdlr.Fonts.Lookup(fnt, fnt.Size)
43 return face.Extents()
44 }
45
46
47 func (hdlr Latex) Lines(txt string) []string {
48 txt = strings.TrimRight(txt, "\n")
49 return strings.Split(txt, "\n")
50 }
51
52
53
54
55
56 func (hdlr Latex) Box(txt string, fnt font.Font) (width, height, depth vg.Length) {
57 cnv := drawtex.New()
58 face := hdlr.Fonts.Lookup(fnt, fnt.Size)
59 fnts := hdlr.fontsFor(fnt)
60 box, err := mtex.Parse(txt, face.Font.Size.Points(), latexDPI, ttf.NewFrom(cnv, fnts))
61 if err != nil {
62 panic(fmt.Errorf("could not parse math expression: %w", err))
63 }
64
65 var sh tex.Ship
66 sh.Call(0, 0, box.(tex.Tree))
67
68 width = vg.Length(box.Width())
69 height = vg.Length(box.Height())
70 depth = vg.Length(box.Depth())
71
72
73
74
75 if depth != 0 {
76 var (
77 e = face.Extents()
78 linegap = e.Height - (e.Ascent + e.Descent)
79 )
80 depth += linegap
81 }
82
83 dpi := vg.Length(hdlr.dpi() / latexDPI)
84 return width * dpi, height * dpi, depth * dpi
85 }
86
87
88
89 func (hdlr Latex) Draw(c vg.Canvas, txt string, sty Style, pt vg.Point) {
90 cnv := drawtex.New()
91 face := hdlr.Fonts.Lookup(sty.Font, sty.Font.Size)
92 fnts := hdlr.fontsFor(sty.Font)
93 box, err := mtex.Parse(txt, face.Font.Size.Points(), latexDPI, ttf.NewFrom(cnv, fnts))
94 if err != nil {
95 panic(fmt.Errorf("could not parse math expression: %w", err))
96 }
97
98 var sh tex.Ship
99 sh.Call(0, 0, box.(tex.Tree))
100
101 w := box.Width()
102 h := box.Height()
103 d := box.Depth()
104
105 dpi := hdlr.dpi() / latexDPI
106 o := latex{
107 cnv: c,
108 fonts: hdlr.Fonts,
109 sty: sty,
110 pt: pt,
111 w: vg.Length(w * dpi),
112 h: vg.Length((h + d) * dpi),
113 cos: 1,
114 sin: 0,
115 }
116 e := face.Extents()
117 o.xoff = vg.Length(sty.XAlign) * o.w
118 o.yoff = o.h + o.h*vg.Length(sty.YAlign) - (e.Height - e.Ascent)
119
120 if sty.Rotation != 0 {
121 sin64, cos64 := math.Sincos(sty.Rotation)
122 o.cos = vg.Length(cos64)
123 o.sin = vg.Length(sin64)
124
125 o.cnv.Push()
126 defer o.cnv.Pop()
127 o.cnv.Rotate(sty.Rotation)
128 }
129
130 err = o.Render(w/latexDPI, (h+d)/latexDPI, dpi, cnv)
131 if err != nil {
132 panic(fmt.Errorf("could not render math expression: %w", err))
133 }
134 }
135
136 func (hdlr *Latex) fontsFor(fnt font.Font) *ttf.Fonts {
137 rm := fnt
138 rm.Variant = "Serif"
139 rm.Weight = stdfnt.WeightNormal
140 rm.Style = stdfnt.StyleNormal
141
142 it := rm
143 it.Style = stdfnt.StyleItalic
144
145 bf := rm
146 bf.Style = stdfnt.StyleNormal
147 bf.Weight = stdfnt.WeightBold
148
149 bfit := bf
150 bfit.Style = stdfnt.StyleItalic
151
152 return &ttf.Fonts{
153 Rm: hdlr.Fonts.Lookup(rm, fnt.Size).Face,
154 Default: hdlr.Fonts.Lookup(rm, fnt.Size).Face,
155 It: hdlr.Fonts.Lookup(it, fnt.Size).Face,
156 Bf: hdlr.Fonts.Lookup(bf, fnt.Size).Face,
157 BfIt: hdlr.Fonts.Lookup(bfit, fnt.Size).Face,
158 }
159 }
160
161
162
163
164 const latexDPI = 72.0
165
166 func (hdlr Latex) dpi() float64 {
167 if hdlr.DPI == 0 {
168 return latexDPI
169 }
170 return hdlr.DPI
171 }
172
173 type latex struct {
174 cnv vg.Canvas
175 fonts *font.Cache
176 sty Style
177 pt vg.Point
178
179 w vg.Length
180 h vg.Length
181
182 cos vg.Length
183 sin vg.Length
184
185 xoff vg.Length
186 yoff vg.Length
187 }
188
189 var _ mtex.Renderer = (*latex)(nil)
190
191 func (r *latex) Render(width, height, dpi float64, c *drawtex.Canvas) error {
192 r.cnv.SetColor(r.sty.Color)
193
194 for _, op := range c.Ops() {
195 switch op := op.(type) {
196 case drawtex.GlyphOp:
197 r.drawGlyph(dpi, op)
198 case drawtex.RectOp:
199 r.drawRect(dpi, op)
200 default:
201 panic(fmt.Errorf("unknown drawtex op %T", op))
202 }
203 }
204
205 return nil
206 }
207
208 func (r *latex) drawGlyph(dpi float64, op drawtex.GlyphOp) {
209 pt := r.pt
210 if r.sty.Rotation != 0 {
211 pt.X, pt.Y = r.rotate(pt.X, pt.Y)
212 }
213
214 pt = pt.Add(vg.Point{
215 X: r.xoff + vg.Length(op.X*dpi),
216 Y: r.yoff - vg.Length(op.Y*dpi),
217 })
218
219 fnt := font.Face{
220 Font: font.From(r.sty.Font, vg.Length(op.Glyph.Size)),
221 Face: op.Glyph.Font,
222 }
223 r.cnv.FillString(fnt, pt, op.Glyph.Symbol)
224 }
225
226 func (r *latex) drawRect(dpi float64, op drawtex.RectOp) {
227 x1 := r.xoff + vg.Length(op.X1*dpi)
228 x2 := r.xoff + vg.Length(op.X2*dpi)
229 y1 := r.yoff - vg.Length(op.Y1*dpi)
230 y2 := r.yoff - vg.Length(op.Y2*dpi)
231
232 pt := r.pt
233 if r.sty.Rotation != 0 {
234 pt.X, pt.Y = r.rotate(pt.X, pt.Y)
235 }
236
237 pts := []vg.Point{
238 vg.Point{X: x1, Y: y1}.Add(pt),
239 vg.Point{X: x2, Y: y1}.Add(pt),
240 vg.Point{X: x2, Y: y2}.Add(pt),
241 vg.Point{X: x1, Y: y2}.Add(pt),
242 vg.Point{X: x1, Y: y1}.Add(pt),
243 }
244
245 fillPolygon(r.cnv, r.sty.Color, pts)
246 }
247
248 func (r *latex) rotate(x, y vg.Length) (vg.Length, vg.Length) {
249 u := x*r.cos + y*r.sin
250 v := y*r.cos - x*r.sin
251 return u, v
252 }
253
254
255 func fillPolygon(c vg.Canvas, clr color.Color, pts []vg.Point) {
256 if len(pts) == 0 {
257 return
258 }
259
260 c.SetColor(clr)
261 p := make(vg.Path, 0, len(pts)+1)
262 p.Move(pts[0])
263 for _, pt := range pts[1:] {
264 p.Line(pt)
265 }
266 p.Close()
267 c.Fill(p)
268 }
269
View as plain text