github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/textinput/font.go (about) 1 package main 2 3 import ( 4 "image" 5 "path/filepath" 6 7 "github.com/go-gl/gl/v2.1/gl" 8 ) 9 10 type Point struct{ X, Y float32 } 11 12 func (a Point) Add(b Point) Point { 13 return Point{ 14 a.X + b.X, 15 a.Y + b.Y, 16 } 17 } 18 19 type GlyphRect struct { 20 Advance float32 21 Min, Max Point 22 } 23 24 type Font struct { 25 Glyph map[rune]GlyphRect 26 RGBA *image.RGBA 27 Width float32 28 GL uint32 29 } 30 31 type FontInfo struct { 32 Path string 33 Glyphs string 34 Size Point 35 Widths map[float32]string 36 } 37 38 var ArcadeFont = FontInfo{ 39 Path: "arcade_43x74.png", 40 Glyphs: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,;:?!-_~#\"'&()[]|`\\/@°+=*$£€<>%", 41 Size: Point{43, 74}, 42 Widths: map[float32]string{ 43 12: "|", 44 15: "il;:'", 45 16: ".", 46 20: "[]", 47 21: ",`*", 48 25: "j()$<>", 49 27: "°", 50 30: "frt-\"&=", 51 34: "ITYkn01?/+%", 52 36: "_", 53 39: "ABCDEFGHJKLMNOPQRSUVWXZabcdeghmopqsuvwxyz23456789!#\\@£€", 54 41: "~", 55 }, 56 } 57 58 func (info *FontInfo) Load() *Font { 59 m, err := loadImage(filepath.FromSlash(info.Path)) 60 if err != nil { 61 panic(err) 62 } 63 64 widths := map[rune]float32{} 65 for size, runes := range info.Widths { 66 for _, r := range runes { 67 widths[r] = size 68 } 69 } 70 71 font := &Font{} 72 font.Glyph = make(map[rune]GlyphRect) 73 font.RGBA = m 74 font.Width = info.Size.X / info.Size.Y 75 76 sz := m.Bounds().Size() 77 u := Point{info.Size.X / float32(sz.X), info.Size.Y / float32(sz.Y)} 78 dot := Point{0, 0} 79 for _, r := range info.Glyphs { 80 advance := widths[r] / float32(info.Size.Y) 81 if advance == 0 { 82 advance = u.X / u.Y 83 } 84 font.Glyph[r] = GlyphRect{ 85 Advance: advance, 86 Min: dot, 87 Max: dot.Add(u), 88 } 89 90 dot.X += u.X 91 if dot.X+u.X >= 1 { 92 dot.X = 0 93 dot.Y += u.Y 94 } 95 } 96 97 font.upload() 98 return font 99 } 100 101 func (font *Font) upload() { 102 gl.Enable(gl.TEXTURE_2D) 103 gl.GenTextures(1, &font.GL) 104 gl.BindTexture(gl.TEXTURE_2D, font.GL) 105 106 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 107 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 108 109 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 110 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 111 gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE) 112 113 gl.TexImage2D( 114 gl.TEXTURE_2D, 115 0, 116 gl.RGBA, 117 int32(font.RGBA.Rect.Size().X), 118 int32(font.RGBA.Rect.Size().Y), 119 0, 120 gl.RGBA, 121 gl.UNSIGNED_BYTE, 122 gl.Ptr(font.RGBA.Pix)) 123 124 gl.Disable(gl.TEXTURE_2D) 125 } 126 127 func (font *Font) DrawText(p Point, textHeight float32, text string) { 128 gl.ActiveTexture(gl.TEXTURE0) 129 gl.Enable(gl.TEXTURE_2D) 130 gl.BindTexture(gl.TEXTURE_2D, font.GL) 131 { 132 gl.Color4ub(0xff, 0xff, 0xff, 0xff) 133 gl.Begin(gl.QUADS) 134 135 dot := p 136 glyphWidth := font.Width * textHeight 137 for _, r := range text { 138 if r == '\n' { 139 dot.X = p.X 140 dot.Y += textHeight 141 continue 142 } 143 144 g, ok := font.Glyph[r] 145 if !ok { 146 dot.X += glyphWidth 147 } 148 149 gl.TexCoord2f(g.Min.X, g.Min.Y) 150 gl.Vertex2f(dot.X, dot.Y) 151 152 gl.TexCoord2f(g.Max.X, g.Min.Y) 153 gl.Vertex2f(dot.X+glyphWidth, dot.Y) 154 155 gl.TexCoord2f(g.Max.X, g.Max.Y) 156 gl.Vertex2f(dot.X+glyphWidth, dot.Y+textHeight) 157 158 gl.TexCoord2f(g.Min.X, g.Max.Y) 159 gl.Vertex2f(dot.X, dot.Y+textHeight) 160 161 dot.X += textHeight * g.Advance 162 } 163 gl.End() 164 } 165 gl.Disable(gl.TEXTURE_2D) 166 }