Compare commits

..

3 Commits

Author SHA1 Message Date
Bertrand Jung
b7e0122d7c
Merge 1dd96726c4 into df266e8acf 2024-08-23 01:44:15 +03:00
Hajime Hoshi
df266e8acf text/v2: add Glyph.OriginOffset{X,Y}
Closes #3070
2024-08-23 03:59:16 +09:00
Hajime Hoshi
6056fc59eb text/v2: update comments about CacheGlyphs
The example was not clear.
2024-08-23 00:13:20 +09:00
4 changed files with 28 additions and 13 deletions

View File

@ -122,7 +122,7 @@ func (g *Game) Update() error {
}
func (g *Game) Draw(screen *ebiten.Image) {
ebitenutil.DebugPrint(screen, "Press O to show/hide origins")
ebitenutil.DebugPrint(screen, "Press O to show/hide origins.\nRed points are the original origin positions.\nThe green points are the origin positions after applying the offset.")
gray := color.RGBA{0x80, 0x80, 0x80, 0xff}
@ -134,7 +134,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
Size: 24,
Language: language.Arabic,
}
x, y := screenWidth-20, 40
x, y := screenWidth-20, 50
w, h := text.Measure(arabicText, f, 0)
// The left upper point is not x but x-w, since the text runs in the rigth-to-left direction.
vector.DrawFilledRect(screen, float32(x)-float32(w), float32(y), float32(w), float32(h), gray, false)
@ -145,6 +145,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
if g.showOrigins {
op := &text.LayoutOptions{}
for _, g := range text.AppendGlyphs(nil, arabicText, f, op) {
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX+g.OriginOffsetX), float32(y)+float32(g.OriginY+g.OriginOffsetY), 2, color.RGBA{0, 0xff, 0, 0xff}, true)
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX), float32(y)+float32(g.OriginY), 2, color.RGBA{0xff, 0, 0, 0xff}, true)
}
}
@ -156,7 +157,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
Size: 24,
Language: language.Hindi,
}
x, y := 20, 100
x, y := 20, 110
w, h := text.Measure(hindiText, f, 0)
vector.DrawFilledRect(screen, float32(x), float32(y), float32(w), float32(h), gray, false)
op := &text.DrawOptions{}
@ -166,6 +167,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
if g.showOrigins {
op := &text.LayoutOptions{}
for _, g := range text.AppendGlyphs(nil, hindiText, f, op) {
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX+g.OriginOffsetX), float32(y)+float32(g.OriginY+g.OriginOffsetY), 2, color.RGBA{0, 0xff, 0, 0xff}, true)
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX), float32(y)+float32(g.OriginY), 2, color.RGBA{0xff, 0, 0, 0xff}, true)
}
}
@ -177,7 +179,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
Size: 24,
Language: language.Burmese,
}
x, y := 20, 160
x, y := 20, 170
w, h := text.Measure(myanmarText, f, 0)
vector.DrawFilledRect(screen, float32(x), float32(y), float32(w), float32(h), gray, false)
op := &text.DrawOptions{}
@ -187,6 +189,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
if g.showOrigins {
op := &text.LayoutOptions{}
for _, g := range text.AppendGlyphs(nil, myanmarText, f, op) {
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX+g.OriginOffsetX), float32(y)+float32(g.OriginY+g.OriginOffsetY), 2, color.RGBA{0, 0xff, 0, 0xff}, true)
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX), float32(y)+float32(g.OriginY), 2, color.RGBA{0xff, 0, 0, 0xff}, true)
}
}
@ -198,7 +201,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
Size: 24,
Language: language.Thai,
}
x, y := 20, 220
x, y := 20, 230
w, h := text.Measure(thaiText, f, 0)
vector.DrawFilledRect(screen, float32(x), float32(y), float32(w), float32(h), gray, false)
op := &text.DrawOptions{}
@ -208,6 +211,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
if g.showOrigins {
op := &text.LayoutOptions{}
for _, g := range text.AppendGlyphs(nil, thaiText, f, op) {
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX+g.OriginOffsetX), float32(y)+float32(g.OriginY+g.OriginOffsetY), 2, color.RGBA{0, 0xff, 0, 0xff}, true)
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX), float32(y)+float32(g.OriginY), 2, color.RGBA{0xff, 0, 0, 0xff}, true)
}
}
@ -223,7 +227,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
Script: language.MustParseScript("Mong"),
}
const lineSpacing = 48
x, y := 20, 280
x, y := 20, 290
w, h := text.Measure(mongolianText, f, lineSpacing)
vector.DrawFilledRect(screen, float32(x), float32(y), float32(w), float32(h), gray, false)
op := &text.DrawOptions{}
@ -235,6 +239,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
op := &text.LayoutOptions{}
op.LineSpacing = lineSpacing
for _, g := range text.AppendGlyphs(nil, mongolianText, f, op) {
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX+g.OriginOffsetX), float32(y)+float32(g.OriginY+g.OriginOffsetY), 2, color.RGBA{0, 0xff, 0, 0xff}, true)
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX), float32(y)+float32(g.OriginY), 2, color.RGBA{0xff, 0, 0, 0xff}, true)
}
}
@ -248,7 +253,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
Language: language.Japanese,
}
const lineSpacing = 48
x, y := screenWidth-20, 280
x, y := screenWidth-20, 290
w, h := text.Measure(japaneseText, f, lineSpacing)
// The left upper point is not x but x-w, since the text runs in the rigth-to-left direction as the secondary direction.
vector.DrawFilledRect(screen, float32(x)-float32(w), float32(y), float32(w), float32(h), gray, false)
@ -262,6 +267,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
op.LineSpacing = lineSpacing
for _, g := range text.AppendGlyphs(nil, japaneseText, f, op) {
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX), float32(y)+float32(g.OriginY), 2, color.RGBA{0xff, 0, 0, 0xff}, true)
vector.DrawFilledCircle(screen, float32(x)+float32(g.OriginX+g.OriginOffsetX), float32(y)+float32(g.OriginY+g.OriginOffsetY), 2, color.RGBA{0, 0xff, 0, 0xff}, true)
}
}
}

View File

@ -323,8 +323,10 @@ func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffse
Image: ebitenImage,
X: float64(imgX),
Y: float64(imgY),
OriginX: fixed26_6ToFloat64(o.X),
OriginY: fixed26_6ToFloat64(o.Y),
OriginX: fixed26_6ToFloat64(origin.X),
OriginY: fixed26_6ToFloat64(origin.Y),
OriginOffsetX: fixed26_6ToFloat64(glyph.shapingGlyph.XOffset),
OriginOffsetY: fixed26_6ToFloat64(-glyph.shapingGlyph.YOffset),
})
origin = origin.Add(fixed.Point26_6{
X: glyph.shapingGlyph.XAdvance,

View File

@ -126,6 +126,8 @@ func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
Y: float64(imgY),
OriginX: fixed26_6ToFloat64(origin.X),
OriginY: fixed26_6ToFloat64(origin.Y),
OriginOffsetX: 0,
OriginOffsetY: 0,
})
origin.X += a
prevR = r

View File

@ -152,6 +152,14 @@ type Glyph struct {
// OriginY is the Y position of the origin of this glyph.
OriginY float64
// OriginOffsetX is the adjustment value to the X position of the origin of this glyph.
// OriginOffsetX is usually 0, but can be non-zero for some special glyphs or glyphs in the vertical text layout.
OriginOffsetX float64
// OriginOffsetY is the adjustment value to the Y position of the origin of this glyph.
// OriginOffsetY is usually 0, but can be non-zero for some special glyphs or glyphs in the vertical text layout.
OriginOffsetY float64
}
// Advance returns the advanced distance from the origin position when rendering the given text with the given face.
@ -244,10 +252,7 @@ func Measure(text string, face Face, lineSpacingInPixels float64) (width, height
// CacheGlyphs creates all such variations for one rune, while Draw and AppendGlyphs create only necessary glyphs.
//
// Draw and AppendGlyphs automatically create and cache necessary glyphs, so usually you don't have to call CacheGlyphs explicitly.
// However, for example, when you call Draw for each rune of one big text, Draw tries to create the glyph cache and render it for each rune.
// This is very inefficient because creating a glyph image and rendering it are different operations
// (`(*ebiten.Image).WritePixels` and `(*ebiten.Image).DrawImage`) and can never be merged as one draw call.
// CacheGlyphs creates necessary glyphs without rendering them so that these operations are likely merged into one draw call regardless of the size of the text.
// If you really care about the performance, CacheGlyphs might be useful.
//
// CacheGlyphs is concurrent-safe.
func CacheGlyphs(text string, face Face) {