Alhamdullah basic arabic rendering :D

This commit is contained in:
bloeys
2022-07-05 08:20:45 +04:00
parent ed1ec0d67e
commit 0368ee9caf
3 changed files with 186 additions and 77 deletions

View File

@ -61,83 +61,167 @@ func (gr *GlyphRend) DrawTextOpenGL01(text string, screenPos *gglm.Vec3, color *
//Color is RGBA in the range [0,1]. //Color is RGBA in the range [0,1].
func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color *gglm.Vec4) { func (gr *GlyphRend) DrawTextOpenGLAbs(text string, screenPos *gglm.Vec3, color *gglm.Vec4) {
//Prepass to pre-allocate the buffer runs := gr.GetTextRuns(text)
rs := []rune(text) if runs == nil {
return
}
// startPos := screenPos.Clone()
pos := screenPos.Clone() pos := screenPos.Clone()
advanceF32 := float32(gr.Atlas.Advance) advanceF32 := float32(gr.Atlas.Advance)
lineHeightF32 := float32(gr.Atlas.LineHeight) lineHeightF32 := float32(gr.Atlas.LineHeight)
scale := gglm.NewVec2(advanceF32, lineHeightF32) scale := gglm.NewVec2(advanceF32, lineHeightF32)
prevRune := invalidRune
buffIndex := gr.GlyphCount * floatsPerGlyph buffIndex := gr.GlyphCount * floatsPerGlyph
for i := 0; i < len(rs); i++ {
r := rs[i] for _, run := range runs {
if r == '\n' {
screenPos.SetY(screenPos.Y() - lineHeightF32) rs := run
pos = screenPos.Clone() prevRune := invalidRune
prevRune = r bidiCat := RuneInfos[rs[0]].BidiCat
continue isLtr := !(bidiCat == BidiCategory_R || bidiCat == BidiCategory_AL || bidiCat == BidiCategory_RLE || bidiCat == BidiCategory_RLO || bidiCat == BidiCategory_RLI || bidiCat == BidiCategory_RLM)
} else if r == ' ' {
pos.AddX(advanceF32) if isLtr {
prevRune = r
continue for i := 0; i < len(rs); i++ {
} else if r == '\t' {
pos.AddX(advanceF32 * float32(gr.SpacesPerTab)) r := rs[i]
prevRune = r if r == '\n' {
continue 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())
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
//Color
gr.GlyphVBO[buffIndex+2] = color.R()
gr.GlyphVBO[buffIndex+3] = color.G()
gr.GlyphVBO[buffIndex+4] = color.B()
gr.GlyphVBO[buffIndex+5] = color.A()
//Model Pos
gr.GlyphVBO[buffIndex+6] = roundF32(drawPos.X())
gr.GlyphVBO[buffIndex+7] = roundF32(drawPos.Y())
gr.GlyphVBO[buffIndex+8] = drawPos.Z()
//Model Scale
gr.GlyphVBO[buffIndex+9] = scale.X()
gr.GlyphVBO[buffIndex+10] = scale.Y()
gr.GlyphCount++
pos.AddX(advanceF32)
//If we fill the buffer we issue a draw call
if gr.GlyphCount == MaxGlyphsPerBatch {
gr.Draw()
buffIndex = 0
} else {
buffIndex += floatsPerGlyph
}
prevRune = r
}
var g *FontAtlasGlyph
if i < len(rs)-1 {
//start or middle of sentence
g = gr.glyphFromRunes(r, prevRune, rs[i+1])
} else { } else {
//Last character
g = gr.glyphFromRunes(r, prevRune, invalidRune) 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())
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
//Color
gr.GlyphVBO[buffIndex+2] = color.R()
gr.GlyphVBO[buffIndex+3] = color.G()
gr.GlyphVBO[buffIndex+4] = color.B()
gr.GlyphVBO[buffIndex+5] = color.A()
//Model Pos
gr.GlyphVBO[buffIndex+6] = roundF32(drawPos.X())
gr.GlyphVBO[buffIndex+7] = roundF32(drawPos.Y())
gr.GlyphVBO[buffIndex+8] = drawPos.Z()
//Model Scale
gr.GlyphVBO[buffIndex+9] = scale.X()
gr.GlyphVBO[buffIndex+10] = scale.Y()
gr.GlyphCount++
pos.AddX(advanceF32)
//If we fill the buffer we issue a draw call
if gr.GlyphCount == MaxGlyphsPerBatch {
gr.Draw()
buffIndex = 0
} else {
buffIndex += floatsPerGlyph
}
prevRune = r
}
} }
//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())
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
//Color
gr.GlyphVBO[buffIndex+2] = color.R()
gr.GlyphVBO[buffIndex+3] = color.G()
gr.GlyphVBO[buffIndex+4] = color.B()
gr.GlyphVBO[buffIndex+5] = color.A()
//Model Pos
gr.GlyphVBO[buffIndex+6] = roundF32(drawPos.X())
gr.GlyphVBO[buffIndex+7] = roundF32(drawPos.Y())
gr.GlyphVBO[buffIndex+8] = drawPos.Z()
//Model Scale
gr.GlyphVBO[buffIndex+9] = scale.X()
gr.GlyphVBO[buffIndex+10] = scale.Y()
gr.GlyphCount++
pos.AddX(advanceF32)
//If we fill the buffer we issue a draw call
if gr.GlyphCount == MaxGlyphsPerBatch {
gr.Draw()
buffIndex = 0
} else {
buffIndex += floatsPerGlyph
}
prevRune = r
} }
} }
@ -146,7 +230,7 @@ func (gr *GlyphRend) GetTextRuns(t string) [][]rune {
rs := []rune(t) rs := []rune(t)
if len(rs) == 0 { if len(rs) == 0 {
return [][]rune{} return nil
} }
runs := make([][]rune, 0, 1) runs := make([][]rune, 0, 1)
@ -157,7 +241,7 @@ func (gr *GlyphRend) GetTextRuns(t string) [][]rune {
r := rs[i] r := rs[i]
ri := RuneInfos[r] ri := RuneInfos[r]
if ri.ScriptTable == currRunScript || ri.ScriptTable == unicode.Common { if ri.ScriptTable == currRunScript {
continue continue
} }
@ -167,9 +251,7 @@ func (gr *GlyphRend) GetTextRuns(t string) [][]rune {
currRunScript = ri.ScriptTable currRunScript = ri.ScriptTable
} }
if runStartIndex != len(rs)-1 { runs = append(runs, rs[runStartIndex:])
runs = append(runs, rs[runStartIndex:])
}
return runs return runs
} }
@ -202,6 +284,7 @@ func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) *FontAtlasGlyph {
ctx = PosCtx_end ctx = PosCtx_end
} }
//This is only needed for Arabic (I think)
switch ctx { switch ctx {
case PosCtx_start: case PosCtx_start:
@ -210,7 +293,7 @@ func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) *FontAtlasGlyph {
otherRune := equivRunes[i] otherRune := equivRunes[i]
otherRuneInfo := RuneInfos[otherRune] otherRuneInfo := RuneInfos[otherRune]
if otherRuneInfo.DecompTag == DecompTags_initial { if otherRuneInfo.DecompTag == DecompTag_initial {
curr = otherRune curr = otherRune
break break
} }
@ -218,7 +301,29 @@ func (gr *GlyphRend) glyphFromRunes(curr, prev, next rune) *FontAtlasGlyph {
case PosCtx_mid: case PosCtx_mid:
equivRunes := RuneInfos[curr].EquivalentRunes
for i := 0; i < len(equivRunes); i++ {
otherRune := equivRunes[i]
otherRuneInfo := RuneInfos[otherRune]
if otherRuneInfo.DecompTag == DecompTag_medial {
curr = otherRune
break
}
}
case PosCtx_end: case PosCtx_end:
equivRunes := RuneInfos[curr].EquivalentRunes
for i := 0; i < len(equivRunes); i++ {
otherRune := equivRunes[i]
otherRuneInfo := RuneInfos[otherRune]
if otherRuneInfo.DecompTag == DecompTag_final {
curr = otherRune
break
}
}
} }
g := gr.Atlas.Glyphs[curr] g := gr.Atlas.Glyphs[curr]

View File

@ -84,7 +84,7 @@ type DecompTag uint8
const ( const (
DecompTag_font DecompTag = iota // A font variant (e.g. a blackletter form). DecompTag_font DecompTag = iota // A font variant (e.g. a blackletter form).
DecompTag_noBreak // A no-break version of a space or hyphen. DecompTag_noBreak // A no-break version of a space or hyphen.
DecompTags_initial // An initial presentation form (Arabic). DecompTag_initial // An initial presentation form (Arabic).
DecompTag_medial // A medial presentation form (Arabic). DecompTag_medial // A medial presentation form (Arabic).
DecompTag_final // A final presentation form (Arabic). DecompTag_final // A final presentation form (Arabic).
DecompTag_isolated // An isolated presentation form (Arabic). DecompTag_isolated // An isolated presentation form (Arabic).
@ -108,7 +108,7 @@ func (cd DecompTag) String() string {
return "font" return "font"
case DecompTag_noBreak: case DecompTag_noBreak:
return "noBreak" return "noBreak"
case DecompTags_initial: case DecompTag_initial:
return "initial" return "initial"
case DecompTag_medial: case DecompTag_medial:
return "medial" return "medial"
@ -809,7 +809,7 @@ func charDecompMapStringToCharDecompMap(c string) DecompTag {
case "<noBreak>": case "<noBreak>":
return DecompTag_noBreak return DecompTag_noBreak
case "<initial>": case "<initial>":
return DecompTags_initial return DecompTag_initial
case "<medial>": case "<medial>":
return DecompTag_medial return DecompTag_medial
case "<final>": case "<final>":

10
main.go
View File

@ -101,8 +101,11 @@ func (p *program) Init() {
p.gridMat = materials.NewMaterial("grid", "./res/shaders/grid.glsl") p.gridMat = materials.NewMaterial("grid", "./res/shaders/grid.glsl")
p.handleWindowResize() p.handleWindowResize()
// runs := p.GlyphRend.GetTextRuns("hello there يا friend") runs := p.GlyphRend.GetTextRuns("hello مرحبا,")
// fmt.Printf("%+v\n%s\n%s\n%s\n", runs, string(runs[0]), string(runs[1]), string(runs[2])) fmt.Printf("%+v\n", runs)
for _, r := range runs {
fmt.Printf("%s;\n", string(r))
}
} }
func (p *program) Update() { func (p *program) Update() {
@ -190,7 +193,8 @@ func (p *program) Render() {
} }
textColor := gglm.NewVec4(r, g, b, 1) textColor := gglm.NewVec4(r, g, b, 1)
str := " مرحبا بك" // str := " مرحبا بك"
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\nمرحبا بك"
// str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ" // str := " ijojo\n\n Hello there, friend|. pq?\n ABCDEFG\tHIJKLMNOPQRSTUVWXYZ"