Canvas refactoring

This commit is contained in:
anoshenko 2022-11-02 20:08:02 +03:00
parent b26525e892
commit 8200f98d0d
9 changed files with 471 additions and 508 deletions

View File

@ -4,6 +4,8 @@ package rui
import ( import (
_ "embed" _ "embed"
"encoding/base64"
"path/filepath"
"strings" "strings"
"syscall/js" "syscall/js"
) )
@ -112,10 +114,46 @@ func (app *wasmApp) createSession() Session {
return session return session
} }
func (app *wasmApp) init() { func (app *wasmApp) init(params AppParams) {
app.params = params
document := js.Global().Get("document") document := js.Global().Get("document")
body := document.Call("querySelector", "body") body := document.Call("querySelector", "body")
head := document.Call("querySelector", "head")
meta := document.Call("createElement", "meta")
meta.Set("name", "viewport")
meta.Set("content", "width=device-width")
head.Call("appendChild", meta)
meta = document.Call("createElement", "base")
meta.Set("target", "_blank")
meta.Set("rel", "noopener")
head.Call("appendChild", meta)
if params.Icon != "" {
if image, ok := resources.images[params.Icon]; ok && image.fs != nil {
dataType := map[string]string{
".svg": "data:image/svg+xml",
".png": "data:image/png",
".jpg": "data:image/jpg",
".jpeg": "data:image/jpg",
".gif": "data:image/gif",
}
ext := strings.ToLower(filepath.Ext(params.Icon))
if prefix, ok := dataType[ext]; ok {
if data, err := image.fs.ReadFile(image.path); err == nil {
meta = document.Call("createElement", "link")
meta.Set("rel", "icon")
meta.Set("href", prefix+";base64,"+base64.StdEncoding.EncodeToString(data))
head.Call("appendChild", meta)
} else {
DebugLog(err.Error())
}
}
}
}
script := document.Call("createElement", "script") script := document.Call("createElement", "script")
script.Set("type", "text/javascript") script.Set("type", "text/javascript")
@ -156,6 +194,11 @@ func (app *wasmApp) init() {
div.Set("download", "") div.Set("download", "")
div.Set("style", "display: none;") div.Set("style", "display: none;")
body.Call("appendChild", div) body.Call("appendChild", div)
if params.TitleColor != 0 {
app.brige.runFunc("setTitleColor", params.TitleColor.cssString())
}
} }
// StartApp - create the new wasmApp and start it // StartApp - create the new wasmApp and start it
@ -168,11 +211,10 @@ func StartApp(addr string, createContentFunc func(Session) SessionContent, param
} }
app := new(wasmApp) app := new(wasmApp)
app.params = params
app.createContentFunc = createContentFunc app.createContentFunc = createContentFunc
app.brige = createWasmBrige() app.brige = createWasmBrige()
app.init() app.init(params)
<-app.close <-app.close
} }

View File

@ -1796,3 +1796,16 @@ function getPropertyValue(answerID, elementId, name) {
function appendStyles(styles) { function appendStyles(styles) {
document.querySelector('style').textContent += styles document.querySelector('style').textContent += styles
} }
function getCanvasContext(elementId) {
const canvas = document.getElementById(elementId)
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
//var gradient;
//var path;
//var img;
ctx.canvas.width = dpr * canvas.clientWidth;
ctx.canvas.height = dpr * canvas.clientHeight;
ctx.scale(dpr, dpr);
return ctx;
}

543
canvas.go
View File

@ -1,7 +1,7 @@
package rui package rui
import ( import (
"fmt" "math"
"strconv" "strconv"
"strings" "strings"
) )
@ -290,42 +290,24 @@ type Canvas interface {
// in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary // in the rectangle (dstX, dstY, dstWidth, dstHeight), scaling in height and width if necessary
DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image) DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image)
finishDraw() string finishDraw()
} }
type canvasData struct { type canvasData struct {
view CanvasView view CanvasView
script strings.Builder session Session
} }
func newCanvas(view CanvasView) Canvas { func newCanvas(view CanvasView) Canvas {
canvas := new(canvasData) canvas := new(canvasData)
canvas.view = view canvas.view = view
canvas.script.Grow(4096) canvas.session = view.Session()
canvas.script.WriteString(`const canvas = document.getElementById('`) canvas.session.cavnasStart(view.htmlID())
canvas.script.WriteString(view.htmlID())
canvas.script.WriteString(`');
const ctx = canvas.getContext('2d');
const dpr = window.devicePixelRatio || 1;
var gradient;
var path;
var img;
ctx.canvas.width = dpr * canvas.clientWidth;
ctx.canvas.height = dpr * canvas.clientHeight;
ctx.scale(dpr, dpr);`)
/*
canvas.script.WriteString(strconv.FormatFloat(view.canvasWidth(), 'g', -1, 64))
canvas.script.WriteString(`;
ctx.canvas.height = dpr * `)
canvas.script.WriteString(strconv.FormatFloat(view.canvasHeight(), 'g', -1, 64))
canvas.script.WriteString(";\nctx.scale(dpr, dpr);")
*/
return canvas return canvas
} }
func (canvas *canvasData) finishDraw() string { func (canvas *canvasData) finishDraw() {
canvas.script.WriteString("\n") canvas.session.cavnasFinish()
return canvas.script.String()
} }
func (canvas *canvasData) View() CanvasView { func (canvas *canvasData) View() CanvasView {
@ -347,162 +329,102 @@ func (canvas *canvasData) Height() float64 {
} }
func (canvas *canvasData) Save() { func (canvas *canvasData) Save() {
canvas.script.WriteString("\nctx.save();") canvas.session.callCanvasFunc("save")
} }
func (canvas *canvasData) Restore() { func (canvas *canvasData) Restore() {
canvas.script.WriteString("\nctx.restore();") canvas.session.callCanvasFunc("restore")
} }
func (canvas *canvasData) ClipRect(x, y, width, height float64) { func (canvas *canvasData) ClipRect(x, y, width, height float64) {
canvas.script.WriteString("\nctx.beginPath();\nctx.rect(") canvas.session.callCanvasFunc("beginPath")
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) canvas.session.callCanvasFunc("rect", x, y, width, height)
canvas.script.WriteRune(',') canvas.session.callCanvasFunc("clip")
canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(width, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(height, 'g', -1, 64))
canvas.script.WriteString(");\nctx.clip();")
} }
func (canvas *canvasData) ClipPath(path Path) { func (canvas *canvasData) ClipPath(path Path) {
canvas.script.WriteString(path.scriptText()) path.create(canvas.session)
canvas.script.WriteString("\nctx.clip();") canvas.session.callCanvasFunc("clip")
} }
func (canvas *canvasData) SetScale(x, y float64) { func (canvas *canvasData) SetScale(x, y float64) {
canvas.script.WriteString("\nctx.scale(") canvas.session.callCanvasFunc("scale", x, y)
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
canvas.script.WriteString(");")
} }
func (canvas *canvasData) SetTranslation(x, y float64) { func (canvas *canvasData) SetTranslation(x, y float64) {
canvas.script.WriteString("\nctx.translate(") canvas.session.callCanvasFunc("translate", x, y)
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
canvas.script.WriteString(");")
} }
func (canvas *canvasData) SetRotation(angle float64) { func (canvas *canvasData) SetRotation(angle float64) {
canvas.script.WriteString("\nctx.rotate(") canvas.session.callCanvasFunc("rotate", angle)
canvas.script.WriteString(strconv.FormatFloat(angle, 'g', -1, 64))
canvas.script.WriteString(");")
} }
func (canvas *canvasData) SetTransformation(xScale, yScale, xSkew, ySkew, dx, dy float64) { func (canvas *canvasData) SetTransformation(xScale, yScale, xSkew, ySkew, dx, dy float64) {
canvas.script.WriteString("\nctx.transform(") canvas.session.callCanvasFunc("transform", xScale, ySkew, xSkew, yScale, dx, dy)
canvas.script.WriteString(strconv.FormatFloat(xScale, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(ySkew, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(xSkew, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(yScale, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(dx, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(dy, 'g', -1, 64))
canvas.script.WriteString(");")
} }
func (canvas *canvasData) ResetTransformation() { func (canvas *canvasData) ResetTransformation() {
canvas.script.WriteString("\nctx.resetTransform();\nctx.scale(dpr, dpr);") canvas.session.callCanvasFunc("resetTransform")
canvas.session.callCanvasFunc("scale", canvas.session.PixelRatio(), canvas.session.PixelRatio())
//canvas.session.callCanvasFunc("scale", angle)
// TODO canvas.script.WriteString("\nctx.resetTransform();\nctx.scale(dpr, dpr);")
} }
func (canvas *canvasData) SetSolidColorFillStyle(color Color) { func (canvas *canvasData) SetSolidColorFillStyle(color Color) {
canvas.script.WriteString("\nctx.fillStyle = \"") canvas.session.updateCanvasProperty("fillStyle", color.cssString())
canvas.script.WriteString(color.cssString())
canvas.script.WriteString(`";`)
} }
func (canvas *canvasData) SetSolidColorStrokeStyle(color Color) { func (canvas *canvasData) SetSolidColorStrokeStyle(color Color) {
canvas.script.WriteString("\nctx.strokeStyle = \"") canvas.session.updateCanvasProperty("strokeStyle", color.cssString())
canvas.script.WriteString(color.cssString())
canvas.script.WriteString(`";`)
} }
func (canvas *canvasData) setLinearGradient(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) { func (canvas *canvasData) createLinearGradient(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) any {
canvas.script.WriteString("\ngradient = ctx.createLinearGradient(")
canvas.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64)) gradient := canvas.session.createCanvasVar("createLinearGradient", x0, y0, x1, y1)
canvas.script.WriteRune(',') canvas.session.callCanvasVarFunc(gradient, "addColorStop", 0, color0.cssString())
canvas.script.WriteString(strconv.FormatFloat(y0, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(x1, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y1, 'g', -1, 64))
canvas.script.WriteString(");\ngradient.addColorStop(0, '")
canvas.script.WriteString(color0.cssString())
canvas.script.WriteString("');")
for _, point := range stopPoints { for _, point := range stopPoints {
if point.Offset >= 0 && point.Offset <= 1 { if point.Offset >= 0 && point.Offset <= 1 {
canvas.script.WriteString("\ngradient.addColorStop(") canvas.session.callCanvasVarFunc(gradient, "addColorStop", point.Offset, point.Color.cssString())
canvas.script.WriteString(strconv.FormatFloat(point.Offset, 'g', -1, 64))
canvas.script.WriteString(", '")
canvas.script.WriteString(point.Color.cssString())
canvas.script.WriteString("');")
} }
} }
canvas.script.WriteString("\ngradient.addColorStop(1, '") canvas.session.callCanvasVarFunc(gradient, "addColorStop", 1, color1.cssString())
canvas.script.WriteString(color1.cssString()) return gradient
canvas.script.WriteString("');")
} }
func (canvas *canvasData) SetLinearGradientFillStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) { func (canvas *canvasData) SetLinearGradientFillStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) {
canvas.setLinearGradient(x0, y0, color0, x1, y1, color1, stopPoints) gradient := canvas.createLinearGradient(x0, y0, color0, x1, y1, color1, stopPoints)
canvas.script.WriteString("\nctx.fillStyle = gradient;") canvas.session.updateCanvasProperty("fillStyle", gradient)
} }
func (canvas *canvasData) SetLinearGradientStrokeStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) { func (canvas *canvasData) SetLinearGradientStrokeStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) {
canvas.setLinearGradient(x0, y0, color0, x1, y1, color1, stopPoints) gradient := canvas.createLinearGradient(x0, y0, color0, x1, y1, color1, stopPoints)
canvas.script.WriteString("\nctx.strokeStyle = gradient;") canvas.session.updateCanvasProperty("strokeStyle", gradient)
} }
func (canvas *canvasData) setRadialGradient(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) { func (canvas *canvasData) createRadialGradient(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) any {
canvas.script.WriteString("\ngradient = ctx.createRadialGradient(") gradient := canvas.session.createCanvasVar("createRadialGradient", x0, y0, r0, x1, y1, r1)
canvas.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64)) canvas.session.callCanvasVarFunc(gradient, "addColorStop", 0, color0.cssString())
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y0, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(r0, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(x1, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y1, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(r1, 'g', -1, 64))
canvas.script.WriteString(");\ngradient.addColorStop(0, '")
canvas.script.WriteString(color0.cssString())
canvas.script.WriteString("');")
for _, point := range stopPoints { for _, point := range stopPoints {
if point.Offset >= 0 && point.Offset <= 1 { if point.Offset >= 0 && point.Offset <= 1 {
canvas.script.WriteString("\ngradient.addColorStop(") canvas.session.callCanvasVarFunc(gradient, "addColorStop", point.Offset, point.Color.cssString())
canvas.script.WriteString(strconv.FormatFloat(point.Offset, 'g', -1, 64))
canvas.script.WriteString(", '")
canvas.script.WriteString(point.Color.cssString())
canvas.script.WriteString("');")
} }
} }
canvas.script.WriteString("\ngradient.addColorStop(1, '") canvas.session.callCanvasVarFunc(gradient, "addColorStop", 1, color1.cssString())
canvas.script.WriteString(color1.cssString()) return gradient
canvas.script.WriteString("');")
} }
func (canvas *canvasData) SetRadialGradientFillStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) { func (canvas *canvasData) SetRadialGradientFillStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) {
canvas.setRadialGradient(x0, y0, r0, color0, x1, y1, r1, color1, stopPoints) gradient := canvas.createRadialGradient(x0, y0, r0, color0, x1, y1, r1, color1, stopPoints)
canvas.script.WriteString("\nctx.fillStyle = gradient;") canvas.session.updateCanvasProperty("fillStyle", gradient)
} }
func (canvas *canvasData) SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) { func (canvas *canvasData) SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) {
canvas.setRadialGradient(x0, y0, r0, color0, x1, y1, r1, color1, stopPoints) gradient := canvas.createRadialGradient(x0, y0, r0, color0, x1, y1, r1, color1, stopPoints)
canvas.script.WriteString("\nctx.strokeStyle = gradient;") canvas.session.updateCanvasProperty("strokeStyle", gradient)
} }
func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) { func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) {
@ -528,89 +450,70 @@ func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) {
return return
} }
canvas.script.WriteString("\nimg = images.get('") canvas.session.callCanvasImageFunc(image.URL(), "fillStyle", "createPattern", repeatText)
canvas.script.WriteString(image.URL())
canvas.script.WriteString("');\nif (img) {\nctx.fillStyle = ctx.createPattern(img,'")
canvas.script.WriteString(repeatText)
canvas.script.WriteString("');\n}")
} }
func (canvas *canvasData) SetLineWidth(width float64) { func (canvas *canvasData) SetLineWidth(width float64) {
if width > 0 { if width > 0 {
canvas.script.WriteString("\nctx.lineWidth = '") canvas.session.updateCanvasProperty("lineWidth", width)
canvas.script.WriteString(strconv.FormatFloat(width, 'g', -1, 64))
canvas.script.WriteString("';")
} }
} }
func (canvas *canvasData) SetLineJoin(join int) { func (canvas *canvasData) SetLineJoin(join int) {
switch join { switch join {
case MiterJoin: case MiterJoin:
canvas.script.WriteString("\nctx.lineJoin = 'miter';") canvas.session.updateCanvasProperty("lineJoin", "miter")
case RoundJoin: case RoundJoin:
canvas.script.WriteString("\nctx.lineJoin = 'round';") canvas.session.updateCanvasProperty("lineJoin", "round")
case BevelJoin: case BevelJoin:
canvas.script.WriteString("\nctx.lineJoin = 'bevel';") canvas.session.updateCanvasProperty("lineJoin", "bevel")
} }
} }
func (canvas *canvasData) SetLineCap(cap int) { func (canvas *canvasData) SetLineCap(cap int) {
switch cap { switch cap {
case ButtCap: case ButtCap:
canvas.script.WriteString("\nctx.lineCap = 'butt';") canvas.session.updateCanvasProperty("lineCap", "butt")
case RoundCap: case RoundCap:
canvas.script.WriteString("\nctx.lineCap = 'round';") canvas.session.updateCanvasProperty("lineCap", "round")
case SquareCap: case SquareCap:
canvas.script.WriteString("\nctx.lineCap = 'square';") canvas.session.updateCanvasProperty("lineCap", "square")
} }
} }
func (canvas *canvasData) SetLineDash(dash []float64, offset float64) { func (canvas *canvasData) SetLineDash(dash []float64, offset float64) {
canvas.script.WriteString("\nctx.setLineDash([") canvas.session.callCanvasFunc("setLineDash", dash)
for i, d := range dash {
if i > 0 {
canvas.script.WriteString(",")
}
canvas.script.WriteString(strconv.FormatFloat(d, 'g', -1, 64))
}
canvas.script.WriteString("]);")
if offset >= 0 { if offset >= 0 {
canvas.script.WriteString("\nctx.lineDashOffset = '") canvas.session.updateCanvasProperty("lineDashOffset", offset)
canvas.script.WriteString(strconv.FormatFloat(offset, 'g', -1, 64))
canvas.script.WriteString("';")
} }
} }
func (canvas *canvasData) writeFont(name string, script *strings.Builder) { /*
names := strings.Split(name, ",") func (canvas *canvasData) convertFont(name string) string {
lead := " " buffer := allocStringBuilder()
for _, font := range names { defer freeStringBuilder(buffer)
font = strings.Trim(font, " \n\"'")
script.WriteString(lead) for i, font := range strings.Split(name, ",") {
lead = "," font = strings.Trim(font, " \n\"'")
if strings.Contains(font, " ") { if i > 0 {
script.WriteRune('"') buffer.WriteRune(',')
script.WriteString(font) }
script.WriteRune('"') if strings.Contains(font, " ") {
} else { buffer.WriteRune('"')
script.WriteString(font) buffer.WriteString(font)
buffer.WriteRune('"')
} else {
buffer.WriteString(font)
}
} }
return buffer.String()
} }
script.WriteString("';") */
}
func (canvas *canvasData) SetFont(name string, size SizeUnit) {
canvas.script.WriteString("\nctx.font = '")
canvas.script.WriteString(size.cssString("1rem", canvas.View().Session()))
canvas.writeFont(name, &canvas.script)
}
func (canvas *canvasData) fontWithParams(name string, size SizeUnit, params FontParams) string { func (canvas *canvasData) fontWithParams(name string, size SizeUnit, params FontParams) string {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
@ -672,135 +575,76 @@ func (canvas *canvasData) fontWithParams(name string, size SizeUnit, params Font
return buffer.String() return buffer.String()
} }
func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params FontParams, script *strings.Builder) { func (canvas *canvasData) SetFont(name string, size SizeUnit) {
script.WriteString("\nctx.font = '") canvas.session.updateCanvasProperty("font", canvas.fontWithParams(name, size, FontParams{}))
if params.Italic {
script.WriteString("italic ")
}
if params.SmallCaps {
script.WriteString("small-caps ")
}
if params.Weight > 0 && params.Weight <= 9 {
switch params.Weight {
case 4:
script.WriteString("normal ")
case 7:
script.WriteString("bold ")
default:
script.WriteString(strconv.Itoa(params.Weight * 100))
script.WriteRune(' ')
}
}
script.WriteString(size.cssString("1rem", canvas.View().Session()))
switch params.LineHeight.Type {
case Auto:
case SizeInPercent:
if params.LineHeight.Value != 100 {
script.WriteString("/")
script.WriteString(strconv.FormatFloat(params.LineHeight.Value/100, 'g', -1, 64))
}
case SizeInFraction:
if params.LineHeight.Value != 1 {
script.WriteString("/")
script.WriteString(strconv.FormatFloat(params.LineHeight.Value, 'g', -1, 64))
}
default:
script.WriteString("/")
script.WriteString(params.LineHeight.cssString("", canvas.View().Session()))
}
canvas.writeFont(name, script)
} }
func (canvas *canvasData) SetFontWithParams(name string, size SizeUnit, params FontParams) { func (canvas *canvasData) SetFontWithParams(name string, size SizeUnit, params FontParams) {
canvas.setFontWithParams(name, size, params, &canvas.script) canvas.session.updateCanvasProperty("font", canvas.fontWithParams(name, size, params))
} }
func (canvas *canvasData) TextMetrics(text string, fontName string, fontSize SizeUnit, fontParams FontParams) TextMetrics { func (canvas *canvasData) TextMetrics(text string, fontName string, fontSize SizeUnit, fontParams FontParams) TextMetrics {
view := canvas.View() return canvas.session.canvasTextMetrics(canvas.view.htmlID(), canvas.fontWithParams(fontName, fontSize, fontParams), text)
return view.Session().canvasTextMetrics(view.htmlID(), canvas.fontWithParams(fontName, fontSize, fontParams), text)
} }
func (canvas *canvasData) SetTextBaseline(baseline int) { func (canvas *canvasData) SetTextBaseline(baseline int) {
switch baseline { switch baseline {
case AlphabeticBaseline: case AlphabeticBaseline:
canvas.script.WriteString("\nctx.textBaseline = 'alphabetic';") canvas.session.updateCanvasProperty("textBaseline", "alphabetic")
case TopBaseline: case TopBaseline:
canvas.script.WriteString("\nctx.textBaseline = 'top';") canvas.session.updateCanvasProperty("textBaseline", "top")
case MiddleBaseline: case MiddleBaseline:
canvas.script.WriteString("\nctx.textBaseline = 'middle';") canvas.session.updateCanvasProperty("textBaseline", "middle")
case BottomBaseline: case BottomBaseline:
canvas.script.WriteString("\nctx.textBaseline = 'bottom';") canvas.session.updateCanvasProperty("textBaseline", "bottom")
case HangingBaseline: case HangingBaseline:
canvas.script.WriteString("\nctx.textBaseline = 'hanging';") canvas.session.updateCanvasProperty("textBaseline", "hanging")
case IdeographicBaseline: case IdeographicBaseline:
canvas.script.WriteString("\nctx.textBaseline = 'ideographic';") canvas.session.updateCanvasProperty("textBaseline", "ideographic")
} }
} }
func (canvas *canvasData) SetTextAlign(align int) { func (canvas *canvasData) SetTextAlign(align int) {
switch align { switch align {
case LeftAlign: case LeftAlign:
canvas.script.WriteString("\nctx.textAlign = 'left';") canvas.session.updateCanvasProperty("textAlign", "left")
case RightAlign: case RightAlign:
canvas.script.WriteString("\nctx.textAlign = 'right';") canvas.session.updateCanvasProperty("textAlign", "right")
case CenterAlign: case CenterAlign:
canvas.script.WriteString("\nctx.textAlign = 'center';") canvas.session.updateCanvasProperty("textAlign", "center")
case StartAlign: case StartAlign:
canvas.script.WriteString("\nctx.textAlign = 'start';") canvas.session.updateCanvasProperty("textAlign", "start")
case EndAlign: case EndAlign:
canvas.script.WriteString("\nctx.textAlign = 'end';") canvas.session.updateCanvasProperty("textAlign", "end")
} }
} }
func (canvas *canvasData) SetShadow(offsetX, offsetY, blur float64, color Color) { func (canvas *canvasData) SetShadow(offsetX, offsetY, blur float64, color Color) {
if color.Alpha() > 0 && blur >= 0 { if color.Alpha() > 0 && blur >= 0 {
canvas.script.WriteString("\nctx.shadowColor = '") canvas.session.updateCanvasProperty("shadowColor", color.cssString())
canvas.script.WriteString(color.cssString()) canvas.session.updateCanvasProperty("shadowOffsetX", offsetX)
canvas.script.WriteString("';\nctx.shadowOffsetX = ") canvas.session.updateCanvasProperty("shadowOffsetY", offsetY)
canvas.script.WriteString(strconv.FormatFloat(offsetX, 'g', -1, 64)) canvas.session.updateCanvasProperty("shadowBlur", blur)
canvas.script.WriteString(";\nctx.shadowOffsetY = ")
canvas.script.WriteString(strconv.FormatFloat(offsetY, 'g', -1, 64))
canvas.script.WriteString(";\nctx.shadowBlur = ")
canvas.script.WriteString(strconv.FormatFloat(blur, 'g', -1, 64))
canvas.script.WriteString(";")
} }
} }
func (canvas *canvasData) ResetShadow() { func (canvas *canvasData) ResetShadow() {
canvas.script.WriteString("\nctx.shadowColor = 'rgba(0,0,0,0)';\nctx.shadowOffsetX = 0;\nctx.shadowOffsetY = 0;\nctx.shadowBlur = 0;") canvas.session.updateCanvasProperty("shadowColor", "rgba(0,0,0,0)")
} canvas.session.updateCanvasProperty("shadowOffsetX", 0)
canvas.session.updateCanvasProperty("shadowOffsetY", 0)
func (canvas *canvasData) writeRectArgs(x, y, width, height float64) { canvas.session.updateCanvasProperty("shadowBlur", 0)
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(width, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(height, 'g', -1, 64))
} }
func (canvas *canvasData) ClearRect(x, y, width, height float64) { func (canvas *canvasData) ClearRect(x, y, width, height float64) {
canvas.script.WriteString("\nctx.clearRect(") canvas.session.callCanvasFunc("clearRect", x, y, width, height)
canvas.writeRectArgs(x, y, width, height)
canvas.script.WriteString(");")
} }
func (canvas *canvasData) FillRect(x, y, width, height float64) { func (canvas *canvasData) FillRect(x, y, width, height float64) {
canvas.script.WriteString("\nctx.fillRect(") canvas.session.callCanvasFunc("fillRect", x, y, width, height)
canvas.writeRectArgs(x, y, width, height)
canvas.script.WriteString(");")
} }
func (canvas *canvasData) StrokeRect(x, y, width, height float64) { func (canvas *canvasData) StrokeRect(x, y, width, height float64) {
canvas.script.WriteString("\nctx.strokeRect(") canvas.session.callCanvasFunc("strokeRect", x, y, width, height)
canvas.writeRectArgs(x, y, width, height)
canvas.script.WriteString(");")
} }
func (canvas *canvasData) FillAndStrokeRect(x, y, width, height float64) { func (canvas *canvasData) FillAndStrokeRect(x, y, width, height float64) {
@ -808,7 +652,7 @@ func (canvas *canvasData) FillAndStrokeRect(x, y, width, height float64) {
canvas.StrokeRect(x, y, width, height) canvas.StrokeRect(x, y, width, height)
} }
func (canvas *canvasData) writeRoundedRect(x, y, width, height, r float64) { func (canvas *canvasData) createRoundedRect(x, y, width, height, r float64) {
left := strconv.FormatFloat(x, 'g', -1, 64) left := strconv.FormatFloat(x, 'g', -1, 64)
top := strconv.FormatFloat(y, 'g', -1, 64) top := strconv.FormatFloat(y, 'g', -1, 64)
right := strconv.FormatFloat(x+width, 'g', -1, 64) right := strconv.FormatFloat(x+width, 'g', -1, 64)
@ -818,104 +662,65 @@ func (canvas *canvasData) writeRoundedRect(x, y, width, height, r float64) {
rightR := strconv.FormatFloat(x+width-r, 'g', -1, 64) rightR := strconv.FormatFloat(x+width-r, 'g', -1, 64)
bottomR := strconv.FormatFloat(y+height-r, 'g', -1, 64) bottomR := strconv.FormatFloat(y+height-r, 'g', -1, 64)
radius := strconv.FormatFloat(r, 'g', -1, 64) radius := strconv.FormatFloat(r, 'g', -1, 64)
canvas.script.WriteString("\nctx.beginPath();\nctx.moveTo(")
canvas.script.WriteString(left) canvas.session.callCanvasFunc("beginPath")
canvas.script.WriteRune(',') canvas.session.callCanvasFunc("moveTo", left, topR)
canvas.script.WriteString(topR) canvas.session.callCanvasFunc("arc", leftR, topR, radius, math.Pi, math.Pi*3/2)
canvas.script.WriteString(");\nctx.arc(") canvas.session.callCanvasFunc("lineTo", rightR, top)
canvas.script.WriteString(leftR) canvas.session.callCanvasFunc("arc", rightR, topR, radius, math.Pi*3/2, math.Pi*2)
canvas.script.WriteRune(',') canvas.session.callCanvasFunc("lineTo", right, bottomR)
canvas.script.WriteString(topR) canvas.session.callCanvasFunc("arc", rightR, bottomR, radius, 0, math.Pi/2)
canvas.script.WriteRune(',') canvas.session.callCanvasFunc("lineTo", leftR, bottom)
canvas.script.WriteString(radius) canvas.session.callCanvasFunc("arc", leftR, bottomR, radius, math.Pi/2, math.Pi)
canvas.script.WriteString(",Math.PI,Math.PI*3/2);\nctx.lineTo(") canvas.session.callCanvasFunc("closePath")
canvas.script.WriteString(rightR)
canvas.script.WriteRune(',')
canvas.script.WriteString(top)
canvas.script.WriteString(");\nctx.arc(")
canvas.script.WriteString(rightR)
canvas.script.WriteRune(',')
canvas.script.WriteString(topR)
canvas.script.WriteRune(',')
canvas.script.WriteString(radius)
canvas.script.WriteString(",Math.PI*3/2,Math.PI*2);\nctx.lineTo(")
canvas.script.WriteString(right)
canvas.script.WriteRune(',')
canvas.script.WriteString(bottomR)
canvas.script.WriteString(");\nctx.arc(")
canvas.script.WriteString(rightR)
canvas.script.WriteRune(',')
canvas.script.WriteString(bottomR)
canvas.script.WriteRune(',')
canvas.script.WriteString(radius)
canvas.script.WriteString(",0,Math.PI/2);\nctx.lineTo(")
canvas.script.WriteString(leftR)
canvas.script.WriteRune(',')
canvas.script.WriteString(bottom)
canvas.script.WriteString(");\nctx.arc(")
canvas.script.WriteString(leftR)
canvas.script.WriteRune(',')
canvas.script.WriteString(bottomR)
canvas.script.WriteRune(',')
canvas.script.WriteString(radius)
canvas.script.WriteString(",Math.PI/2,Math.PI);\nctx.closePath();")
} }
func (canvas *canvasData) FillRoundedRect(x, y, width, height, r float64) { func (canvas *canvasData) FillRoundedRect(x, y, width, height, r float64) {
canvas.writeRoundedRect(x, y, width, height, r) canvas.createRoundedRect(x, y, width, height, r)
canvas.script.WriteString("\nctx.fill();") canvas.session.callCanvasFunc("fill")
} }
func (canvas *canvasData) StrokeRoundedRect(x, y, width, height, r float64) { func (canvas *canvasData) StrokeRoundedRect(x, y, width, height, r float64) {
canvas.writeRoundedRect(x, y, width, height, r) canvas.createRoundedRect(x, y, width, height, r)
canvas.script.WriteString("\nctx.stroke();") canvas.session.callCanvasFunc("stroke")
} }
func (canvas *canvasData) FillAndStrokeRoundedRect(x, y, width, height, r float64) { func (canvas *canvasData) FillAndStrokeRoundedRect(x, y, width, height, r float64) {
canvas.writeRoundedRect(x, y, width, height, r) canvas.createRoundedRect(x, y, width, height, r)
canvas.script.WriteString("\nctx.fill();\nctx.stroke();") canvas.session.callCanvasFunc("fill")
canvas.session.callCanvasFunc("stroke")
} }
func (canvas *canvasData) writeEllipse(x, y, radiusX, radiusY, rotation float64) { func (canvas *canvasData) createEllipse(x, y, radiusX, radiusY, rotation float64) {
yText := strconv.FormatFloat(y, 'g', -1, 64) canvas.session.callCanvasFunc("beginPath")
canvas.script.WriteString("\nctx.beginPath();\nctx.moveTo(") canvas.session.callCanvasFunc("moveTo", x+radiusX, y)
canvas.script.WriteString(strconv.FormatFloat(x+radiusX, 'g', -1, 64)) canvas.session.callCanvasFunc("ellipse", x, y, radiusX, radiusY, rotation, 0, math.Pi*2)
canvas.script.WriteRune(',') //canvas.session.callCanvasFunc("closePath")
canvas.script.WriteString(yText)
canvas.script.WriteString(");\nctx.ellipse(")
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(yText)
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(radiusX, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(radiusY, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(rotation, 'g', -1, 64))
canvas.script.WriteString(",0,Math.PI*2);")
} }
func (canvas *canvasData) FillEllipse(x, y, radiusX, radiusY, rotation float64) { func (canvas *canvasData) FillEllipse(x, y, radiusX, radiusY, rotation float64) {
if radiusX >= 0 && radiusY >= 0 { if radiusX >= 0 && radiusY >= 0 {
canvas.writeEllipse(x, y, radiusX, radiusY, rotation) canvas.createEllipse(x, y, radiusX, radiusY, rotation)
canvas.script.WriteString("\nctx.fill();") canvas.session.callCanvasFunc("fill")
} }
} }
func (canvas *canvasData) StrokeEllipse(x, y, radiusX, radiusY, rotation float64) { func (canvas *canvasData) StrokeEllipse(x, y, radiusX, radiusY, rotation float64) {
if radiusX >= 0 && radiusY >= 0 { if radiusX >= 0 && radiusY >= 0 {
canvas.writeEllipse(x, y, radiusX, radiusY, rotation) canvas.createEllipse(x, y, radiusX, radiusY, rotation)
canvas.script.WriteString("\nctx.stroke();") canvas.session.callCanvasFunc("stroke")
} }
} }
func (canvas *canvasData) FillAndStrokeEllipse(x, y, radiusX, radiusY, rotation float64) { func (canvas *canvasData) FillAndStrokeEllipse(x, y, radiusX, radiusY, rotation float64) {
if radiusX >= 0 && radiusY >= 0 { if radiusX >= 0 && radiusY >= 0 {
canvas.writeEllipse(x, y, radiusX, radiusY, rotation) canvas.createEllipse(x, y, radiusX, radiusY, rotation)
canvas.script.WriteString("\nctx.fill();\nctx.stroke();") canvas.session.callCanvasFunc("fill")
canvas.session.callCanvasFunc("stroke")
} }
} }
/*
func (canvas *canvasData) writePointArgs(x, y float64) { func (canvas *canvasData) writePointArgs(x, y float64) {
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',') canvas.script.WriteRune(',')
@ -947,101 +752,59 @@ func (canvas *canvasData) writeStringArgs(text string, script *strings.Builder)
} }
} }
} }
*/
func (canvas *canvasData) FillText(x, y float64, text string) { func (canvas *canvasData) FillText(x, y float64, text string) {
canvas.script.WriteString("\nctx.fillText('") canvas.session.callCanvasFunc("fillText", text, x, y)
canvas.writeStringArgs(text, &canvas.script)
canvas.script.WriteString(`',`)
canvas.writePointArgs(x, y)
canvas.script.WriteString(");")
} }
func (canvas *canvasData) StrokeText(x, y float64, text string) { func (canvas *canvasData) StrokeText(x, y float64, text string) {
canvas.script.WriteString("\nctx.strokeText('") canvas.session.callCanvasFunc("strokeText", text, x, y)
canvas.writeStringArgs(text, &canvas.script)
canvas.script.WriteString(`',`)
canvas.writePointArgs(x, y)
canvas.script.WriteString(");")
} }
func (canvas *canvasData) FillPath(path Path) { func (canvas *canvasData) FillPath(path Path) {
canvas.script.WriteString(path.scriptText()) path.create(canvas.session)
canvas.script.WriteString("\nctx.fill();") canvas.session.callCanvasFunc("fill")
} }
func (canvas *canvasData) StrokePath(path Path) { func (canvas *canvasData) StrokePath(path Path) {
canvas.script.WriteString(path.scriptText()) path.create(canvas.session)
canvas.script.WriteString("\nctx.stroke();") canvas.session.callCanvasFunc("stroke")
} }
func (canvas *canvasData) FillAndStrokePath(path Path) { func (canvas *canvasData) FillAndStrokePath(path Path) {
canvas.script.WriteString(path.scriptText()) path.create(canvas.session)
canvas.script.WriteString("\nctx.fill();\nctx.stroke();") canvas.session.callCanvasFunc("fill")
canvas.session.callCanvasFunc("stroke")
} }
func (canvas *canvasData) DrawLine(x0, y0, x1, y1 float64) { func (canvas *canvasData) DrawLine(x0, y0, x1, y1 float64) {
canvas.script.WriteString("\nctx.beginPath();\nctx.moveTo(") canvas.session.callCanvasFunc("beginPath")
canvas.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64)) canvas.session.callCanvasFunc("moveTo", x0, y0)
canvas.script.WriteRune(',') canvas.session.callCanvasFunc("lineTo", x1, y1)
canvas.script.WriteString(strconv.FormatFloat(y0, 'g', -1, 64)) canvas.session.callCanvasFunc("stroke")
canvas.script.WriteString(");\nctx.lineTo(")
canvas.script.WriteString(strconv.FormatFloat(x1, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y1, 'g', -1, 64))
canvas.script.WriteString(");\nctx.stroke();")
} }
func (canvas *canvasData) DrawImage(x, y float64, image Image) { func (canvas *canvasData) DrawImage(x, y float64, image Image) {
if image == nil || image.LoadingStatus() != ImageReady { if image == nil || image.LoadingStatus() != ImageReady {
return return
} }
canvas.script.WriteString("\nimg = images.get('")
canvas.script.WriteString(image.URL()) canvas.session.callCanvasImageFunc(image.URL(), "", "drawImage", x, y)
canvas.script.WriteString("');\nif (img) {\nctx.drawImage(img,")
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
canvas.script.WriteString(");\n}")
} }
func (canvas *canvasData) DrawImageInRect(x, y, width, height float64, image Image) { func (canvas *canvasData) DrawImageInRect(x, y, width, height float64, image Image) {
if image == nil || image.LoadingStatus() != ImageReady { if image == nil || image.LoadingStatus() != ImageReady {
return return
} }
canvas.script.WriteString("\nimg = images.get('")
canvas.script.WriteString(image.URL()) canvas.session.callCanvasImageFunc(image.URL(), "", "drawImage", x, y, width, height)
canvas.script.WriteString("');\nif (img) {\nctx.drawImage(img,")
canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(width, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(height, 'g', -1, 64))
canvas.script.WriteString(");\n}")
} }
func (canvas *canvasData) DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image) { func (canvas *canvasData) DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image) {
if image == nil || image.LoadingStatus() != ImageReady { if image == nil || image.LoadingStatus() != ImageReady {
return return
} }
canvas.script.WriteString("\nimg = images.get('")
canvas.script.WriteString(image.URL()) canvas.session.callCanvasImageFunc(image.URL(), "", "drawImage", srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight)
canvas.script.WriteString("');\nif (img) {\nctx.drawImage(img,")
canvas.script.WriteString(strconv.FormatFloat(srcX, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(srcY, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(srcWidth, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(srcHeight, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(dstX, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(dstY, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(dstWidth, 'g', -1, 64))
canvas.script.WriteRune(',')
canvas.script.WriteString(strconv.FormatFloat(dstHeight, 'g', -1, 64))
canvas.script.WriteString(");\n}")
} }

View File

@ -107,7 +107,7 @@ func (canvasView *canvasViewData) Redraw() {
if canvasView.drawer != nil { if canvasView.drawer != nil {
canvasView.drawer(canvas) canvasView.drawer(canvas)
} }
canvasView.session.runScript(canvas.finishDraw()) canvas.finishDraw()
} }
} }

View File

@ -1,7 +1,10 @@
package rui package rui
import ( import (
"encoding/base64"
"fmt" "fmt"
"path/filepath"
"runtime"
"strings" "strings"
) )
@ -129,12 +132,10 @@ func (imageView *imageViewData) set(tag string, value any) bool {
if text, ok := value.(string); ok { if text, ok := value.(string); ok {
imageView.properties[Source] = text imageView.properties[Source] = text
if imageView.created { if imageView.created {
src := text src, srcset := imageView.src(text)
if src != "" && src[0] == '@' {
src, _ = imageProperty(imageView, Source, imageView.session)
}
imageView.session.updateProperty(imageView.htmlID(), "src", src) imageView.session.updateProperty(imageView.htmlID(), "src", src)
if srcset := imageView.srcSet(src); srcset != "" {
if srcset != "" {
imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset) imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset)
} else { } else {
imageView.session.removeProperty(imageView.htmlID(), "srcset") imageView.session.removeProperty(imageView.htmlID(), "srcset")
@ -214,29 +215,51 @@ func (imageView *imageViewData) htmlTag() string {
return "img" return "img"
} }
/* func (imageView *imageViewData) src(src string) (string, string) {
func (imageView *imageViewData) closeHTMLTag() bool { if src != "" && src[0] == '@' {
return false if image, ok := imageView.Session().ImageConstant(src[1:]); ok {
src = image
} else {
src = ""
}
}
if src != "" {
srcset := imageView.srcSet(src)
if runtime.GOOS == "js" {
if image, ok := resources.images[src]; ok && image.fs != nil {
dataType := map[string]string{
".svg": "data:image/svg+xml",
".png": "data:image/png",
".jpg": "data:image/jpg",
".jpeg": "data:image/jpg",
".gif": "data:image/gif",
}
ext := strings.ToLower(filepath.Ext(src))
if prefix, ok := dataType[ext]; ok {
if data, err := image.fs.ReadFile(image.path); err == nil {
return prefix + ";base64," + base64.StdEncoding.EncodeToString(data), ""
} else {
DebugLog(err.Error())
}
}
}
}
return src, srcset
}
return "", ""
} }
*/
func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builder) { func (imageView *imageViewData) htmlProperties(self View, buffer *strings.Builder) {
imageView.viewData.htmlProperties(self, buffer) imageView.viewData.htmlProperties(self, buffer)
if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" { if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" {
if imageResource[0] == '@' { if src, srcset := imageView.src(imageResource); src != "" {
if image, ok := imageView.Session().ImageConstant(imageResource[1:]); ok {
imageResource = image
} else {
imageResource = ""
}
}
if imageResource != "" {
buffer.WriteString(` src="`) buffer.WriteString(` src="`)
buffer.WriteString(imageResource) buffer.WriteString(src)
buffer.WriteString(`"`) buffer.WriteString(`"`)
if srcset := imageView.srcSet(imageResource); srcset != "" { if srcset != "" {
buffer.WriteString(` srcset="`) buffer.WriteString(` srcset="`)
buffer.WriteString(srcset) buffer.WriteString(srcset)
buffer.WriteString(`"`) buffer.WriteString(`"`)

110
path.go
View File

@ -1,10 +1,5 @@
package rui package rui
import (
"strconv"
"strings"
)
// Path is a path interface // Path is a path interface
type Path interface { type Path interface {
// Reset erases the Path // Reset erases the Path
@ -63,134 +58,79 @@ type Path interface {
// If the shape has already been closed or has only one point, this function does nothing. // If the shape has already been closed or has only one point, this function does nothing.
Close() Close()
scriptText() string create(session Session)
}
type pathElement struct {
funcName string
args []any
} }
type pathData struct { type pathData struct {
script strings.Builder elements []pathElement
} }
// NewPath creates a new empty Path // NewPath creates a new empty Path
func NewPath() Path { func NewPath() Path {
path := new(pathData) path := new(pathData)
path.script.Grow(4096) path.Reset()
path.script.WriteString("\nctx.beginPath();")
return path return path
} }
func (path *pathData) Reset() { func (path *pathData) Reset() {
path.script.Reset() path.elements = []pathElement{
path.script.WriteString("\nctx.beginPath();") pathElement{funcName: "beginPath", args: []any{}},
}
} }
func (path *pathData) MoveTo(x, y float64) { func (path *pathData) MoveTo(x, y float64) {
path.script.WriteString("\nctx.moveTo(") path.elements = append(path.elements, pathElement{funcName: "moveTo", args: []any{x, y}})
path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
path.script.WriteString(");")
} }
func (path *pathData) LineTo(x, y float64) { func (path *pathData) LineTo(x, y float64) {
path.script.WriteString("\nctx.lineTo(") path.elements = append(path.elements, pathElement{funcName: "lineTo", args: []any{x, y}})
path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
path.script.WriteString(");")
} }
func (path *pathData) ArcTo(x0, y0, x1, y1, radius float64) { func (path *pathData) ArcTo(x0, y0, x1, y1, radius float64) {
if radius > 0 { if radius > 0 {
path.script.WriteString("\nctx.arcTo(") path.elements = append(path.elements, pathElement{funcName: "arcTo", args: []any{x0, y0, x1, y1, radius}})
path.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y0, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(x1, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y1, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(radius, 'g', -1, 64))
path.script.WriteString(");")
} }
} }
func (path *pathData) Arc(x, y, radius, startAngle, endAngle float64, clockwise bool) { func (path *pathData) Arc(x, y, radius, startAngle, endAngle float64, clockwise bool) {
if radius > 0 { if radius > 0 {
path.script.WriteString("\nctx.arc(")
path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(radius, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(startAngle, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(endAngle, 'g', -1, 64))
if !clockwise { if !clockwise {
path.script.WriteString(",true);") path.elements = append(path.elements, pathElement{funcName: "arc", args: []any{x, y, radius, startAngle, endAngle, true}})
} else { } else {
path.script.WriteString(");") path.elements = append(path.elements, pathElement{funcName: "arc", args: []any{x, y, radius, startAngle, endAngle}})
} }
} }
} }
func (path *pathData) BezierCurveTo(cp0x, cp0y, cp1x, cp1y, x, y float64) { func (path *pathData) BezierCurveTo(cp0x, cp0y, cp1x, cp1y, x, y float64) {
path.script.WriteString("\nctx.bezierCurveTo(") path.elements = append(path.elements, pathElement{funcName: "bezierCurveTo", args: []any{cp0x, cp0y, cp1x, cp1y, x, y}})
path.script.WriteString(strconv.FormatFloat(cp0x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(cp0y, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(cp1x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(cp1y, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
path.script.WriteString(");")
} }
func (path *pathData) QuadraticCurveTo(cpx, cpy, x, y float64) { func (path *pathData) QuadraticCurveTo(cpx, cpy, x, y float64) {
path.script.WriteString("\nctx.quadraticCurveTo(") path.elements = append(path.elements, pathElement{funcName: "quadraticCurveTo", args: []any{cpx, cpy, x, y}})
path.script.WriteString(strconv.FormatFloat(cpx, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(cpy, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
path.script.WriteString(");")
} }
func (path *pathData) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, clockwise bool) { func (path *pathData) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, clockwise bool) {
if radiusX > 0 && radiusY > 0 { if radiusX > 0 && radiusY > 0 {
path.script.WriteString("\nctx.ellipse(")
path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(radiusX, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(radiusY, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(rotation, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(startAngle, 'g', -1, 64))
path.script.WriteRune(',')
path.script.WriteString(strconv.FormatFloat(endAngle, 'g', -1, 64))
if !clockwise { if !clockwise {
path.script.WriteString(",true);") path.elements = append(path.elements, pathElement{funcName: "ellipse", args: []any{x, y, radiusX, radiusY, rotation, startAngle, endAngle, true}})
} else { } else {
path.script.WriteString(");") path.elements = append(path.elements, pathElement{funcName: "ellipse", args: []any{x, y, radiusX, radiusY, rotation, startAngle, endAngle}})
} }
} }
} }
func (path *pathData) Close() { func (path *pathData) Close() {
path.script.WriteString("\nctx.close();") path.elements = append(path.elements, pathElement{funcName: "close", args: []any{}})
} }
func (path *pathData) scriptText() string { func (path *pathData) create(session Session) {
return path.script.String() for _, element := range path.elements {
session.callCanvasFunc(element.funcName, element.args...)
}
} }

View File

@ -123,7 +123,7 @@ func scanEmbedImagesDir(fs *embed.FS, dir, prefix string) {
} else { } else {
ext := strings.ToLower(filepath.Ext(name)) ext := strings.ToLower(filepath.Ext(name))
switch ext { switch ext {
case ".png", ".jpg", ".jpeg", ".svg": case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp":
registerImage(fs, path, prefix+name) registerImage(fs, path, prefix+name)
} }
} }

View File

@ -18,6 +18,13 @@ type webBrige interface {
removeProperty(htmlID, property string) removeProperty(htmlID, property string)
readMessage() (string, bool) readMessage() (string, bool)
writeMessage(text string) bool writeMessage(text string) bool
cavnasStart(htmlID string)
callCanvasFunc(funcName string, args ...any)
callCanvasVarFunc(v any, funcName string, args ...any)
callCanvasImageFunc(url string, property string, funcName string, args ...any)
createCanvasVar(funcName string, args ...any) any
updateCanvasProperty(property string, value any)
cavnasFinish()
canvasTextMetrics(htmlID, font, text string) TextMetrics canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string htmlPropertyValue(htmlID, name string) string
answerReceived(answer DataObject) answerReceived(answer DataObject)
@ -115,6 +122,13 @@ type Session interface {
runScript(script string) runScript(script string)
startUpdateScript(htmlID string) bool startUpdateScript(htmlID string) bool
finishUpdateScript(htmlID string) finishUpdateScript(htmlID string)
cavnasStart(htmlID string)
callCanvasFunc(funcName string, args ...any)
createCanvasVar(funcName string, args ...any) any
callCanvasVarFunc(v any, funcName string, args ...any)
callCanvasImageFunc(url string, property string, funcName string, args ...any)
updateCanvasProperty(property string, value any)
cavnasFinish()
canvasTextMetrics(htmlID, font, text string) TextMetrics canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string htmlPropertyValue(htmlID, name string) string
handleAnswer(data DataObject) handleAnswer(data DataObject)
@ -395,6 +409,49 @@ func (session *sessionData) finishUpdateScript(htmlID string) {
} }
} }
func (session *sessionData) cavnasStart(htmlID string) {
if session.brige != nil {
session.brige.cavnasStart(htmlID)
}
}
func (session *sessionData) callCanvasFunc(funcName string, args ...any) {
if session.brige != nil {
session.brige.callCanvasFunc(funcName, args...)
}
}
func (session *sessionData) updateCanvasProperty(property string, value any) {
if session.brige != nil {
session.brige.updateCanvasProperty(property, value)
}
}
func (session *sessionData) createCanvasVar(funcName string, args ...any) any {
if session.brige != nil {
return session.brige.createCanvasVar(funcName, args...)
}
return nil
}
func (session *sessionData) callCanvasVarFunc(v any, funcName string, args ...any) {
if session.brige != nil && v != nil {
session.brige.callCanvasVarFunc(v, funcName, args...)
}
}
func (session *sessionData) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
if session.brige != nil {
session.brige.callCanvasImageFunc(url, property, funcName, args...)
}
}
func (session *sessionData) cavnasFinish() {
if session.brige != nil {
session.brige.cavnasFinish()
}
}
func (session *sessionData) runScript(script string) { func (session *sessionData) runScript(script string) {
if session.brige != nil { if session.brige != nil {
session.brige.writeMessage(script) session.brige.writeMessage(script)

View File

@ -13,13 +13,19 @@ import (
) )
type wsBrige struct { type wsBrige struct {
conn *websocket.Conn conn *websocket.Conn
answer map[int]chan DataObject answer map[int]chan DataObject
answerID int answerID int
answerMutex sync.Mutex answerMutex sync.Mutex
closed bool closed bool
buffer strings.Builder buffer strings.Builder
updateScripts map[string]*strings.Builder canvasBuffer strings.Builder
canvasVarNumber int
updateScripts map[string]*strings.Builder
}
type canvasVar struct {
name string
} }
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
@ -119,6 +125,21 @@ func (brige *wsBrige) argToString(arg any) (string, bool) {
case float64: case float64:
return fmt.Sprintf("%g", arg), true return fmt.Sprintf("%g", arg), true
case []float64:
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
lead := '['
for _, val := range arg {
buffer.WriteRune(lead)
lead = ','
buffer.WriteString(fmt.Sprintf("%g", val))
}
buffer.WriteRune(']')
return buffer.String(), true
case canvasVar:
return arg.name, true
default: default:
if n, ok := isInt(arg); ok { if n, ok := isInt(arg); ok {
return fmt.Sprintf("%d", n), true return fmt.Sprintf("%d", n), true
@ -203,6 +224,110 @@ func (brige *wsBrige) removeProperty(htmlID, property string) {
} }
} }
func (brige *wsBrige) cavnasStart(htmlID string) {
brige.canvasBuffer.Reset()
brige.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
brige.canvasBuffer.WriteString(htmlID)
brige.canvasBuffer.WriteString(`');`)
}
func (brige *wsBrige) callCanvasFunc(funcName string, args ...any) {
brige.canvasBuffer.WriteString("\nctx.")
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
brige.canvasBuffer.WriteString(", ")
}
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");")
}
func (brige *wsBrige) updateCanvasProperty(property string, value any) {
brige.canvasBuffer.WriteString("\nctx.")
brige.canvasBuffer.WriteString(property)
brige.canvasBuffer.WriteString(" = ")
argText, _ := brige.argToString(value)
brige.canvasBuffer.WriteString(argText)
brige.canvasBuffer.WriteString(";")
}
func (brige *wsBrige) createCanvasVar(funcName string, args ...any) any {
brige.canvasVarNumber++
result := canvasVar{name: fmt.Sprintf("v%d", brige.canvasVarNumber)}
brige.canvasBuffer.WriteString("\nvar ")
brige.canvasBuffer.WriteString(result.name)
brige.canvasBuffer.WriteString(" = ctx.")
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
brige.canvasBuffer.WriteString(", ")
}
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");")
return result
}
func (brige *wsBrige) callCanvasVarFunc(v any, funcName string, args ...any) {
varName, ok := v.(canvasVar)
if !ok {
return
}
brige.canvasBuffer.WriteString("\n")
brige.canvasBuffer.WriteString(varName.name)
brige.canvasBuffer.WriteRune('.')
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteRune('(')
for i, arg := range args {
if i > 0 {
brige.canvasBuffer.WriteString(", ")
}
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");")
}
func (brige *wsBrige) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
brige.canvasBuffer.WriteString("\nimg = images.get('")
brige.canvasBuffer.WriteString(url)
brige.canvasBuffer.WriteString("');\nif (img) {\n")
if property != "" {
brige.canvasBuffer.WriteString("ctx.")
brige.canvasBuffer.WriteString(property)
brige.canvasBuffer.WriteString(" = ")
}
brige.canvasBuffer.WriteString("ctx.")
brige.canvasBuffer.WriteString(funcName)
brige.canvasBuffer.WriteString("(img")
for _, arg := range args {
brige.canvasBuffer.WriteString(", ")
argText, _ := brige.argToString(arg)
brige.canvasBuffer.WriteString(argText)
}
brige.canvasBuffer.WriteString(");\n}")
}
func (brige *wsBrige) cavnasFinish() {
brige.canvasBuffer.WriteString("\n")
script := brige.canvasBuffer.String()
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if brige.conn == nil {
ErrorLog("No connection")
} else if err := brige.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
}
}
func (brige *wsBrige) readMessage() (string, bool) { func (brige *wsBrige) readMessage() (string, bool) {
_, p, err := brige.conn.ReadMessage() _, p, err := brige.conn.ReadMessage()
if err != nil { if err != nil {