From f0d23de3d3e7e2f0435744a07ac69629cea1ceb5 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 5 Dec 2023 17:57:22 +0900 Subject: [PATCH] text/v2: rename Glyph -> Cluster This also changes AppendClusters to return cluster info even if a cluster doesn't have a glyph. --- examples/text/main.go | 17 ++++++++++------- text/v2/gotext.go | 26 +++++++++++++------------- text/v2/layout.go | 25 ++++++++++++++----------- text/v2/limited.go | 6 +++--- text/v2/multi.go | 8 ++++---- text/v2/std.go | 32 +++++++++++++++++--------------- text/v2/text.go | 18 +++++++++--------- text/v2/text_test.go | 2 +- 8 files changed, 71 insertions(+), 63 deletions(-) diff --git a/examples/text/main.go b/examples/text/main.go index 2bbcb92d6..9f66643db 100644 --- a/examples/text/main.go +++ b/examples/text/main.go @@ -61,15 +61,15 @@ type Game struct { counter int kanjiText []rune kanjiTextColor color.RGBA - glyphs []text.Glyph + clusters []text.Cluster } func (g *Game) Update() error { // Initialize the glyphs for special (colorful) rendering. - if len(g.glyphs) == 0 { + if len(g.clusters) == 0 { op := &text.LayoutOptions{} op.LineSpacingInPixels = mplusNormalFace.Size * 1.5 - g.glyphs = text.AppendGlyphs(g.glyphs, sampleText, mplusNormalFace, op) + g.clusters = text.AppendClusters(g.clusters, sampleText, mplusNormalFace, op) } return nil } @@ -121,13 +121,16 @@ func (g *Game) Draw(screen *ebiten.Image) { { const x, y = 240, 360 op := &ebiten.DrawImageOptions{} - // g.glyphs is initialized by text.AppendGlyphs. + // g.glyphs is initialized by text.AppendClusters // You can customize how to render each glyph. // In this example, multiple colors are used to render glyphs. - for i, gl := range g.glyphs { + for i, c := range g.clusters { + if c.Image == nil { + continue + } op.GeoM.Reset() op.GeoM.Translate(x, y) - op.GeoM.Translate(gl.X, gl.Y) + op.GeoM.Translate(c.X, c.Y) op.ColorScale.Reset() r := float32(1) if i%3 == 0 { @@ -142,7 +145,7 @@ func (g *Game) Draw(screen *ebiten.Image) { b = 0.5 } op.ColorScale.Scale(r, g, b, 1) - screen.DrawImage(gl.Image, op) + screen.DrawImage(c.Image, op) } } } diff --git a/text/v2/gotext.go b/text/v2/gotext.go index 9e709d069..438d83272 100644 --- a/text/v2/gotext.go +++ b/text/v2/gotext.go @@ -291,8 +291,8 @@ func (g *GoTextFace) hasGlyph(r rune) bool { return ok } -// appendGlyphsForLine implements Face. -func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph { +// appendClustersForLine implements Face. +func (g *GoTextFace) appendClustersForLine(clusters []Cluster, line string, indexOffset int, originX, originY float64) []Cluster { origin := fixed.Point26_6{ X: float64ToFixed26_6(originX), Y: float64ToFixed26_6(originY), @@ -300,23 +300,23 @@ func (g *GoTextFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffse _, gs := g.Source.shape(line, g) for _, glyph := range gs { img, imgX, imgY := g.glyphImage(glyph, origin) - if img != nil { - glyphs = append(glyphs, Glyph{ - StartIndexInBytes: indexOffset + glyph.startIndex, - EndIndexInBytes: indexOffset + glyph.endIndex, - GID: uint32(glyph.shapingGlyph.GlyphID), - Image: img, - X: float64(imgX), - Y: float64(imgY), - }) - } + // Append a glyph even if img is nil. + // This is necessary to return index information for control characters. + clusters = append(clusters, Cluster{ + StartIndexInBytes: indexOffset + glyph.startIndex, + EndIndexInBytes: indexOffset + glyph.endIndex, + GID: uint32(glyph.shapingGlyph.GlyphID), + Image: img, + X: float64(imgX), + Y: float64(imgY), + }) origin = origin.Add(fixed.Point26_6{ X: glyph.shapingGlyph.XAdvance, Y: -glyph.shapingGlyph.YAdvance, }) } - return glyphs + return clusters } func (g *GoTextFace) glyphImage(glyph glyph, origin fixed.Point26_6) (*ebiten.Image, int, int) { diff --git a/text/v2/layout.go b/text/v2/layout.go index eeb251864..90b71b9d1 100644 --- a/text/v2/layout.go +++ b/text/v2/layout.go @@ -105,7 +105,10 @@ func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) { } geoM := options.GeoM - for _, g := range AppendGlyphs(nil, text, face, &options.LayoutOptions) { + for _, g := range AppendClusters(nil, text, face, &options.LayoutOptions) { + if g.Image == nil { + continue + } op := &options.DrawImageOptions op.GeoM.Reset() op.GeoM.Translate(g.X, g.Y) @@ -114,16 +117,16 @@ func Draw(dst *ebiten.Image, text string, face Face, options *DrawOptions) { } } -// AppendGlyphs appends glyphs to the given slice and returns a slice. +// AppendClusters 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. +// AppendClusters is a low-level API, and you can use AppendClusters to have more control than Draw. +// AppendClusters 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) +// AppendClusters is concurrent-safe. +func AppendClusters(glyphs []Cluster, text string, face Face, options *LayoutOptions) []Cluster { + return appendClusters(glyphs, text, face, 0, 0, options) } // AppndVectorPath appends a vector path for glyphs to the given path. @@ -136,13 +139,13 @@ func AppendVectorPath(path *vector.Path, text string, face Face, options *Layout }) } -// appendGlyphs appends glyphs to the given slice and returns a slice. +// appendClusters appends glyphs to the given slice and returns a slice. // -// appendGlyphs assumes the text is rendered with the position (x, y). +// appendClusters 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 { +func appendClusters(glyphs []Cluster, text string, face Face, x, y float64, options *LayoutOptions) []Cluster { forEachLine(text, face, options, func(line string, indexOffset int, originX, originY float64) { - glyphs = face.appendGlyphsForLine(glyphs, line, indexOffset, originX+x, originY+y) + glyphs = face.appendClustersForLine(glyphs, line, indexOffset, originX+x, originY+y) }) return glyphs } diff --git a/text/v2/limited.go b/text/v2/limited.go index cefeb9c27..e75bc4209 100644 --- a/text/v2/limited.go +++ b/text/v2/limited.go @@ -55,9 +55,9 @@ func (l *LimitedFace) hasGlyph(r rune) bool { return l.unicodeRanges.contains(r) && l.face.hasGlyph(r) } -// appendGlyphsForLine implements Face. -func (l *LimitedFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph { - return l.face.appendGlyphsForLine(glyphs, l.unicodeRanges.filter(line), indexOffset, originX, originY) +// appendClustersForLine implements Face. +func (l *LimitedFace) appendClustersForLine(clusters []Cluster, line string, indexOffset int, originX, originY float64) []Cluster { + return l.face.appendClustersForLine(clusters, l.unicodeRanges.filter(line), indexOffset, originX, originY) } // appendVectorPathForLine implements Face. diff --git a/text/v2/multi.go b/text/v2/multi.go index 92d517c29..32db63c80 100644 --- a/text/v2/multi.go +++ b/text/v2/multi.go @@ -102,15 +102,15 @@ func (m *MultiFace) hasGlyph(r rune) bool { return false } -// appendGlyphsForLine implements Face. -func (m *MultiFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph { +// appendClustersForLine implements Face. +func (m *MultiFace) appendClustersForLine(clusters []Cluster, line string, indexOffset int, originX, originY float64) []Cluster { for _, c := range m.splitText(line) { if c.faceIndex == -1 { continue } f := m.faces[c.faceIndex] t := line[c.textStartIndex:c.textEndIndex] - glyphs = f.appendGlyphsForLine(glyphs, t, indexOffset, originX, originY) + clusters = f.appendClustersForLine(clusters, t, indexOffset, originX, originY) if a := f.advance(t); f.direction().isHorizontal() { originX += a } else { @@ -118,7 +118,7 @@ func (m *MultiFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset } indexOffset += len(t) } - return glyphs + return clusters } // appendVectorPathForLine implements Face. diff --git a/text/v2/std.go b/text/v2/std.go index 46cfaeded..4f1f46f93 100644 --- a/text/v2/std.go +++ b/text/v2/std.go @@ -93,8 +93,8 @@ func (s *StdFace) hasGlyph(r rune) bool { return ok } -// appendGlyphsForLine implements Face. -func (s *StdFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph { +// appendClustersForLine implements Face. +func (s *StdFace) appendClustersForLine(clusters []Cluster, line string, indexOffset int, originX, originY float64) []Cluster { s.copyCheck() origin := fixed.Point26_6{ @@ -108,23 +108,25 @@ func (s *StdFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i origin.X += s.f.Kern(prevR, r) } img, imgX, imgY, a := s.glyphImage(r, origin) - if img != nil { - // Adjust the position to the integers. - // The current glyph images assume that they are rendered on integer positions so far. - _, size := utf8.DecodeRuneInString(line[i:]) - glyphs = append(glyphs, Glyph{ - StartIndexInBytes: indexOffset + i, - EndIndexInBytes: indexOffset + i + size, - Image: img, - X: float64(imgX), - Y: float64(imgY), - }) - } + + // Adjust the position to the integers. + // The current glyph images assume that they are rendered on integer positions so far. + _, size := utf8.DecodeRuneInString(line[i:]) + + // Append a glyph even if img is nil. + // This is necessary to return index information for control characters. + clusters = append(clusters, Cluster{ + StartIndexInBytes: indexOffset + i, + EndIndexInBytes: indexOffset + i + size, + Image: img, + X: float64(imgX), + Y: float64(imgY), + }) origin.X += a prevR = r } - return glyphs + return clusters } func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) { diff --git a/text/v2/text.go b/text/v2/text.go index 9d133435b..21512e1fa 100644 --- a/text/v2/text.go +++ b/text/v2/text.go @@ -36,7 +36,7 @@ type Face interface { hasGlyph(r rune) bool - appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph + appendClustersForLine(glyphs []Cluster, line string, indexOffset int, originX, originY float64) []Cluster appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) direction() Direction @@ -117,12 +117,12 @@ func adjustGranularity(x fixed.Int26_6, face Face) fixed.Int26_6 { return x / factor * factor } -// Glyph represents one glyph to render. -type Glyph struct { - // StartIndexInBytes is the start index in bytes for the given string at AppendGlyphs. +// Cluster represents one grapheme cluster. +type Cluster struct { + // StartIndexInBytes is the start index in bytes for the given string at AppendClusters. StartIndexInBytes int - // EndIndexInBytes is the end index in bytes for the given string at AppendGlyphs. + // EndIndexInBytes is the end index in bytes for the given string at AppendClusters. EndIndexInBytes int // GID is an ID for a glyph of TrueType or OpenType font. GID is valid when the face is GoTextFace. @@ -134,12 +134,12 @@ type Glyph struct { Image *ebiten.Image // X is the X position to render this glyph. - // The position is determined in a sequence of characters given at AppendGlyphs. + // The position is determined in a sequence of characters given at AppendClusters. // The position's origin is the first character's origin position. X float64 // Y is the Y position to render this glyph. - // The position is determined in a sequence of characters given at AppendGlyphs. + // The position is determined in a sequence of characters given at AppendClusters. // The position's origin is the first character's origin position. Y float64 } @@ -245,10 +245,10 @@ func CacheGlyphs(text string, face Face) { c := glyphVariationCount(face) - var buf []Glyph + var buf []Cluster // Create all the possible variations (#2528). for i := 0; i < c; i++ { - buf = appendGlyphs(buf, text, face, x, y, nil) + buf = appendClusters(buf, text, face, x, y, nil) buf = buf[:0] if face.direction().isHorizontal() { diff --git a/text/v2/text_test.go b/text/v2/text_test.go index 01e123d62..0de32e4fd 100644 --- a/text/v2/text_test.go +++ b/text/v2/text_test.go @@ -40,7 +40,7 @@ over the lazy dog.` f := text.NewStdFace(bitmapfont.Face) got := sampleText - for _, g := range text.AppendGlyphs(nil, sampleText, f, nil) { + for _, g := range text.AppendClusters(nil, sampleText, f, nil) { got = got[:g.StartIndexInBytes] + strings.Repeat(" ", g.EndIndexInBytes-g.StartIndexInBytes) + got[g.EndIndexInBytes:] } want := regexp.MustCompile(`\S`).ReplaceAllString(sampleText, " ")