mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 14:38:19 +00:00
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:
@ -19,6 +19,7 @@ import (
|
|||||||
|
|
||||||
type FontAtlas struct {
|
type FontAtlas struct {
|
||||||
Font *truetype.Font
|
Font *truetype.Font
|
||||||
|
Face font.Face
|
||||||
Img *image.RGBA
|
Img *image.RGBA
|
||||||
Glyphs map[rune]FontAtlasGlyph
|
Glyphs map[rune]FontAtlasGlyph
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ type FontAtlas struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FontAtlasGlyph struct {
|
type FontAtlasGlyph struct {
|
||||||
|
R rune
|
||||||
U float32
|
U float32
|
||||||
V float32
|
V float32
|
||||||
SizeU float32
|
SizeU float32
|
||||||
@ -123,6 +125,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
|
|||||||
//Create atlas
|
//Create atlas
|
||||||
atlas := &FontAtlas{
|
atlas := &FontAtlas{
|
||||||
Font: f,
|
Font: f,
|
||||||
|
Face: face,
|
||||||
Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)),
|
Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)),
|
||||||
Glyphs: make(map[rune]FontAtlasGlyph, len(glyphs)),
|
Glyphs: make(map[rune]FontAtlasGlyph, len(glyphs)),
|
||||||
|
|
||||||
@ -145,7 +148,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
|
|||||||
charsOnLine := 0
|
charsOnLine := 0
|
||||||
drawer.Dot = fixed.P(int(atlas.Advance+charPaddingX), lineHeight)
|
drawer.Dot = fixed.P(int(atlas.Advance+charPaddingX), lineHeight)
|
||||||
|
|
||||||
const drawBoundingBoxes bool = true
|
const drawBoundingBoxes bool = false
|
||||||
for currGlyphCount, g := range glyphs {
|
for currGlyphCount, g := range glyphs {
|
||||||
|
|
||||||
gBounds, gAdvanceFixed, _ := face.GlyphBounds(g)
|
gBounds, gAdvanceFixed, _ := face.GlyphBounds(g)
|
||||||
@ -171,6 +174,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
atlas.Glyphs[g] = FontAtlasGlyph{
|
atlas.Glyphs[g] = FontAtlasGlyph{
|
||||||
|
R: g,
|
||||||
U: float32(gTopLeft.X),
|
U: float32(gTopLeft.X),
|
||||||
V: float32(atlasSizeY - gBotRight.Y),
|
V: float32(atlasSizeY - gBotRight.Y),
|
||||||
SizeU: float32(gBotRight.X - gTopLeft.X),
|
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
|
return atlas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/bloeys/nmage/buffers"
|
"github.com/bloeys/nmage/buffers"
|
||||||
"github.com/bloeys/nmage/materials"
|
"github.com/bloeys/nmage/materials"
|
||||||
"github.com/bloeys/nmage/meshes"
|
"github.com/bloeys/nmage/meshes"
|
||||||
|
"github.com/bloeys/nterm/consts"
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
"github.com/golang/freetype/truetype"
|
"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 run.IsLtr {
|
||||||
if i < len(run.Runes)-1 {
|
if i < len(run.Runes)-1 {
|
||||||
//start or middle of sentence
|
//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 {
|
} else {
|
||||||
//Last character
|
//Last character
|
||||||
g = gr.glyphFromRunes(r, prevRune, invalidRune)
|
g = GlyphFromRunes(gr.Atlas.Glyphs, r, prevRune, invalidRune)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
//start or middle of sentence
|
//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 {
|
} else {
|
||||||
//Last character
|
//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
|
//We must adjust char positioning according to: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
|
||||||
drawPos := *pos
|
drawPos := *pos
|
||||||
//The flooring to an integer pixel must happen AFTER the (potentially) fractional adjustments have been made.
|
//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.SetX(floorF32(drawPos.X() + g.BearingX))
|
||||||
drawPos.SetY(floorF32(drawPos.Y() - g.Descent))
|
drawPos.SetY(floorF32(drawPos.Y() - g.Descent))
|
||||||
|
|
||||||
if PrintPositions {
|
if consts.Mode_Debug && PrintPositions {
|
||||||
oldXY := gglm.NewVec2(pos.X(), pos.Y())
|
oldXY := gglm.NewVec2(pos.X(), pos.Y())
|
||||||
newXY := gglm.NewVec2(drawPos.X(), drawPos.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)
|
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
|
gr.GlyphVBO[*bufIndex+1] = g.SizeV
|
||||||
*bufIndex += 2
|
*bufIndex += 2
|
||||||
|
|
||||||
gr.GlyphCount++
|
pos.AddX(g.Advance)
|
||||||
pos.AddX(gr.Atlas.Advance)
|
|
||||||
// pos.AddX(g.Advance)
|
|
||||||
|
|
||||||
//If we fill the buffer we issue a draw call
|
//If we fill the buffer we issue a draw call
|
||||||
|
gr.GlyphCount++
|
||||||
if gr.GlyphCount == MaxGlyphsPerBatch {
|
if gr.GlyphCount == MaxGlyphsPerBatch {
|
||||||
gr.Draw()
|
gr.Draw()
|
||||||
*bufIndex = 0
|
*bufIndex = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func roundF32(x float32) float32 {
|
// func roundF32(x float32) float32 {
|
||||||
return float32(math.Round(float64(x)))
|
// return float32(math.Round(float64(x)))
|
||||||
}
|
// }
|
||||||
|
|
||||||
func ceilF32(x float32) float32 {
|
// func ceilF32(x float32) float32 {
|
||||||
return float32(math.Ceil(float64(x)))
|
// return float32(math.Ceil(float64(x)))
|
||||||
}
|
// }
|
||||||
|
|
||||||
func floorF32(x float32) float32 {
|
func floorF32(x float32) float32 {
|
||||||
return float32(math.Floor(float64(x)))
|
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
|
//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.
|
//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
|
//Isolated case
|
||||||
if !prevIsValid && !nextIsValid {
|
if !prevIsValid && !nextIsValid {
|
||||||
g := gr.Atlas.Glyphs[curr]
|
g := glyphTable[curr]
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,8 +366,7 @@ func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) FontAtlasGlyph {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
g := gr.Atlas.Glyphs[curr]
|
return glyphTable[curr]
|
||||||
return g
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gr *GlyphRend) Draw() {
|
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) {
|
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{
|
gr := &GlyphRend{
|
||||||
GlyphCount: 0,
|
GlyphCount: 0,
|
||||||
GlyphVBO: make([]float32, floatsPerGlyph*MaxGlyphsPerBatch),
|
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")
|
gr.GlyphMat = materials.NewMaterial("glyphMat", "./res/shaders/glyph.glsl")
|
||||||
|
|
||||||
//With the material ready we can generate the atlas
|
//With the material ready we can generate the atlas
|
||||||
var err error
|
|
||||||
gr.Atlas, err = NewFontAtlasFromFile(fontFile, fontOptions)
|
gr.Atlas, err = NewFontAtlasFromFile(fontFile, fontOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -563,10 +569,5 @@ func NewGlyphRend(fontFile string, fontOptions *truetype.Options, screenWidth, s
|
|||||||
|
|
||||||
gr.SetScreenSize(screenWidth, screenHeight)
|
gr.SetScreenSize(screenWidth, screenHeight)
|
||||||
|
|
||||||
RuneInfos, err = ParseUnicodeData("./unicode-data-13.txt", "./arabic-shaping-13.txt")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gr, nil
|
return gr, nil
|
||||||
}
|
}
|
||||||
|
|||||||
2
main.go
2
main.go
@ -94,7 +94,7 @@ func (p *program) Init() {
|
|||||||
fmt.Printf("DPI: %f, font size: %d\n", dpi, p.FontSize)
|
fmt.Printf("DPI: %f, font size: %d\n", dpi, p.FontSize)
|
||||||
|
|
||||||
w, h := p.win.SDLWin.GetSize()
|
w, h := p.win.SDLWin.GetSize()
|
||||||
p.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/KawkabMono-Regular.ttf", &truetype.Options{Size: float64(p.FontSize), DPI: p.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h)
|
p.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/alm-fixed.ttf", &truetype.Options{Size: float64(p.FontSize), DPI: p.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Failed to create atlas from font file. Err: " + err.Error())
|
panic("Failed to create atlas from font file. Err: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
BIN
res/fonts/alm-fixed.ttf
Executable file
BIN
res/fonts/alm-fixed.ttf
Executable file
Binary file not shown.
BIN
res/fonts/arabtype-variable.ttf
Executable file
BIN
res/fonts/arabtype-variable.ttf
Executable file
Binary file not shown.
Reference in New Issue
Block a user