diff --git a/glyphs/font_atlas.go b/glyphs/font_atlas.go index 7fc68c7..5871840 100755 --- a/glyphs/font_atlas.go +++ b/glyphs/font_atlas.go @@ -2,7 +2,6 @@ package glyphs import ( "errors" - "fmt" "image" "image/color" "image/draw" @@ -146,7 +145,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo charsOnLine := 0 drawer.Dot = fixed.P(atlas.Advance+charPaddingX, lineHeight) - const drawBoundingBoxes = false + const drawBoundingBoxes bool = false for currGlyphCount, g := range glyphs { @@ -177,11 +176,7 @@ func NewFontAtlasFromFont(f *truetype.Font, face font.Face, pointSize uint) (*Fo BearingX: float32(bearingX.Ceil()), } - imgRect, mask, maskp, gAdvance, _ := face.Glyph(drawer.Dot, g) - if gAdvance == 0 { - fmt.Printf("Got advance of %s for char with code 0x%04x\n", gAdvance.String(), g) - continue - } + imgRect, mask, maskp, _, _ := face.Glyph(drawer.Dot, g) if drawBoundingBoxes { diff --git a/glyphs/glyphs.go b/glyphs/glyphs.go index da5fad4..98b3d1c 100755 --- a/glyphs/glyphs.go +++ b/glyphs/glyphs.go @@ -68,9 +68,8 @@ func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color pos := screenPos.Clone() advanceF32 := float32(gr.Atlas.Advance) lineHeightF32 := float32(gr.Atlas.LineHeight) - // scale := gglm.NewVec2(advanceF32, lineHeightF32) - buffIndex := gr.GlyphCount * floatsPerGlyph + bufIndex := gr.GlyphCount * floatsPerGlyph for _, run := range runs { @@ -91,155 +90,15 @@ func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color if isLtr { for i := 0; i < len(rs); i++ { - - r := rs[i] - if r == '\n' { - screenPos.SetY(screenPos.Y() - lineHeightF32) - pos = screenPos.Clone() - prevRune = r - continue - } else if r == ' ' { - pos.AddX(advanceF32) - prevRune = r - continue - } else if r == '\t' { - pos.AddX(advanceF32 * float32(gr.SpacesPerTab)) - prevRune = r - continue - } - - var g FontAtlasGlyph - if i < len(rs)-1 { - //start or middle of sentence - g = gr.glyphFromRunes(r, prevRune, rs[i+1]) - } else { - //Last character - g = gr.glyphFromRunes(r, prevRune, invalidRune) - } - - //See: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png - //The uvs coming in make it so that glyphs are sitting on top of the baseline (no descent) and with horizontal bearing applied. - //So to position correctly we move them down by the descent amount. - drawPos := *pos - drawPos.SetX(drawPos.X() + g.BearingX) - drawPos.SetY(drawPos.Y() - g.Descent) - - //Add the glyph information to the vbo - //UV - gr.GlyphVBO[buffIndex+0] = g.U - gr.GlyphVBO[buffIndex+1] = g.V - buffIndex += 2 - - //UVSize - gr.GlyphVBO[buffIndex+0] = g.SizeU - gr.GlyphVBO[buffIndex+1] = g.SizeV - buffIndex += 2 - - //Color - gr.GlyphVBO[buffIndex+0] = color.R() - gr.GlyphVBO[buffIndex+1] = color.G() - gr.GlyphVBO[buffIndex+2] = color.B() - gr.GlyphVBO[buffIndex+3] = color.A() - buffIndex += 4 - - //Model Pos - gr.GlyphVBO[buffIndex+0] = drawPos.X() - gr.GlyphVBO[buffIndex+1] = drawPos.Y() - gr.GlyphVBO[buffIndex+2] = drawPos.Z() - buffIndex += 3 - - //Model Scale - gr.GlyphVBO[buffIndex+0] = g.SizeU - gr.GlyphVBO[buffIndex+1] = g.SizeV - buffIndex += 2 - - gr.GlyphCount++ - pos.AddX(advanceF32) - - //If we fill the buffer we issue a draw call - if gr.GlyphCount == MaxGlyphsPerBatch { - gr.Draw() - buffIndex = 0 - } - - prevRune = r + gr.drawRune(rs, i, prevRune, screenPos, pos, color, advanceF32, lineHeightF32, &bufIndex, isLtr) + prevRune = rs[i] } } else { for i := len(rs) - 1; i >= 0; i-- { - - r := rs[i] - if r == '\n' { - screenPos.SetY(screenPos.Y() - lineHeightF32) - pos = screenPos.Clone() - prevRune = r - continue - } else if r == ' ' { - pos.AddX(advanceF32) - prevRune = r - continue - } else if r == '\t' { - pos.AddX(advanceF32 * float32(gr.SpacesPerTab)) - prevRune = r - continue - } - - var g FontAtlasGlyph - if i > 0 { - //start or middle of sentence - g = gr.glyphFromRunes(r, rs[i-1], prevRune) - } else { - //Last character - g = gr.glyphFromRunes(r, invalidRune, prevRune) - } - - //See: https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png - //The uvs coming in make it so that glyphs are sitting on top of the baseline (no descent) and with horizontal bearing applied. - //So to position correctly we move them down by the descent amount. - drawPos := *pos - drawPos.SetX(drawPos.X() + g.BearingX) - drawPos.SetY(drawPos.Y() - g.Descent) - - //Add the glyph information to the vbo - //UV - gr.GlyphVBO[buffIndex+0] = g.U - gr.GlyphVBO[buffIndex+1] = g.V - buffIndex += 2 - - //UVSize - gr.GlyphVBO[buffIndex+0] = g.SizeU - gr.GlyphVBO[buffIndex+1] = g.SizeV - buffIndex += 2 - - //Color - gr.GlyphVBO[buffIndex+0] = color.R() - gr.GlyphVBO[buffIndex+1] = color.G() - gr.GlyphVBO[buffIndex+2] = color.B() - gr.GlyphVBO[buffIndex+3] = color.A() - buffIndex += 4 - - //Model Pos - gr.GlyphVBO[buffIndex+0] = drawPos.X() - gr.GlyphVBO[buffIndex+1] = drawPos.Y() - gr.GlyphVBO[buffIndex+2] = drawPos.Z() - buffIndex += 3 - - //Model Scale - gr.GlyphVBO[buffIndex+0] = g.SizeU - gr.GlyphVBO[buffIndex+1] = g.SizeV - buffIndex += 2 - - gr.GlyphCount++ - pos.AddX(advanceF32) - - //If we fill the buffer we issue a draw call - if gr.GlyphCount == MaxGlyphsPerBatch { - gr.Draw() - buffIndex = 0 - } - - prevRune = r + gr.drawRune(rs, i, prevRune, screenPos, pos, color, advanceF32, lineHeightF32, &bufIndex, isLtr) + prevRune = rs[i] } } @@ -247,8 +106,92 @@ func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color } } +func (gr *GlyphRend) drawRune(rs []rune, i int, prevRune rune, screenPos, pos *gglm.Vec3, color *gglm.Vec4, advanceF32, lineHeightF32 float32, bufIndex *uint32, isLtr bool) { + + r := rs[i] + if r == '\n' { + screenPos.SetY(screenPos.Y() - lineHeightF32) + *pos = *screenPos.Clone() + // prevRune = r + return + } else if r == ' ' { + pos.AddX(advanceF32) + // prevRune = r + return + } else if r == '\t' { + pos.AddX(advanceF32 * float32(gr.SpacesPerTab)) + // prevRune = r + return + } + + var g FontAtlasGlyph + if isLtr { + if i < len(rs)-1 { + //start or middle of sentence + g = gr.glyphFromRunes(r, prevRune, rs[i+1]) + } else { + //Last character + g = gr.glyphFromRunes(r, prevRune, invalidRune) + } + } else { + if i > 0 { + //start or middle of sentence + g = gr.glyphFromRunes(r, rs[i-1], prevRune) + } else { + //Last character + g = gr.glyphFromRunes(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 + drawPos.SetX(drawPos.X() + g.BearingX) + drawPos.SetY(drawPos.Y() - g.Descent) + + //Add the glyph information to the vbo + //UV + gr.GlyphVBO[*bufIndex+0] = g.U + gr.GlyphVBO[*bufIndex+1] = g.V + *bufIndex += 2 + + //UVSize + gr.GlyphVBO[*bufIndex+0] = g.SizeU + gr.GlyphVBO[*bufIndex+1] = g.SizeV + *bufIndex += 2 + + //Color + gr.GlyphVBO[*bufIndex+0] = color.R() + gr.GlyphVBO[*bufIndex+1] = color.G() + gr.GlyphVBO[*bufIndex+2] = color.B() + gr.GlyphVBO[*bufIndex+3] = color.A() + *bufIndex += 4 + + //Model Pos + gr.GlyphVBO[*bufIndex+0] = drawPos.X() + gr.GlyphVBO[*bufIndex+1] = drawPos.Y() + gr.GlyphVBO[*bufIndex+2] = drawPos.Z() + *bufIndex += 3 + + //Model Scale + gr.GlyphVBO[*bufIndex+0] = g.SizeU + gr.GlyphVBO[*bufIndex+1] = g.SizeV + *bufIndex += 2 + + gr.GlyphCount++ + pos.AddX(advanceF32) + + //If we fill the buffer we issue a draw call + if gr.GlyphCount == MaxGlyphsPerBatch { + gr.Draw() + *bufIndex = 0 + } + + // prevRune = r +} + func (gr *GlyphRend) GetTextRuns(t string) [][]rune { + //PERF: Might be better to pass a [][]rune buffer to avoid allocating on the heap rs := []rune(t) if len(rs) == 0 { @@ -405,7 +348,10 @@ func (gr *GlyphRend) Draw() { gr.InstancedBuf.Bind() gr.GlyphMat.Bind() + //We need to disable depth testing so that nearby characters don't occlude each other + gl.Disable(gl.DEPTH_TEST) gl.DrawElementsInstanced(gl.TRIANGLES, gr.GlyphMesh.Buf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0), int32(gr.GlyphCount)) + gl.Enable(gl.DEPTH_TEST) gr.GlyphCount = 0 } diff --git a/go.mod b/go.mod index a1ee36f..338c8cc 100755 --- a/go.mod +++ b/go.mod @@ -7,11 +7,9 @@ require ( github.com/bloeys/nmage v0.12.13 github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/inkyblackness/imgui-go/v4 v4.3.0 github.com/veandco/go-sdl2 v0.4.10 golang.org/x/image v0.0.0-20220617043117-41969df76e82 ) -require ( - github.com/bloeys/assimp-go v0.4.2 // indirect - github.com/inkyblackness/imgui-go/v4 v4.3.0 // indirect -) +require github.com/bloeys/assimp-go v0.4.2 // indirect diff --git a/main.go b/main.go index e1f9a6f..be69335 100755 --- a/main.go +++ b/main.go @@ -11,7 +11,6 @@ import ( "github.com/bloeys/nmage/meshes" "github.com/bloeys/nmage/renderer/rend3dgl" nmageimgui "github.com/bloeys/nmage/ui/imgui" - "github.com/bloeys/nterm/assert" "github.com/bloeys/nterm/glyphs" "github.com/golang/freetype/truetype" "github.com/inkyblackness/imgui-go/v4" @@ -60,7 +59,7 @@ func main() { rend: rend, imguiInfo: nmageimgui.NewImGUI(), - FontSize: 20, + FontSize: 14, } p.win.EventCallbacks = append(p.win.EventCallbacks, func(e sdl.Event) { @@ -92,7 +91,7 @@ func (p *program) Init() { fmt.Printf("DPI: %f, font size: %d\n", dpi, p.FontSize) w, h := p.win.SDLWin.GetSize() - p.GlyphRend, err = glyphs.NewGlyphRend("./res/fonts/Consolas.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/KawkabMono-Regular.ttf", &truetype.Options{Size: float64(p.FontSize), DPI: p.Dpi, SubPixelsX: subPixelX, SubPixelsY: subPixelY, Hinting: hinting}, w, h) if err != nil { panic("Failed to create atlas from font file. Err: " + err.Error()) } @@ -169,12 +168,9 @@ func (p *program) Update() { } imgui.InputText("", &textToShow) - if len(textToShow) > 0 { - assert.T(len(textToShow) == len(p.GlyphRend.GetTextRuns(textToShow)[0]), "??") - } } -var textToShow = "Hello there my friend" +var textToShow = " Hello there يا friend. أسمي عمر wow!" var xOff float32 = 0 var yOff float32 = 0 @@ -211,7 +207,7 @@ func (p *program) Render() { str := textToShow // str := "مرحبا بك my friend" // str := "my, friend" - // str := " hello there يا friend. سمي عمر wow" + // str := " hello there يا friend. أسمي عمر wow" // str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ\nمرحبا بك" // str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ" diff --git a/res/shaders/glyph.glsl b/res/shaders/glyph.glsl index 5c69fdf..b44d33a 100755 --- a/res/shaders/glyph.glsl +++ b/res/shaders/glyph.glsl @@ -46,7 +46,7 @@ uniform sampler2D diffTex; void main() { vec4 texColor = texelFetch(diffTex, ivec2(v2fUV0), 0); - // vec4 texColor = texture(diffTex, v2fUV0); + // This commented out part highlights the full region of the char // if (texColor.r == 0) // { // fragColor = vec4(0,1,0,0.25);