1package e2e23import (4 "image"5 "image/color"6 "math"78 "golang.org/x/image/vector"9)1011// shape builds a filled outline for the rasterizer. Every polygon is12// wound the same way, so overlapping parts add up rather than cancel,13// except holes, wound the other way.14type shape struct {15 z *vector.Rasterizer16}1718type pt = [2]float321920// poly adds a closed polygon; hole winds it the other way.21func (s *shape) poly(pts []pt, hole bool) {22 var area float3223 for i, p := range pts {24 q := pts[(i+1)%len(pts)]25 area += p[0]*q[1] - q[0]*p[1]26 }27 if (area < 0) != hole {28 for i, j := 0, len(pts)-1; i < j; i, j = i+1, j-1 {29 pts[i], pts[j] = pts[j], pts[i]30 }31 }32 s.z.MoveTo(pts[0][0], pts[0][1])33 for _, p := range pts[1:] {34 s.z.LineTo(p[0], p[1])35 }36 s.z.ClosePath()37}3839// arc is n+1 points along a circle's arc from a0 to a1 (radians, y down).40func arc(cx, cy, r, a0, a1 float32, n int) []pt {41 pts := make([]pt, 0, n+1)42 for i := 0; i <= n; i++ {43 a := float64(a0 + (a1-a0)*float32(i)/float32(n))44 pts = append(pts, pt{cx + r*float32(math.Cos(a)), cy + r*float32(math.Sin(a))})45 }46 return pts47}4849func (s *shape) disk(cx, cy, r float32) {50 pts := arc(cx, cy, r, 0, 2*math.Pi, 32)51 s.poly(pts[:32], false)52}5354// ring is a circle's outline t wide, centered on radius r.55func (s *shape) ring(cx, cy, r, t float32) {56 outer := arc(cx, cy, r+t/2, 0, 2*math.Pi, 32)57 inner := arc(cx, cy, r-t/2, 0, 2*math.Pi, 32)58 s.poly(outer[:32], false)59 s.poly(inner[:32], true)60}6162// pie is the slice of a disk from a0 to a1.63func (s *shape) pie(cx, cy, r, a0, a1 float32) {64 s.poly(append([]pt{{cx, cy}}, arc(cx, cy, r, a0, a1, 24)...), false)65}6667// seg is a straight line t wide from a to b.68func (s *shape) seg(a, b pt, t float32) {69 dx, dy := b[0]-a[0], b[1]-a[1]70 l := float32(math.Hypot(float64(dx), float64(dy)))71 if l == 0 {72 return73 }74 nx, ny := -dy/l*t/2, dx/l*t/275 s.poly([]pt{{a[0] + nx, a[1] + ny}, {b[0] + nx, b[1] + ny}, {b[0] - nx, b[1] - ny}, {a[0] - nx, a[1] - ny}}, false)76}7778// stroke is a line through pts, t wide, with round joins and ends.79func (s *shape) stroke(pts []pt, t float32) {80 for i := range pts {81 if i > 0 {82 s.seg(pts[i-1], pts[i], t)83 }84 s.disk(pts[i][0], pts[i][1], t/2)85 }86}8788// roundRect is a rectangle with corners of radius r.89func (s *shape) roundRect(x0, y0, x1, y1, r float32) {90 var pts []pt91 pts = append(pts, arc(x1-r, y0+r, r, -math.Pi/2, 0, 8)...)92 pts = append(pts, arc(x1-r, y1-r, r, 0, math.Pi/2, 8)...)93 pts = append(pts, arc(x0+r, y1-r, r, math.Pi/2, math.Pi, 8)...)94 pts = append(pts, arc(x0+r, y0+r, r, math.Pi, 3*math.Pi/2, 8)...)95 s.poly(pts, false)96}9798// symbol draws in a square the cell's width, centered on the cell:99// (u, v) from 0 to 1 across it.100type symbol struct {101 *shape102 x0, y0, side float32103}104105func (y symbol) p(u, v float32) pt { return pt{y.x0 + u*y.side, y.y0 + v*y.side} }106107func (y symbol) tri(a, b, c pt) {108 y.poly([]pt{y.p(a[0], a[1]), y.p(b[0], b[1]), y.p(c[0], c[1])}, false)109}110111func (y symbol) line(t float32, uv ...float32) {112 var pts []pt113 for i := 0; i+1 < len(uv); i += 2 {114 pts = append(pts, y.p(uv[i], uv[i+1]))115 }116 y.stroke(pts, t*y.side)117}118119func (y symbol) box(u0, v0, u1, v1, t float32) {120 y.line(t, u0, v0, u1, v0, u1, v1, u0, v1, u0, v0)121}122123func (y symbol) rect(u0, v0, u1, v1 float32) {124 y.poly([]pt{y.p(u0, v0), y.p(u1, v0), y.p(u1, v1), y.p(u0, v1)}, false)125}126127// symbols are the shapes 012 draws its chrome with that fonts draw at128// odd sizes or not at all: triangles, circles, checks and the notebook129// toolbar's icons.130var symbols = map[rune]func(y symbol){131 '▶': func(y symbol) { y.tri(pt{.2, .12}, pt{.92, .5}, pt{.2, .88}) },132 '▸': func(y symbol) { y.tri(pt{.3, .28}, pt{.76, .5}, pt{.3, .72}) },133 '◀': func(y symbol) { y.tri(pt{.8, .12}, pt{.08, .5}, pt{.8, .88}) },134 '◂': func(y symbol) { y.tri(pt{.7, .28}, pt{.24, .5}, pt{.7, .72}) },135 '▼': func(y symbol) { y.tri(pt{.08, .22}, pt{.92, .22}, pt{.5, .86}) },136 '▾': func(y symbol) { y.tri(pt{.22, .34}, pt{.78, .34}, pt{.5, .7}) },137 '▲': func(y symbol) { y.tri(pt{.08, .8}, pt{.92, .8}, pt{.5, .16}) },138 '▴': func(y symbol) { y.tri(pt{.22, .68}, pt{.78, .68}, pt{.5, .32}) },139 '■': func(y symbol) { y.rect(.14, .14, .86, .86) },140 '◆': func(y symbol) { y.poly([]pt{y.p(.5, .1), y.p(.9, .5), y.p(.5, .9), y.p(.1, .5)}, false) },141 '◇': func(y symbol) { y.line(.1, .5, .12, .88, .5, .5, .88, .12, .5, .5, .12) },142 '●': func(y symbol) { y.disk(y.p(.5, .5)[0], y.p(.5, .5)[1], .4*y.side) },143 '○': func(y symbol) { circle(y, 0, 0) },144 '◔': func(y symbol) { circle(y, -math.Pi/2, 0) },145 '◑': func(y symbol) { circle(y, -math.Pi/2, math.Pi/2) },146 '◕': func(y symbol) { circle(y, -math.Pi/2, math.Pi) },147 '✓': func(y symbol) { y.line(.12, .14, .54, .4, .8, .88, .2) },148 '✗': func(y symbol) { y.line(.12, .2, .2, .8, .8); y.line(.12, .8, .2, .2, .8) },149 '❯': func(y symbol) { y.line(.16, .3, .16, .72, .5, .3, .84) },150 '▦': func(y symbol) {151 y.box(.1, .1, .9, .9, .08)152 y.line(.06, .1, .37, .9, .37)153 y.line(.06, .1, .63, .9, .63)154 y.line(.06, .37, .1, .37, .9)155 y.line(.06, .63, .1, .63, .9)156 },157 '⧉': func(y symbol) { y.box(.08, .08, .64, .64, .08); y.box(.36, .36, .92, .92, .08) },158 '⎘': func(y symbol) { y.box(.16, .16, .84, .94, .08); y.rect(.34, .04, .66, .26) },159 '↻': reload,160 '↵': func(y symbol) {161 y.line(.09, .84, .12, .84, .62, .3, .62)162 y.tri(pt{.06, .62}, pt{.36, .38}, pt{.36, .86})163 },164 '✂': func(y symbol) {165 y.ring(y.p(.22, .26)[0], y.p(.22, .26)[1], .13*y.side, .08*y.side)166 y.ring(y.p(.22, .74)[0], y.p(.22, .74)[1], .13*y.side, .08*y.side)167 y.line(.09, .32, .34, .94, .7)168 y.line(.09, .32, .66, .94, .3)169 },170}171172// circle is an outline with the slice from a0 to a1 filled.173func circle(y symbol, a0, a1 float32) {174 c := y.p(.5, .5)175 y.ring(c[0], c[1], .36*y.side, .09*y.side)176 if a1 > a0 {177 y.pie(c[0], c[1], .38*y.side, a0, a1)178 }179}180181// reload is ↻: most of a circle, clockwise, with the arrowhead at its182// end.183func reload(y symbol) {184 c := y.p(.5, .52)185 r := .32 * y.side186 a0, a1 := float32(-math.Pi/5), float32(1.4*math.Pi)187 y.stroke(arc(c[0], c[1], r, a0, a1, 24), .09*y.side)188 end := pt{c[0] + r*float32(math.Cos(float64(a1))), c[1] + r*float32(math.Sin(float64(a1)))}189 dir := pt{-float32(math.Sin(float64(a1))), float32(math.Cos(float64(a1)))}190 n := pt{-dir[1], dir[0]}191 h := .22 * y.side192 y.poly([]pt{193 {end[0] + dir[0]*h, end[1] + dir[1]*h},194 {end[0] + n[0]*h*.8, end[1] + n[1]*h*.8},195 {end[0] - n[0]*h*.8, end[1] - n[1]*h*.8},196 }, false)197}198199// drawSpecial draws r as terminals draw it rather than from the font:200// box drawing, blocks and braille fill the cell and join their201// neighbors, and symbols are drawn at one size. False when r is text.202func drawSpecial(img *image.NRGBA, r rune, cell image.Rectangle, c color.RGBA, fs *stillFaces) bool {203 switch {204 case r >= 0x2500 && r <= 0x257f:205 return boxDrawing(img, r, cell, c)206 case r >= 0x2580 && r <= 0x259f:207 return blockElement(img, r, cell, c)208 case r >= 0x2800 && r <= 0x28ff:209 braille(img, r, cell, c)210 return true211 }212 draw, ok := symbols[r]213 if !ok {214 return false215 }216 // A symbol is as big as a capital letter, centered on one, and may217 // reach a little into the cells beside it, as fonts draw them.218 side := float32(stillFontSize) * .9219 area := cell.Inset(-cell.Dx() / 4)220 mid := float32(fs.ascent+cell.Dx()/4) - float32(stillFontSize)*.33221 fillShape(img, area, c, func(s *shape) {222 draw(symbol{shape: s, x0: (float32(area.Dx()) - side) / 2, y0: mid - side/2, side: side})223 })224 return true225}226227// braille draws the dots of a braille pattern, two columns of four.228func braille(img *image.NRGBA, r rune, cell image.Rectangle, c color.RGBA) {229 bits := int(r - 0x2800)230 // dots 1-3 and 7 down the left, 4-6 and 8 down the right231 pos := [8][2]int{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {0, 3}, {1, 3}}232 w, h := float32(cell.Dx()), float32(cell.Dy())233 fillShape(img, cell, c, func(s *shape) {234 for i, p := range pos {235 if bits&(1<<i) != 0 {236 s.disk(w*(.27+.46*float32(p[0])), h*(.14+.24*float32(p[1])), w*.14)237 }238 }239 })240}