aminal/glfont/font.go

315 lines
7.7 KiB
Go

package glfont
import (
"fmt"
"image"
"image/draw"
"io"
"github.com/go-gl/gl/all-core/gl"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
const DPI = 72
// A Font allows rendering of text to an OpenGL context.
type Font struct {
characters map[rune]*character
vao uint32
vbo uint32
program uint32
texture uint32 // Holds the glyph texture id.
color color
ttf *truetype.Font
ttfFace font.Face
scale float32
linePadding float32
lineHeight float32
}
type color struct {
r float32
g float32
b float32
a float32
}
// LoadFont loads the specified font at the given scale.
func LoadFont(reader io.Reader, scale float32, windowWidth int, windowHeight int) (*Font, error) {
// Configure the default font vertex and fragment shaders
program, err := newProgram(vertexFontShader, fragmentFontShader)
if err != nil {
panic(err)
}
// Activate corresponding render state
gl.UseProgram(program)
// set screen resolution
resUniform := gl.GetUniformLocation(program, gl.Str("resolution\x00"))
gl.Uniform2f(resUniform, float32(windowWidth), float32(windowHeight))
gl.UseProgram(0)
return LoadTrueTypeFont(program, reader, scale)
}
func (f *Font) Free() {
for _, chr := range f.characters {
gl.DeleteTextures(1, &chr.textureID)
}
gl.DeleteBuffers(1, &f.vbo)
gl.DeleteVertexArrays(1, &f.vao)
gl.DeleteProgram(f.program)
f.vbo = 0
f.vao = 0
f.program = 0
}
// SetColor allows you to set the text color to be used when you draw the text
func (f *Font) SetColor(red float32, green float32, blue float32, alpha float32) {
f.color.r = red
f.color.g = green
f.color.b = blue
f.color.a = alpha
}
func (f *Font) UpdateResolution(windowWidth int, windowHeight int) {
gl.UseProgram(f.program)
resUniform := gl.GetUniformLocation(f.program, gl.Str("resolution\x00"))
gl.Uniform2f(resUniform, float32(windowWidth), float32(windowHeight))
gl.UseProgram(0)
// f.characters = map[rune]*character{}
}
func (f *Font) LineHeight() float32 {
return f.lineHeight
}
func (f *Font) LinePadding() float32 {
return f.linePadding
}
// Printf draws a string to the screen, takes a list of arguments like printf
func (f *Font) Print(x, y float32, text string) error {
indices := []rune(text)
if len(indices) == 0 {
return nil
}
// setup blending mode
gl.Enable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// Activate corresponding render state
gl.UseProgram(f.program)
// set text color
gl.Uniform4f(gl.GetUniformLocation(f.program, gl.Str("textColor\x00")), f.color.r, f.color.g, f.color.b, f.color.a)
// set screen resolution
// resUniform := gl.GetUniformLocation(f.program, gl.Str("resolution\x00"))
// gl.Uniform2f(resUniform, float32(2560), float32(1440))
gl.ActiveTexture(gl.TEXTURE0)
gl.BindVertexArray(f.vao)
// Iterate through all characters in string
for i := range indices {
// get rune
runeIndex := indices[i]
// find rune in fontChar list
ch, err := f.GetRune(runeIndex)
if err != nil {
return err // @todo ignore errors?
}
// calculate position and size for current rune
xpos := x + float32(ch.bearingH)
ypos := y - float32(+ch.height-ch.bearingV)
w := float32(ch.width)
h := float32(ch.height)
// set quad positions
x1 := xpos
x2 := xpos + w
y1 := ypos
y2 := ypos + h
// setup quad array
vertices := []float32{
// X, Y, Z, U, V
// Front
x1, y1, 0.0, 0.0,
x2, y1, 1.0, 0.0,
x1, y2, 0.0, 1.0,
x1, y2, 0.0, 1.0,
x2, y1, 1.0, 0.0,
x2, y2, 1.0, 1.0,
}
// Render glyph texture over quad
gl.BindTexture(gl.TEXTURE_2D, ch.textureID)
// Update content of VBO memory
gl.BindBuffer(gl.ARRAY_BUFFER, f.vbo)
// BufferSubData(target Enum, offset int, data []byte)
gl.BufferSubData(gl.ARRAY_BUFFER, 0, len(vertices)*4, gl.Ptr(vertices)) // Be sure to use glBufferSubData and not glBufferData
// Render quad
gl.DrawArrays(gl.TRIANGLES, 0, 24)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += float32((ch.advance >> 6)) // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels))
}
// clear opengl textures and programs
gl.BindVertexArray(0)
gl.BindTexture(gl.TEXTURE_2D, 0)
gl.UseProgram(0)
gl.Disable(gl.BLEND)
return nil
}
// Width returns the width of a piece of text in pixels
func (f *Font) Size(text string) (float32, float32) {
var width float32
var height float32
indices := []rune(text)
if len(indices) == 0 {
return 0, 0
}
// Iterate through all characters in string
for i := range indices {
// get rune
runeIndex := indices[i]
// find rune in fontChar list
ch, err := f.GetRune(runeIndex)
if err != nil {
continue
}
// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
width += float32((ch.advance >> 6)) // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels))
// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
if float32(ch.height)*f.scale > height {
height = float32(ch.height)
}
}
return width, height
}
func (f *Font) MaxSize() (float32, float32) {
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
return float32(b.Max.X - b.Min.X), float32(b.Max.Y - b.Min.Y)
}
func (f *Font) MinY() float32 {
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
return float32(b.Min.Y)
}
func (f *Font) MaxY() float32 {
b := f.ttf.Bounds(fixed.Int26_6(f.scale))
return float32(b.Max.Y)
}
func (f *Font) GetRune(r rune) (*character, error) {
cc, ok := f.characters[r]
if ok {
return cc, nil
}
char := new(character)
gBnd, gAdv, ok := f.ttfFace.GlyphBounds(r)
if ok != true {
return nil, fmt.Errorf("ttf face glyphBounds error")
}
gh := int32((gBnd.Max.Y - gBnd.Min.Y) >> 6)
gw := int32((gBnd.Max.X - gBnd.Min.X) >> 6)
// if gylph has no diamensions set to a max value
if gw == 0 || gh == 0 {
gBnd = f.ttf.Bounds(fixed.Int26_6(f.scale))
gw = int32((gBnd.Max.X - gBnd.Min.X) >> 6)
gh = int32((gBnd.Max.Y - gBnd.Min.Y) >> 6)
// above can sometimes yield 0 for font smaller than 48pt, 1 is minimum
if gw == 0 || gh == 0 {
gw = 1
gh = 1
}
}
// The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y.
gAscent := int(-gBnd.Min.Y) >> 6
gdescent := int(gBnd.Max.Y) >> 6
// set w,h and adv, bearing V and bearing H in char
char.width = int(gw)
char.height = int(gh)
char.advance = int(gAdv)
char.bearingV = gdescent
char.bearingH = (int(gBnd.Min.X) >> 6)
// create image to draw glyph
fg, bg := image.White, image.Black
rect := image.Rect(0, 0, int(gw), int(gh))
rgba := image.NewRGBA(rect)
draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src)
// create a freetype context for drawing
c := freetype.NewContext()
c.SetDPI(DPI)
c.SetFont(f.ttf)
c.SetFontSize(float64(f.scale))
c.SetClip(rgba.Bounds())
c.SetDst(rgba)
c.SetSrc(fg)
c.SetHinting(font.HintingFull)
// set the glyph dot
px := 0 - (int(gBnd.Min.X) >> 6)
py := (gAscent)
pt := freetype.Pt(px, py)
// Draw the text from mask to image
if _, err := c.DrawString(string(r), pt); err != nil {
return nil, err
}
// Generate texture
var texture uint32
gl.GenTextures(1, &texture)
gl.BindTexture(gl.TEXTURE_2D, texture)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, int32(rgba.Rect.Dx()), int32(rgba.Rect.Dy()), 0,
gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(rgba.Pix))
char.textureID = texture
f.characters[r] = char
return char, nil
}