mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 14:38:19 +00:00
Proper character positioning and scale
This commit is contained in:
BIN
atlas.png
BIN
atlas.png
Binary file not shown.
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 344 KiB |
4
go.mod
4
go.mod
@ -3,7 +3,9 @@ module github.com/bloeys/nterm
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bloeys/gglm v0.3.1
|
||||||
github.com/bloeys/nmage v0.11.12
|
github.com/bloeys/nmage v0.11.12
|
||||||
|
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/veandco/go-sdl2 v0.4.10
|
github.com/veandco/go-sdl2 v0.4.10
|
||||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82
|
golang.org/x/image v0.0.0-20220617043117-41969df76e82
|
||||||
@ -11,7 +13,5 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bloeys/assimp-go v0.4.2 // indirect
|
github.com/bloeys/assimp-go v0.4.2 // indirect
|
||||||
github.com/bloeys/gglm v0.3.1 // indirect
|
|
||||||
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 // indirect
|
|
||||||
github.com/inkyblackness/imgui-go/v4 v4.3.0 // indirect
|
github.com/inkyblackness/imgui-go/v4 v4.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
79
main.go
79
main.go
@ -57,13 +57,18 @@ func main() {
|
|||||||
type FontTexAtlas struct {
|
type FontTexAtlas struct {
|
||||||
Img *image.RGBA
|
Img *image.RGBA
|
||||||
Glyphs map[rune]FontTexAtlasGlyph
|
Glyphs map[rune]FontTexAtlasGlyph
|
||||||
GlyphSizeU float32
|
LineHeight int
|
||||||
GlyphSizeV float32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FontTexAtlasGlyph struct {
|
type FontTexAtlasGlyph struct {
|
||||||
U float32
|
U float32
|
||||||
V float32
|
V float32
|
||||||
|
SizeU float32
|
||||||
|
SizeV float32
|
||||||
|
|
||||||
|
Ascent float32
|
||||||
|
Descent float32
|
||||||
|
Advance float32
|
||||||
}
|
}
|
||||||
|
|
||||||
var atlas *FontTexAtlas
|
var atlas *FontTexAtlas
|
||||||
@ -80,7 +85,7 @@ func (p *program) Init() {
|
|||||||
panic("Failed to parse font. Err: " + err.Error())
|
panic("Failed to parse font. Err: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
pointSize := 40
|
pointSize := 64
|
||||||
face := truetype.NewFace(f, &truetype.Options{Size: float64(pointSize), DPI: 72})
|
face := truetype.NewFace(f, &truetype.Options{Size: float64(pointSize), DPI: 72})
|
||||||
atlas = genTextureAtlas(f, face, pointSize)
|
atlas = genTextureAtlas(f, face, pointSize)
|
||||||
}
|
}
|
||||||
@ -121,8 +126,7 @@ func genTextureAtlas(f *truetype.Font, face font.Face, textSize int) *FontTexAtl
|
|||||||
atlas := &FontTexAtlas{
|
atlas := &FontTexAtlas{
|
||||||
Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)),
|
Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)),
|
||||||
Glyphs: make(map[rune]FontTexAtlasGlyph, len(glyphs)),
|
Glyphs: make(map[rune]FontTexAtlasGlyph, len(glyphs)),
|
||||||
GlyphSizeU: float32(charWidth) / float32(atlasSizeX),
|
LineHeight: lineHeight,
|
||||||
GlyphSizeV: float32(lineHeight) / float32(atlasSizeY),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Clear background to black
|
//Clear background to black
|
||||||
@ -140,9 +144,24 @@ func genTextureAtlas(f *truetype.Font, face font.Face, textSize int) *FontTexAtl
|
|||||||
drawer.Dot = fixed.P(0, lineHeight)
|
drawer.Dot = fixed.P(0, lineHeight)
|
||||||
for _, g := range glyphs {
|
for _, g := range glyphs {
|
||||||
|
|
||||||
|
gBounds, gAdvanceFixed, _ := face.GlyphBounds(g)
|
||||||
|
|
||||||
|
descent := gBounds.Max.Y
|
||||||
|
advanceRounded := gAdvanceFixed.Round()
|
||||||
|
ascent := -gBounds.Min.Y
|
||||||
|
|
||||||
|
heightRounded := (ascent + descent).Round()
|
||||||
|
|
||||||
atlas.Glyphs[g] = FontTexAtlasGlyph{
|
atlas.Glyphs[g] = FontTexAtlasGlyph{
|
||||||
U: float32(drawer.Dot.X.Floor()) / float32(atlasSizeX),
|
U: float32(drawer.Dot.X.Round()) / float32(atlasSizeX),
|
||||||
V: (float32(atlasSizeY-drawer.Dot.Y.Floor()) / float32(atlasSizeY)),
|
V: (float32(atlasSizeY-(drawer.Dot.Y+descent).Round()) / float32(atlasSizeY)),
|
||||||
|
|
||||||
|
SizeU: float32(advanceRounded) / float32(atlasSizeX),
|
||||||
|
SizeV: (float32(heightRounded) / float32(atlasSizeY)),
|
||||||
|
|
||||||
|
Ascent: float32(ascent.Round()),
|
||||||
|
Descent: float32(descent.Round()),
|
||||||
|
Advance: float32(advanceRounded),
|
||||||
}
|
}
|
||||||
drawer.DrawString(string(g))
|
drawer.DrawString(string(g))
|
||||||
|
|
||||||
@ -190,7 +209,7 @@ func (p *program) Update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) Render() {
|
func (p *program) Render() {
|
||||||
p.drawTextOpenGL(atlas, "Hello friend", gglm.NewVec3(-9, 0, 0))
|
p.drawTextOpenGL(atlas, "Hello friend.\nHow are you?", gglm.NewVec3(0, 100, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) FrameEnd() {
|
func (p *program) FrameEnd() {
|
||||||
@ -263,7 +282,7 @@ func getGlyphsFromRanges(ranges [][2]rune) []rune {
|
|||||||
var glyphMesh *meshes.Mesh
|
var glyphMesh *meshes.Mesh
|
||||||
var glyphMat *materials.Material
|
var glyphMat *materials.Material
|
||||||
|
|
||||||
func (p *program) drawTextOpenGL(atlas *FontTexAtlas, text string, pos *gglm.Vec3) {
|
func (p *program) drawTextOpenGL(atlas *FontTexAtlas, text string, startPos *gglm.Vec3) {
|
||||||
|
|
||||||
if glyphMesh == nil {
|
if glyphMesh == nil {
|
||||||
glyphMesh = &meshes.Mesh{
|
glyphMesh = &meshes.Mesh{
|
||||||
@ -321,26 +340,28 @@ func (p *program) drawTextOpenGL(atlas *FontTexAtlas, text string, pos *gglm.Vec
|
|||||||
//Prepare to draw
|
//Prepare to draw
|
||||||
glyphMesh.Buf.Bind()
|
glyphMesh.Buf.Bind()
|
||||||
|
|
||||||
glyphMat.DiffuseTex = atlasTex.TexID
|
//The projection matrix fits the screen size. This is needed so we can size and position characters correctly.
|
||||||
glyphMat.SetAttribute(glyphMesh.Buf)
|
w, h := p.win.SDLWin.GetSize()
|
||||||
|
projMtx := gglm.Ortho(0, float32(w), float32(h), 0, 0.1, 10)
|
||||||
projMtx := gglm.Ortho(-10, 10, 10, -10, 0.1, 10)
|
|
||||||
viewMat := gglm.LookAt(gglm.NewVec3(0, 0, -1), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0))
|
viewMat := gglm.LookAt(gglm.NewVec3(0, 0, -1), gglm.NewVec3(0, 0, 0), gglm.NewVec3(0, 1, 0))
|
||||||
|
|
||||||
|
glyphMat.DiffuseTex = atlasTex.TexID
|
||||||
|
glyphMat.SetAttribute(glyphMesh.Buf)
|
||||||
glyphMat.SetUnifMat4("projMat", &projMtx.Mat4)
|
glyphMat.SetUnifMat4("projMat", &projMtx.Mat4)
|
||||||
glyphMat.SetUnifMat4("viewMat", &viewMat.Mat4)
|
glyphMat.SetUnifMat4("viewMat", &viewMat.Mat4)
|
||||||
glyphMat.Bind()
|
glyphMat.Bind()
|
||||||
|
|
||||||
|
//Draw
|
||||||
|
pos := startPos.Clone()
|
||||||
rs := []rune(text)
|
rs := []rune(text)
|
||||||
tr := gglm.NewTrMatId()
|
|
||||||
tr.Translate(pos)
|
|
||||||
tr.Scale(gglm.NewVec3(1, 1, 1))
|
|
||||||
for i := 0; i < len(rs); i++ {
|
for i := 0; i < len(rs); i++ {
|
||||||
|
|
||||||
r := rs[i]
|
r := rs[i]
|
||||||
g := atlas.Glyphs[r]
|
g := atlas.Glyphs[r]
|
||||||
if g.U < 0 {
|
if r == '\n' {
|
||||||
print(g.U)
|
startPos.SetY(startPos.Y() - float32(atlas.LineHeight))
|
||||||
|
pos = startPos.Clone()
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
glyphMesh.Buf.SetData([]float32{
|
glyphMesh.Buf.SetData([]float32{
|
||||||
@ -349,22 +370,32 @@ func (p *program) drawTextOpenGL(atlas *FontTexAtlas, text string, pos *gglm.Vec
|
|||||||
1, 1, 1, 1,
|
1, 1, 1, 1,
|
||||||
|
|
||||||
0.5, -0.5, 0,
|
0.5, -0.5, 0,
|
||||||
g.U + atlas.GlyphSizeU, g.V,
|
g.U + g.SizeU, g.V,
|
||||||
1, 1, 1, 1,
|
1, 1, 1, 1,
|
||||||
|
|
||||||
-0.5, 0.5, 0,
|
-0.5, 0.5, 0,
|
||||||
g.U, g.V + atlas.GlyphSizeV,
|
g.U, g.V + g.SizeV,
|
||||||
1, 1, 1, 1,
|
1, 1, 1, 1,
|
||||||
|
|
||||||
0.5, 0.5, 0,
|
0.5, 0.5, 0,
|
||||||
g.U + atlas.GlyphSizeU, g.V + atlas.GlyphSizeV,
|
g.U + g.SizeU, g.V + g.SizeV,
|
||||||
1, 1, 1, 1,
|
1, 1, 1, 1,
|
||||||
})
|
})
|
||||||
glyphMesh.Buf.Bind()
|
glyphMesh.Buf.Bind()
|
||||||
|
|
||||||
p.rend.Draw(glyphMesh, tr, glyphMat)
|
height := float32(g.Ascent + g.Descent)
|
||||||
|
scale := gglm.NewVec3(g.Advance, height, 1)
|
||||||
|
|
||||||
tr.Translate(gglm.NewVec3(1, 0, 0))
|
//See: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
|
||||||
}
|
//Quads are drawn from the center and so that's our baseline. But chars shouldn't be centered, they should follow ascent/decent/advance.
|
||||||
|
//To make them do that vertically, we raise them above the baseline (y+height/2), then since they are sitting on top of the baseline we can simply
|
||||||
|
//move them down by the decent amount to put them in the correct vertical position.
|
||||||
|
//
|
||||||
|
//Horizontally the character should be drawn from the left edge not the center, so we just move it forward by advance/2
|
||||||
|
drawPos := *pos
|
||||||
|
drawPos.Add(gglm.NewVec3(g.Advance*0.5, height*0.5-g.Descent, 0))
|
||||||
|
|
||||||
|
p.rend.Draw(glyphMesh, gglm.NewTrMatId().Translate(&drawPos).Scale(scale), glyphMat)
|
||||||
|
pos.SetX(pos.X() + g.Advance)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user