package ui import ( "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" "golang.org/x/image/math/fixed" ) // ShapedText holds layout results from a single ShapeText call. // The PathSpec and BitmapsCall can be drawn without further shaper access. type ShapedText struct { Width int // total width in pixels PathSpec clip.PathSpec // vector glyph path, ready to draw BitmapsCall op.CallOp // bitmap glyph call (empty if none) FirstX fixed.Int26_6 // first glyph X offset FirstY int32 // first glyph Y offset Text string FontSize unit.Sp } // ShapeText layouts text, measures width, and returns a ShapedText // with an immutable path ready for drawing. The shaper is called once. func (r *Renderer) ShapeText(gtx layout.Context, str string, size unit.Sp) ShapedText { r.shp.LayoutString(text.Parameters{ PxPerEm: fixed.I(gtx.Sp(size)), MinWidth: 0, MaxWidth: 1000, MaxLines: 1, }, str) // Collect glyphs and measure width var glyphs []text.Glyph var cumWidth fixed.Int26_6 for { g, ok := r.shp.NextGlyph() if !ok { break } cumWidth += g.Advance glyphs = append(glyphs, g) } // Build lines and collect the last line's path spec var lastSpec clip.PathSpec var lastBitmaps op.CallOp var firstX fixed.Int26_6 var firstY int32 var line []text.Glyph for _, g := range glyphs { if len(line) == 0 { firstX = g.X firstY = g.Y } line = append(line, g) if g.Flags&text.FlagLineBreak != 0 || cap(line)-len(line) == 0 { lastSpec = r.shp.Shape(line) lastBitmaps = r.shp.Bitmaps(line) line = line[:0] } } if len(line) > 0 { lastSpec = r.shp.Shape(line) lastBitmaps = r.shp.Bitmaps(line) } return ShapedText{ Width: int(cumWidth >> 6), PathSpec: lastSpec, BitmapsCall: lastBitmaps, FirstX: firstX, FirstY: firstY, Text: str, FontSize: size, } } // DrawShapedText draws a pre-shaped text at the given position. // x, y are in Dp. Uses the stored path spec — zero shaper calls. func (r *Renderer) DrawShapedText(gtx layout.Context, st ShapedText, x, y Dp, col color.NRGBA) { xPx := r.toPx(x) yPx := r.toPx(y) m := op.Record(gtx.Ops) // Compute offset: position + first glyph offset offX := float32(xPx) + float32(st.FirstX)/64.0 offY := float32(yPx) + float32(st.FirstY) t := op.Affine(f32.Affine2D{}.Offset(f32.Pt(offX, offY))).Push(gtx.Ops) // Draw vector path outline := clip.Outline{Path: st.PathSpec}.Op().Push(gtx.Ops) paint.ColorOp{Color: col}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) outline.Pop() // Draw bitmap glyphs if st.BitmapsCall != (op.CallOp{}) { st.BitmapsCall.Add(gtx.Ops) } t.Pop() call := m.Stop() call.Add(gtx.Ops) }