Horizontal position issues was because of the font! Also:

turn GlyphFromRunes into a function and use g.Advance
to have basic support for variable width fonts
This commit is contained in:
bloeys
2022-07-10 17:24:06 +04:00
parent dbd77e9ce7
commit c84702270e
6 changed files with 53 additions and 27 deletions

View File

@ -19,6 +19,7 @@ import (
type FontAtlas struct {
Font *truetype.Font
Face font.Face
Img *image.RGBA
Glyphs map[rune]FontAtlasGlyph
@ -28,6 +29,7 @@ type FontAtlas struct {
}
type FontAtlasGlyph struct {
R rune
U float32
V float32
SizeU float32
@ -123,6 +125,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
//Create atlas
atlas := &FontAtlas{
Font: f,
Face: face,
Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)),
Glyphs: make(map[rune]FontAtlasGlyph, len(glyphs)),
@ -145,7 +148,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
charsOnLine := 0
drawer.Dot = fixed.P(int(atlas.Advance+charPaddingX), lineHeight)
const drawBoundingBoxes bool = true
const drawBoundingBoxes bool = false
for currGlyphCount, g := range glyphs {
gBounds, gAdvanceFixed, _ := face.GlyphBounds(g)
@ -171,6 +174,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
}
atlas.Glyphs[g] = FontAtlasGlyph{
R: g,
U: float32(gTopLeft.X),
V: float32(atlasSizeY - gBotRight.Y),
SizeU: float32(gBotRight.X - gTopLeft.X),
@ -206,6 +210,27 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
}
}
// // This is a test section that uses the drawer to draw an Arabic
// // string at the bottom of the atlas. Useful to compare glyph renderer against a 'correct' implementation
// str := "السلام عليكم"
// rs := []rune(str)
// finalR := make([]rune, 0)
// prevRune := invalidRune
// for i := len(rs) - 1; i >= 0; i-- {
// var g FontAtlasGlyph
// if i > 0 {
// //start or middle of sentence
// g = GlyphFromRunes(atlas.Glyphs, rs[i], rs[i-1], prevRune)
// } else {
// //Last character
// g = GlyphFromRunes(atlas.Glyphs, rs[i], invalidRune, prevRune)
// }
// prevRune = rs[i]
// finalR = append(finalR, g.R)
// }
// drawer.Dot.Y += lineHeightFixed + charPaddingYFixed
// drawer.DrawString(string(finalR))
return atlas, nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/bloeys/nmage/buffers"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes"
"github.com/bloeys/nterm/consts"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/golang/freetype/truetype"
)
@ -120,29 +121,29 @@ func (gr *GlyphRend) drawRune(run *TextRun, i int, prevRune rune, screenPos, pos
if run.IsLtr {
if i < len(run.Runes)-1 {
//start or middle of sentence
g = gr.glyphFromRunes(r, prevRune, run.Runes[i+1])
g = GlyphFromRunes(gr.Atlas.Glyphs, r, prevRune, run.Runes[i+1])
} else {
//Last character
g = gr.glyphFromRunes(r, prevRune, invalidRune)
g = GlyphFromRunes(gr.Atlas.Glyphs, r, prevRune, invalidRune)
}
} else {
if i > 0 {
//start or middle of sentence
g = gr.glyphFromRunes(r, run.Runes[i-1], prevRune)
g = GlyphFromRunes(gr.Atlas.Glyphs, r, run.Runes[i-1], prevRune)
} else {
//Last character
g = gr.glyphFromRunes(r, invalidRune, prevRune)
g = GlyphFromRunes(gr.Atlas.Glyphs, r, invalidRune, prevRune)
}
}
//We must adjust char positioning according to: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
drawPos := *pos
//The flooring to an integer pixel must happen AFTER the (potentially) fractional adjustments have been made.
//This is what the truetype face.Rasterizer does and seems to give good results
//This is what the truetype face.Rasterizer does and seems to give good results. Do NOT floor bearing/descent first.
drawPos.SetX(floorF32(drawPos.X() + g.BearingX))
drawPos.SetY(floorF32(drawPos.Y() - g.Descent))
if PrintPositions {
if consts.Mode_Debug && PrintPositions {
oldXY := gglm.NewVec2(pos.X(), pos.Y())
newXY := gglm.NewVec2(drawPos.X(), drawPos.Y())
fmt.Printf("char=%s; PosBefore=%s, PosAfter=%s; Bearing/Decent=(%f, %f)\n", string(r), oldXY.String(), newXY.String(), g.BearingX, g.Descent)
@ -177,24 +178,23 @@ func (gr *GlyphRend) drawRune(run *TextRun, i int, prevRune rune, screenPos, pos
gr.GlyphVBO[*bufIndex+1] = g.SizeV
*bufIndex += 2
gr.GlyphCount++
pos.AddX(gr.Atlas.Advance)
// pos.AddX(g.Advance)
pos.AddX(g.Advance)
//If we fill the buffer we issue a draw call
gr.GlyphCount++
if gr.GlyphCount == MaxGlyphsPerBatch {
gr.Draw()
*bufIndex = 0
}
}
func roundF32(x float32) float32 {
return float32(math.Round(float64(x)))
}
// func roundF32(x float32) float32 {
// return float32(math.Round(float64(x)))
// }
func ceilF32(x float32) float32 {
return float32(math.Ceil(float64(x)))
}
// func ceilF32(x float32) float32 {
// return float32(math.Ceil(float64(x)))
// }
func floorF32(x float32) float32 {
return float32(math.Floor(float64(x)))
@ -270,7 +270,7 @@ func (gr *GlyphRend) GetTextRuns(t string, textRunsBuf *[]TextRun) {
}
}
func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) FontAtlasGlyph {
func GlyphFromRunes(glyphTable map[rune]FontAtlasGlyph, curr, prev, next rune) FontAtlasGlyph {
//PERF: Map access times are absolute garbage to the point that ~85%+ of the runtime of this func
//is spent reading from maps :). Using nSet or fMap or similar would be a lot better.
@ -287,7 +287,7 @@ func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) FontAtlasGlyph {
//Isolated case
if !prevIsValid && !nextIsValid {
g := gr.Atlas.Glyphs[curr]
g := glyphTable[curr]
return g
}
@ -366,8 +366,7 @@ func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) FontAtlasGlyph {
// }
}
g := gr.Atlas.Glyphs[curr]
return g
return glyphTable[curr]
}
func (gr *GlyphRend) Draw() {
@ -461,6 +460,14 @@ func (gr *GlyphRend) SetScreenSize(screenWidth, screenHeight int32) {
func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, screenHeight int32) (*GlyphRend, error) {
var err error
if RuneInfos == nil {
RuneInfos, err = ParseUnicodeData("./unicode-data-13.txt", "./arabic-shaping-13.txt")
if err != nil {
return nil, err
}
}
gr := &GlyphRend{
GlyphCount: 0,
GlyphVBO: make([]float32, floatsPerGlyph*MaxGlyphsPerBatch),
@ -495,7 +502,6 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
gr.GlyphMat = materials.NewMaterial("glyphMat", "./res/shaders/glyph.glsl")
//With the material ready we can generate the atlas
var err error
gr.Atlas, err = NewFontAtlasFromFile(fontFile, fontOptions)
if err != nil {
return nil, err
@ -563,10 +569,5 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
gr.SetScreenSize(screenWidth, screenHeight)
RuneInfos, err = ParseUnicodeData("./unicode-data-13.txt", "./arabic-shaping-13.txt")
if err != nil {
return nil, err
}
return gr, nil
}