├── README.md ├── font.go ├── go.mod ├── go.sum ├── shader.go └── truetype.go /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/nullboundary/glfont)](https://goreportcard.com/report/github.com/nullboundary/glfont) 2 | 3 | Name : glfont Library 4 | Author : Noah Shibley, http://socialhardware.net 5 | Date : June 16th 2016 6 | Notes : A modern opengl text rendering library for golang 7 | Dependencies: freetype, go-gl, glfw 8 | 9 | *** 10 | # Function List: 11 | 12 | #### func LoadFont 13 | 14 | ```go 15 | func LoadFont(file string, scale int32, windowWidth int, windowHeight int) (*Font, error) 16 | ``` 17 | LoadFont loads the specified font at the given scale. The default character set 18 | is ASCII (codepoints 32 to 127). 19 | 20 | #### func LoadTrueTypeFont 21 | 22 | ```go 23 | func LoadTrueTypeFont(program uint32, r io.Reader, scale int32, low, high rune, dir Direction) (*Font, error) 24 | ``` 25 | LoadTrueTypeFont builds buffers and textures based on a ttf files gylphs. 26 | 27 | #### func LoadFontBytes 28 | 29 | ```go 30 | func LoadFontBytes(buf []byte, scale int32, windowWidth int, windowHeight int) (*Font, error) { 31 | ``` 32 | LoadFontBytes loads font directly from bytes (such as `goregulat.TTF`, https://pkg.go.dev/golang.org/x/image/font/gofont/goregular ) at the given scale. The default character set 33 | is ASCII (codepoints 32 to 127). 34 | 35 | #### func (*Font) GenerateGlyphs 36 | 37 | ```go 38 | func (f *Font) GenerateGlyphs(low, high rune) error 39 | ``` 40 | GenerateGlyphs builds additional glyphs for non-ASCII Unicode codepoints. 41 | 42 | #### func (*Font) Printf 43 | 44 | ```go 45 | func (f *Font) Printf(x, y float32, scale float32, fs string, argv ...interface{}) error 46 | ``` 47 | Printf draws a string to the screen, takes a list of arguments like printf 48 | 49 | #### func (*Font) SetColor 50 | 51 | ```go 52 | func (f *Font) SetColor(red float32, green float32, blue float32, alpha float32) 53 | ``` 54 | SetColor allows you to set the text color to be used when you draw the text 55 | 56 | #### func (f *Font) UpdateResolution 57 | 58 | ```go 59 | func (f *Font) UpdateResolution(windowWidth int, windowHeight int) 60 | ``` 61 | UpdateResolution is needed when the viewport is resized 62 | 63 | #### func (f *Font) Width 64 | 65 | ```go 66 | func (f *Font) Width(scale float32, fs string, argv ...interface{}) float32 67 | ``` 68 | Width returns the width of a piece of text in pixels 69 | 70 | *** 71 | 72 | # Example: 73 | 74 | ```go 75 | 76 | package main 77 | 78 | import ( 79 | "fmt" 80 | "log" 81 | "runtime" 82 | 83 | "github.com/go-gl/gl/all-core/gl" 84 | "github.com/go-gl/glfw/v3.1/glfw" 85 | "github.com/nullboundary/glfont" 86 | ) 87 | 88 | const windowWidth = 1920 89 | const windowHeight = 1080 90 | 91 | func init() { 92 | runtime.LockOSThread() 93 | } 94 | 95 | func main() { 96 | 97 | if err := glfw.Init(); err != nil { 98 | log.Fatalln("failed to initialize glfw:", err) 99 | } 100 | defer glfw.Terminate() 101 | 102 | glfw.WindowHint(glfw.Resizable, glfw.True) 103 | glfw.WindowHint(glfw.ContextVersionMajor, 3) 104 | glfw.WindowHint(glfw.ContextVersionMinor, 2) 105 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 106 | glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True) 107 | 108 | window, _ := glfw.CreateWindow(int(windowWidth), int(windowHeight), "glfontExample", glfw.GetPrimaryMonitor(), nil) 109 | 110 | window.MakeContextCurrent() 111 | glfw.SwapInterval(1) 112 | 113 | if err := gl.Init(); err != nil { 114 | panic(err) 115 | } 116 | 117 | //load font (fontfile, font scale, window width, window height 118 | font, err := glfont.LoadFont("Roboto-Light.ttf", int32(52), windowWidth, windowHeight) 119 | if err != nil { 120 | log.Panicf("LoadFont: %v", err) 121 | } 122 | 123 | gl.Enable(gl.DEPTH_TEST) 124 | gl.DepthFunc(gl.LESS) 125 | gl.ClearColor(0.0, 0.0, 0.0, 0.0) 126 | 127 | for !window.ShouldClose() { 128 | gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) 129 | 130 | //set color and draw text 131 | font.SetColor(1.0, 1.0, 1.0, 1.0) //r,g,b,a font color 132 | font.Printf(100, 100, 1.0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.") //x,y,scale,string,printf args 133 | 134 | window.SwapBuffers() 135 | glfw.PollEvents() 136 | 137 | } 138 | } 139 | ``` 140 | 141 | #### Contributors 142 | 143 | * [kivutar](https://github.com/kivutar) 144 | * [samhocevar](https://github.com/samhocevar) 145 | * [bobiverse](https://github.com/bobiverse) 146 | -------------------------------------------------------------------------------- /font.go: -------------------------------------------------------------------------------- 1 | package glfont 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/go-gl/gl/all-core/gl" 9 | ) 10 | 11 | // Direction represents the direction in which strings should be rendered. 12 | type Direction uint8 13 | 14 | // Known directions. 15 | const ( 16 | LeftToRight Direction = iota // E.g.: Latin 17 | RightToLeft // E.g.: Arabic 18 | TopToBottom // E.g.: Chinese 19 | ) 20 | 21 | type color struct { 22 | r float32 23 | g float32 24 | b float32 25 | a float32 26 | } 27 | 28 | // Use default preapration for exported functions like `LoadFont` and `LoadFontFromBytes` 29 | func configureDefaults(windowWidth int, windowHeight int) uint32 { 30 | // Configure the default font vertex and fragment shaders 31 | program, err := newProgram(vertexFontShader, fragmentFontShader) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | // Activate corresponding render state 37 | gl.UseProgram(program) 38 | 39 | // set screen resolution 40 | resUniform := gl.GetUniformLocation(program, gl.Str("resolution\x00")) 41 | gl.Uniform2f(resUniform, float32(windowWidth), float32(windowHeight)) 42 | 43 | return program 44 | } 45 | 46 | // LoadFontBytes loads the specified font bytes at the given scale. 47 | func LoadFontBytes(buf []byte, scale int32, windowWidth int, windowHeight int) (*Font, error) { 48 | program := configureDefaults(windowWidth, windowHeight) 49 | 50 | fd := bytes.NewReader(buf) 51 | return LoadTrueTypeFont(program, fd, scale, 32, 127, LeftToRight) 52 | } 53 | 54 | // LoadFont loads the specified font at the given scale. 55 | func LoadFont(file string, scale int32, windowWidth int, windowHeight int) (*Font, error) { 56 | fd, err := os.Open(file) 57 | if err != nil { 58 | return nil, err 59 | } 60 | defer fd.Close() 61 | 62 | program := configureDefaults(windowWidth, windowHeight) 63 | 64 | return LoadTrueTypeFont(program, fd, scale, 32, 127, LeftToRight) 65 | } 66 | 67 | // SetColor allows you to set the text color to be used when you draw the text 68 | func (f *Font) SetColor(red float32, green float32, blue float32, alpha float32) { 69 | f.color.r = red 70 | f.color.g = green 71 | f.color.b = blue 72 | f.color.a = alpha 73 | } 74 | 75 | // UpdateResolution used to recalibrate fonts for new window size 76 | func (f *Font) UpdateResolution(windowWidth int, windowHeight int) { 77 | gl.UseProgram(f.program) 78 | resUniform := gl.GetUniformLocation(f.program, gl.Str("resolution\x00")) 79 | gl.Uniform2f(resUniform, float32(windowWidth), float32(windowHeight)) 80 | gl.UseProgram(0) 81 | } 82 | 83 | // Printf draws a string to the screen, takes a list of arguments like printf 84 | func (f *Font) Printf(x, y float32, scale float32, fs string, argv ...interface{}) error { 85 | 86 | indices := []rune(fmt.Sprintf(fs, argv...)) 87 | 88 | if len(indices) == 0 { 89 | return nil 90 | } 91 | 92 | // setup blending mode 93 | gl.Enable(gl.BLEND) 94 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 95 | 96 | // Activate corresponding render state 97 | gl.UseProgram(f.program) 98 | // set text color 99 | gl.Uniform4f(gl.GetUniformLocation(f.program, gl.Str("textColor\x00")), f.color.r, f.color.g, f.color.b, f.color.a) 100 | // set screen resolution 101 | // resUniform := gl.GetUniformLocation(f.program, gl.Str("resolution\x00")) 102 | // gl.Uniform2f(resUniform, float32(2560), float32(1440)) 103 | 104 | gl.ActiveTexture(gl.TEXTURE0) 105 | gl.BindVertexArray(f.vao) 106 | 107 | // Iterate through all characters in string 108 | for i := range indices { 109 | 110 | // get rune 111 | runeIndex := indices[i] 112 | 113 | // find rune in fontChar list 114 | ch, ok := f.fontChar[runeIndex] 115 | 116 | // load missing runes in batches of 32 117 | if !ok { 118 | low := runeIndex - (runeIndex % 32) 119 | f.GenerateGlyphs(low, low+31) 120 | ch, ok = f.fontChar[runeIndex] 121 | } 122 | 123 | // skip runes that are not in font chacter range 124 | if !ok { 125 | fmt.Printf("%c %d\n", runeIndex, runeIndex) 126 | continue 127 | } 128 | 129 | // calculate position and size for current rune 130 | xpos := x + float32(ch.bearingH)*scale 131 | ypos := y - float32(ch.height-ch.bearingV)*scale 132 | w := float32(ch.width) * scale 133 | h := float32(ch.height) * scale 134 | vertices := []float32{ 135 | xpos + w, ypos, 1.0, 0.0, 136 | xpos, ypos, 0.0, 0.0, 137 | xpos, ypos + h, 0.0, 1.0, 138 | 139 | xpos, ypos + h, 0.0, 1.0, 140 | xpos + w, ypos + h, 1.0, 1.0, 141 | xpos + w, ypos, 1.0, 0.0, 142 | } 143 | 144 | // Render glyph texture over quad 145 | gl.BindTexture(gl.TEXTURE_2D, ch.textureID) 146 | // Update content of VBO memory 147 | gl.BindBuffer(gl.ARRAY_BUFFER, f.vbo) 148 | 149 | // BufferSubData(target Enum, offset int, data []byte) 150 | gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(vertices)*4, gl.Ptr(vertices)) // Be sure to use glBufferSubData and not glBufferData 151 | // Render quad 152 | gl.DrawArrays(gl.TRIANGLES, 0, 16) 153 | 154 | gl.BindBuffer(gl.ARRAY_BUFFER, 0) 155 | // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) 156 | x += float32((ch.advance >> 6)) * scale // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels)) 157 | 158 | } 159 | 160 | // clear opengl textures and programs 161 | gl.BindVertexArray(0) 162 | gl.BindTexture(gl.TEXTURE_2D, 0) 163 | gl.UseProgram(0) 164 | gl.Disable(gl.BLEND) 165 | 166 | return nil 167 | } 168 | 169 | // Width returns the width of a piece of text in pixels 170 | func (f *Font) Width(scale float32, fs string, argv ...interface{}) float32 { 171 | 172 | var width float32 173 | 174 | indices := []rune(fmt.Sprintf(fs, argv...)) 175 | 176 | if len(indices) == 0 { 177 | return 0 178 | } 179 | 180 | // Iterate through all characters in string 181 | for i := range indices { 182 | 183 | // get rune 184 | runeIndex := indices[i] 185 | 186 | // find rune in fontChar list 187 | ch, ok := f.fontChar[runeIndex] 188 | 189 | // load missing runes in batches of 32 190 | if !ok { 191 | low := runeIndex & rune(32-1) 192 | f.GenerateGlyphs(low, low+31) 193 | ch, ok = f.fontChar[runeIndex] 194 | } 195 | 196 | // skip runes that are not in font chacter range 197 | if !ok { 198 | fmt.Printf("%c %d\n", runeIndex, runeIndex) 199 | continue 200 | } 201 | 202 | // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) 203 | width += float32((ch.advance >> 6)) * scale // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels)) 204 | 205 | } 206 | 207 | return width 208 | } 209 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nullboundary/glfont 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 7 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 8 | golang.org/x/image v0.3.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= 2 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 3 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 4 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 5 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 6 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 7 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 8 | golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg= 9 | golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A= 10 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 11 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 12 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 13 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 14 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 15 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 16 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 17 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 18 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 19 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 20 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 21 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 22 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 23 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 24 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 25 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 26 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 27 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 28 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 29 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 30 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 31 | -------------------------------------------------------------------------------- /shader.go: -------------------------------------------------------------------------------- 1 | package glfont 2 | 3 | import ( 4 | "github.com/go-gl/gl/all-core/gl" 5 | 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | //newProgram links the frag and vertex shader programs 11 | func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error) { 12 | vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER) 13 | if err != nil { 14 | return 0, err 15 | } 16 | 17 | fragmentShader, err := compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER) 18 | if err != nil { 19 | return 0, err 20 | } 21 | 22 | program := gl.CreateProgram() 23 | 24 | gl.AttachShader(program, vertexShader) 25 | gl.AttachShader(program, fragmentShader) 26 | gl.LinkProgram(program) 27 | 28 | var status int32 29 | gl.GetProgramiv(program, gl.LINK_STATUS, &status) 30 | if status == gl.FALSE { 31 | var logLength int32 32 | gl.GetProgramiv(program, gl.INFO_LOG_LENGTH, &logLength) 33 | 34 | log := strings.Repeat("\x00", int(logLength+1)) 35 | gl.GetProgramInfoLog(program, logLength, nil, gl.Str(log)) 36 | 37 | return 0, fmt.Errorf("failed to link program: %v", log) 38 | } 39 | 40 | gl.DeleteShader(vertexShader) 41 | gl.DeleteShader(fragmentShader) 42 | 43 | return program, nil 44 | } 45 | 46 | //compileShader compiles the shader program 47 | func compileShader(source string, shaderType uint32) (uint32, error) { 48 | shader := gl.CreateShader(shaderType) 49 | 50 | csources, free := gl.Strs(source) 51 | gl.ShaderSource(shader, 1, csources, nil) 52 | free() 53 | gl.CompileShader(shader) 54 | 55 | var status int32 56 | gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status) 57 | if status == gl.FALSE { 58 | var logLength int32 59 | gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLength) 60 | 61 | log := strings.Repeat("\x00", int(logLength+1)) 62 | gl.GetShaderInfoLog(shader, logLength, nil, gl.Str(log)) 63 | 64 | return 0, fmt.Errorf("failed to compile %v: %v", source, log) 65 | } 66 | 67 | return shader, nil 68 | } 69 | 70 | var fragmentFontShader = `#version 150 core 71 | in vec2 fragTexCoord; 72 | out vec4 outputColor; 73 | 74 | uniform sampler2D tex; 75 | uniform vec4 textColor; 76 | 77 | void main() 78 | { 79 | vec4 sampled = vec4(1.0, 1.0, 1.0, texture(tex, fragTexCoord).r); 80 | outputColor = textColor * sampled; 81 | }` + "\x00" 82 | 83 | var vertexFontShader = `#version 150 core 84 | 85 | //vertex position 86 | in vec2 vert; 87 | 88 | //pass through to fragTexCoord 89 | in vec2 vertTexCoord; 90 | 91 | //window res 92 | uniform vec2 resolution; 93 | 94 | //pass to frag 95 | out vec2 fragTexCoord; 96 | 97 | void main() { 98 | // convert the rectangle from pixels to 0.0 to 1.0 99 | vec2 zeroToOne = vert / resolution; 100 | 101 | // convert from 0->1 to 0->2 102 | vec2 zeroToTwo = zeroToOne * 2.0; 103 | 104 | // convert from 0->2 to -1->+1 (clipspace) 105 | vec2 clipSpace = zeroToTwo - 1.0; 106 | 107 | fragTexCoord = vertTexCoord; 108 | 109 | gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); 110 | }` + "\x00" 111 | -------------------------------------------------------------------------------- /truetype.go: -------------------------------------------------------------------------------- 1 | package glfont 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-gl/gl/all-core/gl" 6 | "github.com/golang/freetype" 7 | "github.com/golang/freetype/truetype" 8 | "golang.org/x/image/font" 9 | "golang.org/x/image/math/fixed" 10 | "image" 11 | "image/draw" 12 | "io" 13 | "io/ioutil" 14 | ) 15 | 16 | // A Font allows rendering of text to an OpenGL context. 17 | type Font struct { 18 | fontChar map[rune]*character 19 | ttf *truetype.Font 20 | scale int32 21 | vao uint32 22 | vbo uint32 23 | program uint32 24 | texture uint32 // Holds the glyph texture id. 25 | color color 26 | } 27 | 28 | type character struct { 29 | textureID uint32 // ID handle of the glyph texture 30 | width int //glyph width 31 | height int //glyph height 32 | advance int //glyph advance 33 | bearingH int //glyph bearing horizontal 34 | bearingV int //glyph bearing vertical 35 | } 36 | 37 | //GenerateGlyphs builds a set of textures based on a ttf files gylphs 38 | func (f *Font) GenerateGlyphs(low, high rune) error { 39 | //create a freetype context for drawing 40 | c := freetype.NewContext() 41 | c.SetDPI(72) 42 | c.SetFont(f.ttf) 43 | c.SetFontSize(float64(f.scale)) 44 | c.SetHinting(font.HintingFull) 45 | 46 | //create new face to measure glyph dimensions 47 | ttfFace := truetype.NewFace(f.ttf, &truetype.Options{ 48 | Size: float64(f.scale), 49 | DPI: 72, 50 | Hinting: font.HintingFull, 51 | }) 52 | 53 | //make each gylph 54 | for ch := low; ch <= high; ch++ { 55 | char := new(character) 56 | 57 | gBnd, gAdv, ok := ttfFace.GlyphBounds(ch) 58 | if ok != true { 59 | return fmt.Errorf("ttf face glyphBounds error") 60 | } 61 | 62 | gh := int32((gBnd.Max.Y - gBnd.Min.Y) >> 6) 63 | gw := int32((gBnd.Max.X - gBnd.Min.X) >> 6) 64 | 65 | //if gylph has no dimensions set to a max value 66 | if gw == 0 || gh == 0 { 67 | gBnd = f.ttf.Bounds(fixed.Int26_6(f.scale)) 68 | gw = int32((gBnd.Max.X - gBnd.Min.X) >> 6) 69 | gh = int32((gBnd.Max.Y - gBnd.Min.Y) >> 6) 70 | 71 | //above can sometimes yield 0 for font smaller than 48pt, 1 is minimum 72 | if gw == 0 || gh == 0 { 73 | gw = 1 74 | gh = 1 75 | } 76 | } 77 | 78 | //The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y. 79 | gAscent := int(-gBnd.Min.Y) >> 6 80 | gdescent := int(gBnd.Max.Y) >> 6 81 | 82 | //set w,h and adv, bearing V and bearing H in char 83 | char.width = int(gw) 84 | char.height = int(gh) 85 | char.advance = int(gAdv) 86 | char.bearingV = gdescent 87 | char.bearingH = (int(gBnd.Min.X) >> 6) 88 | 89 | //create image to draw glyph 90 | fg, bg := image.White, image.Black 91 | rect := image.Rect(0, 0, int(gw), int(gh)) 92 | rgba := image.NewRGBA(rect) 93 | draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) 94 | 95 | //set the glyph dot 96 | px := 0 - (int(gBnd.Min.X) >> 6) 97 | py := (gAscent) 98 | pt := freetype.Pt(px, py) 99 | 100 | // Draw the text from mask to image 101 | c.SetClip(rgba.Bounds()) 102 | c.SetDst(rgba) 103 | c.SetSrc(fg) 104 | _, err := c.DrawString(string(ch), pt) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | // Generate texture 110 | var texture uint32 111 | gl.GenTextures(1, &texture) 112 | gl.BindTexture(gl.TEXTURE_2D, texture) 113 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) 114 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) 115 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 116 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) 117 | gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(rgba.Rect.Dx()), int32(rgba.Rect.Dy()), 0, 118 | gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(rgba.Pix)) 119 | 120 | char.textureID = texture 121 | 122 | //add char to fontChar list 123 | f.fontChar[ch] = char 124 | } 125 | 126 | gl.BindTexture(gl.TEXTURE_2D, 0) 127 | return nil 128 | } 129 | 130 | //LoadTrueTypeFont builds OpenGL buffers and glyph textures based on a ttf file 131 | func LoadTrueTypeFont(program uint32, r io.Reader, scale int32, low, high rune, dir Direction) (*Font, error) { 132 | data, err := ioutil.ReadAll(r) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | // Read the truetype font. 138 | ttf, err := truetype.Parse(data) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | //make Font stuct type 144 | f := new(Font) 145 | f.fontChar = make(map[rune]*character) 146 | f.ttf = ttf 147 | f.scale = scale 148 | f.program = program //set shader program 149 | f.SetColor(1.0, 1.0, 1.0, 1.0) //set default white 150 | 151 | err = f.GenerateGlyphs(low, high) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | // Configure VAO/VBO for texture quads 157 | gl.GenVertexArrays(1, &f.vao) 158 | gl.GenBuffers(1, &f.vbo) 159 | gl.BindVertexArray(f.vao) 160 | gl.BindBuffer(gl.ARRAY_BUFFER, f.vbo) 161 | 162 | gl.BufferData(gl.ARRAY_BUFFER, 6*4*4, nil, gl.STATIC_DRAW) 163 | 164 | vertAttrib := uint32(gl.GetAttribLocation(f.program, gl.Str("vert\x00"))) 165 | gl.EnableVertexAttribArray(vertAttrib) 166 | gl.VertexAttribPointer(vertAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(0)) 167 | defer gl.DisableVertexAttribArray(vertAttrib) 168 | 169 | texCoordAttrib := uint32(gl.GetAttribLocation(f.program, gl.Str("vertTexCoord\x00"))) 170 | gl.EnableVertexAttribArray(texCoordAttrib) 171 | gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, 4*4, gl.PtrOffset(2*4)) 172 | defer gl.DisableVertexAttribArray(texCoordAttrib) 173 | 174 | gl.BindBuffer(gl.ARRAY_BUFFER, 0) 175 | gl.BindVertexArray(0) 176 | 177 | return f, nil 178 | } 179 | --------------------------------------------------------------------------------