text/v2: add LayoutOptions

Now AppendGlyphs can treat multiple lines and alignments.
This commit is contained in:
Hajime Hoshi 2023-11-13 23:15:54 +09:00
parent cca4e78651
commit ea1d9dde4e
3 changed files with 66 additions and 57 deletions

View File

@ -18,7 +18,6 @@ import (
"image/color" "image/color"
"log" "log"
"math" "math"
"strings"
"golang.org/x/image/font" "golang.org/x/image/font"
"golang.org/x/image/font/opentype" "golang.org/x/image/font/opentype"
@ -75,15 +74,15 @@ type Game struct {
counter int counter int
kanjiText []rune kanjiText []rune
kanjiTextColor color.RGBA kanjiTextColor color.RGBA
glyphs [][]text.Glyph glyphs []text.Glyph
} }
func (g *Game) Update() error { func (g *Game) Update() error {
// Initialize the glyphs for special (colorful) rendering. // Initialize the glyphs for special (colorful) rendering.
if len(g.glyphs) == 0 { if len(g.glyphs) == 0 {
for _, line := range strings.Split(sampleText, "\n") { op := &text.LayoutOptions{}
g.glyphs = append(g.glyphs, text.AppendGlyphs(nil, line, mplusNormalFace, 0, 0)) op.LineHeightInPixels = mplusNormalFace.Metrics().Height
} g.glyphs = text.AppendGlyphs(g.glyphs, sampleText, mplusNormalFace, op)
} }
return nil return nil
} }
@ -137,33 +136,30 @@ func (g *Game) Draw(screen *ebiten.Image) {
text.Draw(screen, sampleText, mplusBigFace, op) text.Draw(screen, sampleText, mplusBigFace, op)
} }
{ {
const x, y = 240, 380 const x, y = 240, 360
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
// g.glyphs is initialized by text.AppendGlyphs. // g.glyphs is initialized by text.AppendGlyphs.
// You can customize how to render each glyph. // You can customize how to render each glyph.
// In this example, multiple colors are used to render glyphs. // In this example, multiple colors are used to render glyphs.
for j, line := range g.glyphs { for i, gl := range g.glyphs {
for i, gl := range line { op.GeoM.Reset()
op.GeoM.Reset() op.GeoM.Translate(x, y)
op.GeoM.Translate(x, y) op.GeoM.Translate(gl.X, gl.Y)
op.GeoM.Translate(0, float64(j)*mplusNormalFace.Metrics().Height) op.ColorScale.Reset()
op.GeoM.Translate(gl.X, gl.Y) r := float32(1)
op.ColorScale.Reset() if i%3 == 0 {
r := float32(1) r = 0.5
if i%3 == 0 {
r = 0.5
}
g := float32(1)
if i%3 == 1 {
g = 0.5
}
b := float32(1)
if i%3 == 2 {
b = 0.5
}
op.ColorScale.Scale(r, g, b, 1)
screen.DrawImage(gl.Image, op)
} }
g := float32(1)
if i%3 == 1 {
g = 0.5
}
b := float32(1)
if i%3 == 2 {
b = 0.5
}
op.ColorScale.Scale(r, g, b, 1)
screen.DrawImage(gl.Image, op)
} }
} }
} }

View File

@ -35,12 +35,16 @@ const (
// DrawImageOptions.GeoM is an additional geometry transformation // DrawImageOptions.GeoM is an additional geometry transformation
// after putting the rendering region along with the specified alignments. // after putting the rendering region along with the specified alignments.
// DrawImageOptions.ColorScale scales the text color. // DrawImageOptions.ColorScale scales the text color.
type DrawOptions struct {
ebiten.DrawImageOptions
LayoutOptions
}
// LayoutOptions represents options for layouting texts.
// //
// PrimaryAlign and SecondaryAlign determine where to put the text in the given region at Draw. // PrimaryAlign and SecondaryAlign determine where to put the text in the given region at Draw.
// Draw might render the text outside of the specified image bounds, so you might have to specify GeoM to make the text visible. // Draw might render the text outside of the specified image bounds, so you might have to specify GeoM to make the text visible.
type DrawOptions struct { type LayoutOptions struct {
ebiten.DrawImageOptions
// LineHeightInPixels is a line height in pixels. // LineHeightInPixels is a line height in pixels.
LineHeightInPixels float64 LineHeightInPixels float64
@ -95,12 +99,43 @@ type DrawOptions struct {
// If the vertical alignment is center, the rendering region's middle Y comes to the origin. // If the vertical alignment is center, the rendering region's middle Y comes to the origin.
// If the vertical alignment is bottom, the rendering region's bottom Y comes to the origin. // If the vertical alignment is bottom, the rendering region's bottom Y comes to the origin.
func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) { func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) {
if options == nil {
options = &DrawOptions{}
}
geoM := options.GeoM
for _, g := range AppendGlyphs(nil, text, face, &options.LayoutOptions) {
op := &options.DrawImageOptions
op.GeoM.Reset()
op.GeoM.Translate(g.X, g.Y)
op.GeoM.Concat(geoM)
dst.DrawImage(g.Image, op)
}
}
// AppendGlyphs appends glyphs to the given slice and returns a slice.
//
// AppendGlyphs is a low-level API, and you can use AppendGlyphs to have more control than Draw.
// AppendGlyphs is also available to precache glyphs.
//
// For the details of options, see Draw function.
//
// AppendGlyphs is concurrent-safe.
func AppendGlyphs(glyphs []Glyph, text string, face Face, options *LayoutOptions) []Glyph {
return appendGlyphs(glyphs, text, face, 0, 0, options)
}
// appendGlyphs appends glyphs to the given slice and returns a slice.
//
// appendGlyphs assumes the text is rendered with the position (x, y).
// (x, y) might affect the subpixel rendering results.
func appendGlyphs(glyphs []Glyph, text string, face Face, x, y float64, options *LayoutOptions) []Glyph {
if text == "" { if text == "" {
return return glyphs
} }
if options == nil { if options == nil {
options = &DrawOptions{} options = &LayoutOptions{}
} }
// Calculate the advances for each line. // Calculate the advances for each line.
@ -171,7 +206,6 @@ func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) {
var originX, originY float64 var originX, originY float64
var i int var i int
geoM := options.GeoM
for t := text; ; { for t := text; ; {
line, rest, found := strings.Cut(t, "\n") line, rest, found := strings.Cut(t, "\n")
@ -197,7 +231,7 @@ func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) {
} }
} }
drawLine(dst, line, face, options, originX+offsetX, originY+offsetY, geoM) glyphs = face.appendGlyphs(glyphs, line, originX+offsetX+x, originY+offsetY+y)
if !found { if !found {
break break
@ -217,17 +251,8 @@ func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) {
originX -= options.LineHeightInPixels originX -= options.LineHeightInPixels
} }
} }
}
func drawLine(dst *ebiten.Image, line string, face Face, options *DrawOptions, originX, originY float64, geoM ebiten.GeoM) { return glyphs
op := &options.DrawImageOptions
gs := face.appendGlyphs(nil, line, originX, originY)
for _, g := range gs {
op.GeoM.Reset()
op.GeoM.Translate(g.X, g.Y)
op.GeoM.Concat(geoM)
dst.DrawImage(g.Image, op)
}
} }
type horizontalAlign int type horizontalAlign int

View File

@ -115,18 +115,6 @@ type Glyph struct {
GID uint32 GID uint32
} }
// AppendGlyphs appends glyphs to the given slice and returns a slice.
//
// AppendGlyphs is a low-level API, and you can use AppendGlyphs to have more control than Draw.
// AppendGlyphs is also available to precache glyphs.
//
// AppendGlyphs doesn't treat multiple lines.
//
// AppendGlyphs is concurrent-safe.
func AppendGlyphs(glyphs []Glyph, text string, face Face, originX, originY float64) []Glyph {
return face.appendGlyphs(glyphs, text, originX, originY)
}
// Advance returns the advanced distance from the origin position when rendering the given text with the given face. // Advance returns the advanced distance from the origin position when rendering the given text with the given face.
// //
// Advance doesn't treat multiple lines. // Advance doesn't treat multiple lines.
@ -229,7 +217,7 @@ func CacheGlyphs(text string, face Face) {
var buf []Glyph var buf []Glyph
// Create all the possible variations (#2528). // Create all the possible variations (#2528).
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
buf = AppendGlyphs(buf, text, face, x, y) buf = appendGlyphs(buf, text, face, x, y, nil)
buf = buf[:0] buf = buf[:0]
if face.direction().isHorizontal() { if face.direction().isHorizontal() {