Use shaper.Shape() and shaper.Bitmaps() to draw glyphs directly (no double-shaping)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Greg Pomerantz 2026-05-09 11:13:06 -04:00
parent 0fdaea990f
commit 16719afb95
2 changed files with 52 additions and 19 deletions

View File

@ -2,10 +2,13 @@ package ui
import (
"image"
"image/color"
"gioui.org/f32"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/text"
"gioui.org/unit"
"gioui.org/widget/material"
@ -107,10 +110,8 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
}
}
// Position filename at (reg.X + 8, filenameLineY)
call1 := op.Offset(image.Point{X: int(reg.X + unit.Dp(8)), Y: int(filenameLineY)}).Push(gtx.Ops)
material.Label(th, r.theme.FontSize, displayFilename).Layout(gtx)
call1.Pop()
// Position filename at (reg.X + 8, filenameLineY) - draw using shaper to avoid double-shaping
r.drawText(gtx, th.Shaper, displayFilename, r.theme.FontSize, reg.X+unit.Dp(8), filenameLineY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
// Line 2: Icons
iconsLineY := filenameLineY + filenameLineH
@ -120,21 +121,15 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
iconY := iconsLineY
if sb.CutCopy {
call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops)
material.Label(th, r.theme.FontSize, "✂").Layout(gtx)
call.Pop()
r.drawText(gtx, th.Shaper, "✂", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
leftX += unit.Dp(36)
}
if sb.Copy {
call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops)
material.Label(th, r.theme.FontSize, "⎘").Layout(gtx)
call.Pop()
r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
leftX += unit.Dp(36)
}
if sb.Paste {
call := op.Offset(image.Point{X: int(leftX), Y: int(iconY)}).Push(gtx.Ops)
material.Label(th, r.theme.FontSize, "⎘").Layout(gtx)
call.Pop()
r.drawText(gtx, th.Shaper, "⎘", r.theme.FontSize, leftX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
leftX += unit.Dp(36)
}
@ -142,15 +137,11 @@ func (r *Renderer) drawStatusBar(gtx layout.Context, sb StatusBar) {
rightX := reg.X + reg.W - unit.Dp(8)
if sb.Search {
rightX -= unit.Dp(36)
call := op.Offset(image.Point{X: int(rightX), Y: int(iconY)}).Push(gtx.Ops)
material.Label(th, r.theme.FontSize, "🔍").Layout(gtx)
call.Pop()
r.drawText(gtx, th.Shaper, "🔍", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
}
if sb.ConflictIcon {
rightX -= unit.Dp(36)
call := op.Offset(image.Point{X: int(rightX), Y: int(iconY)}).Push(gtx.Ops)
material.Label(th, r.theme.FontSize, "⚠").Layout(gtx)
call.Pop()
r.drawText(gtx, th.Shaper, "⚠", r.theme.FontSize, rightX, iconY, color.NRGBA{R: 0, G: 0, B: 0, A: 255})
}
c.Pop()
@ -205,6 +196,48 @@ func boolToString(b bool) string {
return "Off"
}
// drawText draws text using the same approach as Gio's textView:
// layout text, iterate glyphs, buffer into lines, and draw using shaper.Shape().
func (r *Renderer) drawText(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp, x, y unit.Dp, col color.NRGBA) {
// Layout text
shp.LayoutString(text.Parameters{PxPerEm: fixed.I(gtx.Sp(size))}, str)
// Draw glyphs using the same approach as Gio's textView
var glyphs [32]text.Glyph
line := glyphs[:0]
for g, ok := shp.NextGlyph(); ok; g, ok = shp.NextGlyph() {
line = append(line, g)
if g.Flags&text.FlagLineBreak != 0 || cap(line)-len(line) == 0 {
r.drawLine(gtx, shp, line, x, y, col)
line = line[:0]
}
}
if len(line) > 0 {
r.drawLine(gtx, shp, line, x, y, col)
}
}
// drawLine draws a line of glyphs using shaper.Shape() and shaper.Bitmaps().
func (r *Renderer) drawLine(gtx layout.Context, shp *text.Shaper, line []text.Glyph, x, y unit.Dp, col color.NRGBA) {
// Apply offset transform
off := f32.Point{X: float32(x), Y: float32(y)}
t := op.Affine(f32.Affine2D{}.Offset(off)).Push(gtx.Ops)
// Draw vector glyphs
path := shp.Shape(line)
outline := clip.Outline{Path: path}.Op().Push(gtx.Ops)
paint.ColorOp{Color: col}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
outline.Pop()
// Draw bitmap glyphs (emoji, etc.)
if call := shp.Bitmaps(line); call != (op.CallOp{}) {
call.Add(gtx.Ops)
}
t.Pop()
}
// charWidths returns the cumulative width after each rune in str.
func charWidths(gtx layout.Context, shp *text.Shaper, str string, size unit.Sp) []int {
// Match Gio's textView: PxPerEm = font size in device pixels

BIN
pad

Binary file not shown.