diff --git a/paint/renderers/htmlcanvas/htmlcanvas.go b/paint/renderers/htmlcanvas/htmlcanvas.go
index 382dd871c5..97ee06df45 100644
--- a/paint/renderers/htmlcanvas/htmlcanvas.go
+++ b/paint/renderers/htmlcanvas/htmlcanvas.go
@@ -10,9 +10,7 @@
package htmlcanvas
import (
- "fmt"
"image"
- "strings"
"syscall/js"
"cogentcore.org/core/colors"
@@ -23,8 +21,6 @@ import (
"cogentcore.org/core/paint/render"
"cogentcore.org/core/styles"
"cogentcore.org/core/styles/units"
- "cogentcore.org/core/text/rich"
- "cogentcore.org/core/text/shaped"
)
// Renderer is an HTML canvas renderer.
@@ -222,40 +218,6 @@ func (rs *Renderer) RenderPath(pt *render.Path) {
}
}
-func (rs *Renderer) RenderText(text *render.Text) {
- // TODO: improve
- for _, line := range text.Text.Lines {
- for i, run := range line.Runs {
- span := line.Source[i]
- st := &rich.Style{}
- raw := st.FromRunes(span)
-
- rs.applyTextStyle(st, run, text)
- // TODO: probably should do something better for pos
- pos := run.MaxBounds.Max.Add(line.Offset).Add(text.Position)
- rs.ctx.Call("fillText", string(raw), pos.X, pos.Y)
- }
- }
-}
-
-// applyTextStyle applies the given [rich.Style] to the HTML canvas context.
-func (rs *Renderer) applyTextStyle(s *rich.Style, run shaped.Run, text *render.Text) {
- // See https://developer.mozilla.org/en-US/docs/Web/CSS/font
- // TODO: fix font weight, font size, line height, font family
- parts := []string{s.Slant.String(), "normal", s.Weight.String(), s.Stretch.String(), fmt.Sprintf("%gpx/%g", s.Size*text.Text.FontSize, text.Text.LineHeight), s.Family.String()}
- rs.ctx.Set("font", strings.Join(parts, " "))
-
- // TODO: use caching like in RenderPath?
- if run.FillColor == nil {
- run.FillColor = colors.Uniform(text.Text.Color)
- }
- // if run.StrokeColor == nil {
- // run.StrokeColor = ctx.Style.Stroke.Color
- // }
- rs.ctx.Set("fillStyle", rs.imageToStyle(run.FillColor))
- rs.ctx.Set("strokeStyle", rs.imageToStyle(run.StrokeColor))
-}
-
func jsAwait(v js.Value) (result js.Value, ok bool) { // TODO: use wgpu version
// COPIED FROM https://go-review.googlesource.com/c/go/+/150917/
if v.Type() != js.TypeObject || v.Get("then").Type() != js.TypeFunction {
diff --git a/paint/renderers/htmlcanvas/text.go b/paint/renderers/htmlcanvas/text.go
new file mode 100644
index 0000000000..da90ab4a9b
--- /dev/null
+++ b/paint/renderers/htmlcanvas/text.go
@@ -0,0 +1,106 @@
+// Copyright (c) 2025, Cogent Core. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build js
+
+package htmlcanvas
+
+import (
+ "fmt"
+ "image"
+ "strings"
+
+ "cogentcore.org/core/colors"
+ "cogentcore.org/core/math32"
+ "cogentcore.org/core/paint/render"
+ "cogentcore.org/core/text/rich"
+ "cogentcore.org/core/text/shaped"
+)
+
+// RenderText rasterizes the given Text
+func (rs *Renderer) RenderText(txt *render.Text) {
+ rs.TextLines(txt.Text, &txt.Context, txt.Position)
+}
+
+// TextLines rasterizes the given shaped.Lines.
+// The text will be drawn starting at the start pixel position, which specifies the
+// left baseline location of the first text item..
+func (rs *Renderer) TextLines(lns *shaped.Lines, ctx *render.Context, pos math32.Vector2) {
+ start := pos.Add(lns.Offset)
+ // rs.Scanner.SetClip(ctx.Bounds.Rect.ToRect())
+ clr := colors.Uniform(lns.Color)
+ runes := lns.Source.Join() // TODO: bad for performance with append
+ for li := range lns.Lines {
+ ln := &lns.Lines[li]
+ rs.TextLine(ln, lns, runes, clr, start) // todo: start + offset
+ }
+}
+
+// TextLine rasterizes the given shaped.Line.
+func (rs *Renderer) TextLine(ln *shaped.Line, lns *shaped.Lines, runes []rune, clr image.Image, start math32.Vector2) {
+ off := start.Add(ln.Offset)
+ for ri := range ln.Runs {
+ run := &ln.Runs[ri]
+ rs.TextRun(run, ln, lns, runes, clr, off)
+ if run.Direction.IsVertical() {
+ off.Y += math32.FromFixed(run.Advance)
+ } else {
+ off.X += math32.FromFixed(run.Advance)
+ }
+ }
+}
+
+// TextRun rasterizes the given text run into the output image using the
+// font face set in the shaping.
+// The text will be drawn starting at the start pixel position.
+func (rs *Renderer) TextRun(run *shaped.Run, ln *shaped.Line, lns *shaped.Lines, runes []rune, clr image.Image, start math32.Vector2) {
+ // todo: render strike-through
+ // dir := run.Direction
+ // rbb := run.MaxBounds.Translate(start)
+ if run.Background != nil {
+ // rs.FillBounds(rbb, run.Background) TODO
+ }
+ if len(ln.Selections) > 0 {
+ for _, sel := range ln.Selections {
+ rsel := sel.Intersect(run.Runes())
+ if rsel.Len() > 0 {
+ fi := run.FirstGlyphAt(rsel.Start)
+ li := run.LastGlyphAt(rsel.End - 1)
+ if fi >= 0 && li >= fi {
+ // sbb := run.GlyphRegionBounds(fi, li) TODO
+ // rs.FillBounds(sbb.Translate(start), lns.SelectionColor) TODO
+ }
+ }
+ }
+ }
+ // fill := clr
+ // if run.FillColor != nil {
+ // fill = run.FillColor
+ // }
+ // stroke := run.StrokeColor
+ // fsz := math32.FromFixed(run.Size)
+
+ region := run.Runes()
+ raw := runes[region.Start:region.End]
+ rs.ctx.Call("fillText", string(raw), start.X, start.Y)
+}
+
+// applyTextStyle applies the given [rich.Style] to the HTML canvas context.
+func (rs *Renderer) applyTextStyle(s *rich.Style, run shaped.Run, text *render.Text) {
+ // See https://developer.mozilla.org/en-US/docs/Web/CSS/font
+ // TODO: fix font weight, font size, line height, font family
+ fmt.Println(s.Weight.String())
+ parts := []string{s.Slant.String(), "normal", s.Weight.String(), s.Stretch.String(), fmt.Sprintf("%gpx/%g", s.Size*text.Text.FontSize, text.Text.LineHeight), s.Family.String()}
+ rs.ctx.Set("font", strings.Join(parts, " "))
+
+ // TODO: use caching like in RenderPath?
+ if run.FillColor == nil {
+ run.FillColor = colors.Uniform(text.Text.Color)
+ }
+ // if run.StrokeColor == nil {
+ // run.StrokeColor = ctx.Style.Stroke.Color
+ // }
+ rs.ctx.Set("fillStyle", rs.imageToStyle(run.FillColor))
+ rs.ctx.Set("strokeStyle", rs.imageToStyle(run.StrokeColor))
+}