From 8200f98d0d22ae692498e04634f4e286b55c63c1 Mon Sep 17 00:00:00 2001 From: anoshenko Date: Wed, 2 Nov 2022 20:08:02 +0300 Subject: [PATCH] Canvas refactoring --- appWasm.go | 48 ++++- app_scripts.js | 15 +- canvas.go | 543 ++++++++++++++----------------------------------- canvasView.go | 2 +- imageView.go | 63 ++++-- path.go | 110 +++------- resources.go | 2 +- session.go | 57 ++++++ webBrige.go | 139 ++++++++++++- 9 files changed, 471 insertions(+), 508 deletions(-) diff --git a/appWasm.go b/appWasm.go index d38ba81..77ac069 100644 --- a/appWasm.go +++ b/appWasm.go @@ -4,6 +4,8 @@ package rui import ( _ "embed" + "encoding/base64" + "path/filepath" "strings" "syscall/js" ) @@ -112,10 +114,46 @@ func (app *wasmApp) createSession() Session { return session } -func (app *wasmApp) init() { +func (app *wasmApp) init(params AppParams) { + + app.params = params document := js.Global().Get("document") 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.Set("type", "text/javascript") @@ -156,6 +194,11 @@ func (app *wasmApp) init() { div.Set("download", "") div.Set("style", "display: none;") body.Call("appendChild", div) + + if params.TitleColor != 0 { + app.brige.runFunc("setTitleColor", params.TitleColor.cssString()) + } + } // 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.params = params app.createContentFunc = createContentFunc app.brige = createWasmBrige() - app.init() + app.init(params) <-app.close } diff --git a/app_scripts.js b/app_scripts.js index 10f2a20..defb07e 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1795,4 +1795,17 @@ function getPropertyValue(answerID, elementId, name) { function appendStyles(styles) { document.querySelector('style').textContent += styles -} \ No newline at end of file +} + +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; +} diff --git a/canvas.go b/canvas.go index 2489829..a1d4656 100644 --- a/canvas.go +++ b/canvas.go @@ -1,7 +1,7 @@ package rui import ( - "fmt" + "math" "strconv" "strings" ) @@ -290,42 +290,24 @@ type Canvas interface { // 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) - finishDraw() string + finishDraw() } type canvasData struct { - view CanvasView - script strings.Builder + view CanvasView + session Session } func newCanvas(view CanvasView) Canvas { canvas := new(canvasData) canvas.view = view - canvas.script.Grow(4096) - canvas.script.WriteString(`const canvas = document.getElementById('`) - 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);") - */ + canvas.session = view.Session() + canvas.session.cavnasStart(view.htmlID()) return canvas } -func (canvas *canvasData) finishDraw() string { - canvas.script.WriteString("\n") - return canvas.script.String() +func (canvas *canvasData) finishDraw() { + canvas.session.cavnasFinish() } func (canvas *canvasData) View() CanvasView { @@ -347,162 +329,102 @@ func (canvas *canvasData) Height() float64 { } func (canvas *canvasData) Save() { - canvas.script.WriteString("\nctx.save();") + canvas.session.callCanvasFunc("save") } func (canvas *canvasData) Restore() { - canvas.script.WriteString("\nctx.restore();") + canvas.session.callCanvasFunc("restore") } func (canvas *canvasData) ClipRect(x, y, width, height float64) { - canvas.script.WriteString("\nctx.beginPath();\nctx.rect(") - 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(");\nctx.clip();") + canvas.session.callCanvasFunc("beginPath") + canvas.session.callCanvasFunc("rect", x, y, width, height) + canvas.session.callCanvasFunc("clip") } func (canvas *canvasData) ClipPath(path Path) { - canvas.script.WriteString(path.scriptText()) - canvas.script.WriteString("\nctx.clip();") + path.create(canvas.session) + canvas.session.callCanvasFunc("clip") } func (canvas *canvasData) SetScale(x, y float64) { - canvas.script.WriteString("\nctx.scale(") - canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) - canvas.script.WriteRune(',') - canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64)) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("scale", x, y) } func (canvas *canvasData) SetTranslation(x, y float64) { - canvas.script.WriteString("\nctx.translate(") - canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) - canvas.script.WriteRune(',') - canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64)) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("translate", x, y) } func (canvas *canvasData) SetRotation(angle float64) { - canvas.script.WriteString("\nctx.rotate(") - canvas.script.WriteString(strconv.FormatFloat(angle, 'g', -1, 64)) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("rotate", angle) } func (canvas *canvasData) SetTransformation(xScale, yScale, xSkew, ySkew, dx, dy float64) { - canvas.script.WriteString("\nctx.transform(") - 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(");") + canvas.session.callCanvasFunc("transform", xScale, ySkew, xSkew, yScale, dx, dy) } 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) { - canvas.script.WriteString("\nctx.fillStyle = \"") - canvas.script.WriteString(color.cssString()) - canvas.script.WriteString(`";`) + canvas.session.updateCanvasProperty("fillStyle", color.cssString()) } func (canvas *canvasData) SetSolidColorStrokeStyle(color Color) { - canvas.script.WriteString("\nctx.strokeStyle = \"") - canvas.script.WriteString(color.cssString()) - canvas.script.WriteString(`";`) + canvas.session.updateCanvasProperty("strokeStyle", color.cssString()) } -func (canvas *canvasData) setLinearGradient(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) { - canvas.script.WriteString("\ngradient = ctx.createLinearGradient(") - canvas.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64)) - canvas.script.WriteRune(',') - 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("');") +func (canvas *canvasData) createLinearGradient(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) any { + + gradient := canvas.session.createCanvasVar("createLinearGradient", x0, y0, x1, y1) + canvas.session.callCanvasVarFunc(gradient, "addColorStop", 0, color0.cssString()) for _, point := range stopPoints { if point.Offset >= 0 && point.Offset <= 1 { - canvas.script.WriteString("\ngradient.addColorStop(") - canvas.script.WriteString(strconv.FormatFloat(point.Offset, 'g', -1, 64)) - canvas.script.WriteString(", '") - canvas.script.WriteString(point.Color.cssString()) - canvas.script.WriteString("');") + canvas.session.callCanvasVarFunc(gradient, "addColorStop", point.Offset, point.Color.cssString()) } } - canvas.script.WriteString("\ngradient.addColorStop(1, '") - canvas.script.WriteString(color1.cssString()) - canvas.script.WriteString("');") + canvas.session.callCanvasVarFunc(gradient, "addColorStop", 1, color1.cssString()) + return gradient } 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) - canvas.script.WriteString("\nctx.fillStyle = gradient;") + gradient := canvas.createLinearGradient(x0, y0, color0, x1, y1, color1, stopPoints) + canvas.session.updateCanvasProperty("fillStyle", gradient) } 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) - canvas.script.WriteString("\nctx.strokeStyle = gradient;") + gradient := canvas.createLinearGradient(x0, y0, color0, x1, y1, color1, stopPoints) + canvas.session.updateCanvasProperty("strokeStyle", gradient) } -func (canvas *canvasData) setRadialGradient(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) { - canvas.script.WriteString("\ngradient = ctx.createRadialGradient(") - canvas.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64)) - 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("');") +func (canvas *canvasData) createRadialGradient(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) any { + gradient := canvas.session.createCanvasVar("createRadialGradient", x0, y0, r0, x1, y1, r1) + canvas.session.callCanvasVarFunc(gradient, "addColorStop", 0, color0.cssString()) for _, point := range stopPoints { if point.Offset >= 0 && point.Offset <= 1 { - canvas.script.WriteString("\ngradient.addColorStop(") - canvas.script.WriteString(strconv.FormatFloat(point.Offset, 'g', -1, 64)) - canvas.script.WriteString(", '") - canvas.script.WriteString(point.Color.cssString()) - canvas.script.WriteString("');") + canvas.session.callCanvasVarFunc(gradient, "addColorStop", point.Offset, point.Color.cssString()) } } - canvas.script.WriteString("\ngradient.addColorStop(1, '") - canvas.script.WriteString(color1.cssString()) - canvas.script.WriteString("');") + canvas.session.callCanvasVarFunc(gradient, "addColorStop", 1, color1.cssString()) + return gradient } 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) - canvas.script.WriteString("\nctx.fillStyle = gradient;") + gradient := canvas.createRadialGradient(x0, y0, r0, color0, x1, y1, r1, color1, stopPoints) + canvas.session.updateCanvasProperty("fillStyle", gradient) } 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) - canvas.script.WriteString("\nctx.strokeStyle = gradient;") + gradient := canvas.createRadialGradient(x0, y0, r0, color0, x1, y1, r1, color1, stopPoints) + canvas.session.updateCanvasProperty("strokeStyle", gradient) } func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) { @@ -528,89 +450,70 @@ func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) { return } - canvas.script.WriteString("\nimg = images.get('") - canvas.script.WriteString(image.URL()) - canvas.script.WriteString("');\nif (img) {\nctx.fillStyle = ctx.createPattern(img,'") - canvas.script.WriteString(repeatText) - canvas.script.WriteString("');\n}") + canvas.session.callCanvasImageFunc(image.URL(), "fillStyle", "createPattern", repeatText) } func (canvas *canvasData) SetLineWidth(width float64) { if width > 0 { - canvas.script.WriteString("\nctx.lineWidth = '") - canvas.script.WriteString(strconv.FormatFloat(width, 'g', -1, 64)) - canvas.script.WriteString("';") + canvas.session.updateCanvasProperty("lineWidth", width) } } func (canvas *canvasData) SetLineJoin(join int) { switch join { case MiterJoin: - canvas.script.WriteString("\nctx.lineJoin = 'miter';") + canvas.session.updateCanvasProperty("lineJoin", "miter") case RoundJoin: - canvas.script.WriteString("\nctx.lineJoin = 'round';") + canvas.session.updateCanvasProperty("lineJoin", "round") case BevelJoin: - canvas.script.WriteString("\nctx.lineJoin = 'bevel';") + canvas.session.updateCanvasProperty("lineJoin", "bevel") } } func (canvas *canvasData) SetLineCap(cap int) { switch cap { case ButtCap: - canvas.script.WriteString("\nctx.lineCap = 'butt';") + canvas.session.updateCanvasProperty("lineCap", "butt") case RoundCap: - canvas.script.WriteString("\nctx.lineCap = 'round';") + canvas.session.updateCanvasProperty("lineCap", "round") case SquareCap: - canvas.script.WriteString("\nctx.lineCap = 'square';") + canvas.session.updateCanvasProperty("lineCap", "square") } } func (canvas *canvasData) SetLineDash(dash []float64, offset float64) { - canvas.script.WriteString("\nctx.setLineDash([") - for i, d := range dash { - if i > 0 { - canvas.script.WriteString(",") - } - canvas.script.WriteString(strconv.FormatFloat(d, 'g', -1, 64)) - } - - canvas.script.WriteString("]);") + canvas.session.callCanvasFunc("setLineDash", dash) if offset >= 0 { - canvas.script.WriteString("\nctx.lineDashOffset = '") - canvas.script.WriteString(strconv.FormatFloat(offset, 'g', -1, 64)) - canvas.script.WriteString("';") + canvas.session.updateCanvasProperty("lineDashOffset", offset) } } -func (canvas *canvasData) writeFont(name string, script *strings.Builder) { - names := strings.Split(name, ",") - lead := " " - for _, font := range names { - font = strings.Trim(font, " \n\"'") - script.WriteString(lead) - lead = "," - if strings.Contains(font, " ") { - script.WriteRune('"') - script.WriteString(font) - script.WriteRune('"') - } else { - script.WriteString(font) +/* + func (canvas *canvasData) convertFont(name string) string { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + for i, font := range strings.Split(name, ",") { + font = strings.Trim(font, " \n\"'") + if i > 0 { + buffer.WriteRune(',') + } + if strings.Contains(font, " ") { + buffer.WriteRune('"') + 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 { buffer := allocStringBuilder() defer freeStringBuilder(buffer) @@ -672,135 +575,76 @@ func (canvas *canvasData) fontWithParams(name string, size SizeUnit, params Font return buffer.String() } -func (canvas *canvasData) setFontWithParams(name string, size SizeUnit, params FontParams, script *strings.Builder) { - script.WriteString("\nctx.font = '") - 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) SetFont(name string, size SizeUnit) { + canvas.session.updateCanvasProperty("font", canvas.fontWithParams(name, size, 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 { - view := canvas.View() - return view.Session().canvasTextMetrics(view.htmlID(), canvas.fontWithParams(fontName, fontSize, fontParams), text) + return canvas.session.canvasTextMetrics(canvas.view.htmlID(), canvas.fontWithParams(fontName, fontSize, fontParams), text) } func (canvas *canvasData) SetTextBaseline(baseline int) { switch baseline { case AlphabeticBaseline: - canvas.script.WriteString("\nctx.textBaseline = 'alphabetic';") + canvas.session.updateCanvasProperty("textBaseline", "alphabetic") case TopBaseline: - canvas.script.WriteString("\nctx.textBaseline = 'top';") + canvas.session.updateCanvasProperty("textBaseline", "top") case MiddleBaseline: - canvas.script.WriteString("\nctx.textBaseline = 'middle';") + canvas.session.updateCanvasProperty("textBaseline", "middle") case BottomBaseline: - canvas.script.WriteString("\nctx.textBaseline = 'bottom';") + canvas.session.updateCanvasProperty("textBaseline", "bottom") case HangingBaseline: - canvas.script.WriteString("\nctx.textBaseline = 'hanging';") + canvas.session.updateCanvasProperty("textBaseline", "hanging") case IdeographicBaseline: - canvas.script.WriteString("\nctx.textBaseline = 'ideographic';") + canvas.session.updateCanvasProperty("textBaseline", "ideographic") } } func (canvas *canvasData) SetTextAlign(align int) { switch align { case LeftAlign: - canvas.script.WriteString("\nctx.textAlign = 'left';") + canvas.session.updateCanvasProperty("textAlign", "left") case RightAlign: - canvas.script.WriteString("\nctx.textAlign = 'right';") + canvas.session.updateCanvasProperty("textAlign", "right") case CenterAlign: - canvas.script.WriteString("\nctx.textAlign = 'center';") + canvas.session.updateCanvasProperty("textAlign", "center") case StartAlign: - canvas.script.WriteString("\nctx.textAlign = 'start';") + canvas.session.updateCanvasProperty("textAlign", "start") case EndAlign: - canvas.script.WriteString("\nctx.textAlign = 'end';") + canvas.session.updateCanvasProperty("textAlign", "end") } } func (canvas *canvasData) SetShadow(offsetX, offsetY, blur float64, color Color) { if color.Alpha() > 0 && blur >= 0 { - canvas.script.WriteString("\nctx.shadowColor = '") - canvas.script.WriteString(color.cssString()) - canvas.script.WriteString("';\nctx.shadowOffsetX = ") - canvas.script.WriteString(strconv.FormatFloat(offsetX, 'g', -1, 64)) - 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(";") + canvas.session.updateCanvasProperty("shadowColor", color.cssString()) + canvas.session.updateCanvasProperty("shadowOffsetX", offsetX) + canvas.session.updateCanvasProperty("shadowOffsetY", offsetY) + canvas.session.updateCanvasProperty("shadowBlur", blur) } } func (canvas *canvasData) ResetShadow() { - canvas.script.WriteString("\nctx.shadowColor = 'rgba(0,0,0,0)';\nctx.shadowOffsetX = 0;\nctx.shadowOffsetY = 0;\nctx.shadowBlur = 0;") -} - -func (canvas *canvasData) writeRectArgs(x, y, width, height float64) { - 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.session.updateCanvasProperty("shadowColor", "rgba(0,0,0,0)") + canvas.session.updateCanvasProperty("shadowOffsetX", 0) + canvas.session.updateCanvasProperty("shadowOffsetY", 0) + canvas.session.updateCanvasProperty("shadowBlur", 0) } func (canvas *canvasData) ClearRect(x, y, width, height float64) { - canvas.script.WriteString("\nctx.clearRect(") - canvas.writeRectArgs(x, y, width, height) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("clearRect", x, y, width, height) } func (canvas *canvasData) FillRect(x, y, width, height float64) { - canvas.script.WriteString("\nctx.fillRect(") - canvas.writeRectArgs(x, y, width, height) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("fillRect", x, y, width, height) } func (canvas *canvasData) StrokeRect(x, y, width, height float64) { - canvas.script.WriteString("\nctx.strokeRect(") - canvas.writeRectArgs(x, y, width, height) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("strokeRect", x, y, width, height) } 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) } -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) top := strconv.FormatFloat(y, '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) bottomR := strconv.FormatFloat(y+height-r, 'g', -1, 64) radius := strconv.FormatFloat(r, 'g', -1, 64) - canvas.script.WriteString("\nctx.beginPath();\nctx.moveTo(") - canvas.script.WriteString(left) - canvas.script.WriteRune(',') - canvas.script.WriteString(topR) - canvas.script.WriteString(");\nctx.arc(") - canvas.script.WriteString(leftR) - canvas.script.WriteRune(',') - canvas.script.WriteString(topR) - canvas.script.WriteRune(',') - canvas.script.WriteString(radius) - canvas.script.WriteString(",Math.PI,Math.PI*3/2);\nctx.lineTo(") - 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();") + + canvas.session.callCanvasFunc("beginPath") + canvas.session.callCanvasFunc("moveTo", left, topR) + canvas.session.callCanvasFunc("arc", leftR, topR, radius, math.Pi, math.Pi*3/2) + canvas.session.callCanvasFunc("lineTo", rightR, top) + canvas.session.callCanvasFunc("arc", rightR, topR, radius, math.Pi*3/2, math.Pi*2) + canvas.session.callCanvasFunc("lineTo", right, bottomR) + canvas.session.callCanvasFunc("arc", rightR, bottomR, radius, 0, math.Pi/2) + canvas.session.callCanvasFunc("lineTo", leftR, bottom) + canvas.session.callCanvasFunc("arc", leftR, bottomR, radius, math.Pi/2, math.Pi) + canvas.session.callCanvasFunc("closePath") } func (canvas *canvasData) FillRoundedRect(x, y, width, height, r float64) { - canvas.writeRoundedRect(x, y, width, height, r) - canvas.script.WriteString("\nctx.fill();") + canvas.createRoundedRect(x, y, width, height, r) + canvas.session.callCanvasFunc("fill") } func (canvas *canvasData) StrokeRoundedRect(x, y, width, height, r float64) { - canvas.writeRoundedRect(x, y, width, height, r) - canvas.script.WriteString("\nctx.stroke();") + canvas.createRoundedRect(x, y, width, height, r) + canvas.session.callCanvasFunc("stroke") } func (canvas *canvasData) FillAndStrokeRoundedRect(x, y, width, height, r float64) { - canvas.writeRoundedRect(x, y, width, height, r) - canvas.script.WriteString("\nctx.fill();\nctx.stroke();") + canvas.createRoundedRect(x, y, width, height, r) + canvas.session.callCanvasFunc("fill") + canvas.session.callCanvasFunc("stroke") } -func (canvas *canvasData) writeEllipse(x, y, radiusX, radiusY, rotation float64) { - yText := strconv.FormatFloat(y, 'g', -1, 64) - canvas.script.WriteString("\nctx.beginPath();\nctx.moveTo(") - canvas.script.WriteString(strconv.FormatFloat(x+radiusX, 'g', -1, 64)) - canvas.script.WriteRune(',') - 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) createEllipse(x, y, radiusX, radiusY, rotation float64) { + canvas.session.callCanvasFunc("beginPath") + canvas.session.callCanvasFunc("moveTo", x+radiusX, y) + canvas.session.callCanvasFunc("ellipse", x, y, radiusX, radiusY, rotation, 0, math.Pi*2) + //canvas.session.callCanvasFunc("closePath") } func (canvas *canvasData) FillEllipse(x, y, radiusX, radiusY, rotation float64) { if radiusX >= 0 && radiusY >= 0 { - canvas.writeEllipse(x, y, radiusX, radiusY, rotation) - canvas.script.WriteString("\nctx.fill();") + canvas.createEllipse(x, y, radiusX, radiusY, rotation) + canvas.session.callCanvasFunc("fill") } } func (canvas *canvasData) StrokeEllipse(x, y, radiusX, radiusY, rotation float64) { if radiusX >= 0 && radiusY >= 0 { - canvas.writeEllipse(x, y, radiusX, radiusY, rotation) - canvas.script.WriteString("\nctx.stroke();") + canvas.createEllipse(x, y, radiusX, radiusY, rotation) + canvas.session.callCanvasFunc("stroke") } } func (canvas *canvasData) FillAndStrokeEllipse(x, y, radiusX, radiusY, rotation float64) { if radiusX >= 0 && radiusY >= 0 { - canvas.writeEllipse(x, y, radiusX, radiusY, rotation) - canvas.script.WriteString("\nctx.fill();\nctx.stroke();") + canvas.createEllipse(x, y, radiusX, radiusY, rotation) + canvas.session.callCanvasFunc("fill") + canvas.session.callCanvasFunc("stroke") } } +/* func (canvas *canvasData) writePointArgs(x, y float64) { canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) 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) { - canvas.script.WriteString("\nctx.fillText('") - canvas.writeStringArgs(text, &canvas.script) - canvas.script.WriteString(`',`) - canvas.writePointArgs(x, y) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("fillText", text, x, y) } func (canvas *canvasData) StrokeText(x, y float64, text string) { - canvas.script.WriteString("\nctx.strokeText('") - canvas.writeStringArgs(text, &canvas.script) - canvas.script.WriteString(`',`) - canvas.writePointArgs(x, y) - canvas.script.WriteString(");") + canvas.session.callCanvasFunc("strokeText", text, x, y) } func (canvas *canvasData) FillPath(path Path) { - canvas.script.WriteString(path.scriptText()) - canvas.script.WriteString("\nctx.fill();") + path.create(canvas.session) + canvas.session.callCanvasFunc("fill") } func (canvas *canvasData) StrokePath(path Path) { - canvas.script.WriteString(path.scriptText()) - canvas.script.WriteString("\nctx.stroke();") + path.create(canvas.session) + canvas.session.callCanvasFunc("stroke") } func (canvas *canvasData) FillAndStrokePath(path Path) { - canvas.script.WriteString(path.scriptText()) - canvas.script.WriteString("\nctx.fill();\nctx.stroke();") + path.create(canvas.session) + canvas.session.callCanvasFunc("fill") + canvas.session.callCanvasFunc("stroke") } func (canvas *canvasData) DrawLine(x0, y0, x1, y1 float64) { - canvas.script.WriteString("\nctx.beginPath();\nctx.moveTo(") - canvas.script.WriteString(strconv.FormatFloat(x0, 'g', -1, 64)) - canvas.script.WriteRune(',') - canvas.script.WriteString(strconv.FormatFloat(y0, 'g', -1, 64)) - 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();") + canvas.session.callCanvasFunc("beginPath") + canvas.session.callCanvasFunc("moveTo", x0, y0) + canvas.session.callCanvasFunc("lineTo", x1, y1) + canvas.session.callCanvasFunc("stroke") } func (canvas *canvasData) DrawImage(x, y float64, image Image) { if image == nil || image.LoadingStatus() != ImageReady { return } - canvas.script.WriteString("\nimg = images.get('") - canvas.script.WriteString(image.URL()) - 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}") + + canvas.session.callCanvasImageFunc(image.URL(), "", "drawImage", x, y) } func (canvas *canvasData) DrawImageInRect(x, y, width, height float64, image Image) { if image == nil || image.LoadingStatus() != ImageReady { return } - canvas.script.WriteString("\nimg = images.get('") - canvas.script.WriteString(image.URL()) - 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}") + + canvas.session.callCanvasImageFunc(image.URL(), "", "drawImage", x, y, width, height) } func (canvas *canvasData) DrawImageFragment(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight float64, image Image) { if image == nil || image.LoadingStatus() != ImageReady { return } - canvas.script.WriteString("\nimg = images.get('") - canvas.script.WriteString(image.URL()) - 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}") + + canvas.session.callCanvasImageFunc(image.URL(), "", "drawImage", srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight) } diff --git a/canvasView.go b/canvasView.go index a0b1286..6dbde8f 100644 --- a/canvasView.go +++ b/canvasView.go @@ -107,7 +107,7 @@ func (canvasView *canvasViewData) Redraw() { if canvasView.drawer != nil { canvasView.drawer(canvas) } - canvasView.session.runScript(canvas.finishDraw()) + canvas.finishDraw() } } diff --git a/imageView.go b/imageView.go index 7a56072..c907989 100644 --- a/imageView.go +++ b/imageView.go @@ -1,7 +1,10 @@ package rui import ( + "encoding/base64" "fmt" + "path/filepath" + "runtime" "strings" ) @@ -129,12 +132,10 @@ func (imageView *imageViewData) set(tag string, value any) bool { if text, ok := value.(string); ok { imageView.properties[Source] = text if imageView.created { - src := text - if src != "" && src[0] == '@' { - src, _ = imageProperty(imageView, Source, imageView.session) - } + src, srcset := imageView.src(text) imageView.session.updateProperty(imageView.htmlID(), "src", src) - if srcset := imageView.srcSet(src); srcset != "" { + + if srcset != "" { imageView.session.updateProperty(imageView.htmlID(), "srcset", srcset) } else { imageView.session.removeProperty(imageView.htmlID(), "srcset") @@ -214,29 +215,51 @@ func (imageView *imageViewData) htmlTag() string { return "img" } -/* -func (imageView *imageViewData) closeHTMLTag() bool { - return false +func (imageView *imageViewData) src(src string) (string, string) { + if src != "" && src[0] == '@' { + 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) { + imageView.viewData.htmlProperties(self, buffer) if imageResource, ok := imageProperty(imageView, Source, imageView.Session()); ok && imageResource != "" { - if imageResource[0] == '@' { - if image, ok := imageView.Session().ImageConstant(imageResource[1:]); ok { - imageResource = image - } else { - imageResource = "" - } - } - - if imageResource != "" { + if src, srcset := imageView.src(imageResource); src != "" { buffer.WriteString(` src="`) - buffer.WriteString(imageResource) + buffer.WriteString(src) buffer.WriteString(`"`) - if srcset := imageView.srcSet(imageResource); srcset != "" { + if srcset != "" { buffer.WriteString(` srcset="`) buffer.WriteString(srcset) buffer.WriteString(`"`) diff --git a/path.go b/path.go index 413e805..de16961 100644 --- a/path.go +++ b/path.go @@ -1,10 +1,5 @@ package rui -import ( - "strconv" - "strings" -) - // Path is a path interface type Path interface { // 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. Close() - scriptText() string + create(session Session) +} + +type pathElement struct { + funcName string + args []any } type pathData struct { - script strings.Builder + elements []pathElement } // NewPath creates a new empty Path func NewPath() Path { path := new(pathData) - path.script.Grow(4096) - path.script.WriteString("\nctx.beginPath();") + path.Reset() return path } func (path *pathData) Reset() { - path.script.Reset() - path.script.WriteString("\nctx.beginPath();") + path.elements = []pathElement{ + pathElement{funcName: "beginPath", args: []any{}}, + } } func (path *pathData) MoveTo(x, y float64) { - path.script.WriteString("\nctx.moveTo(") - path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) - path.script.WriteRune(',') - path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64)) - path.script.WriteString(");") + path.elements = append(path.elements, pathElement{funcName: "moveTo", args: []any{x, y}}) } func (path *pathData) LineTo(x, y float64) { - path.script.WriteString("\nctx.lineTo(") - path.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) - path.script.WriteRune(',') - path.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64)) - path.script.WriteString(");") + path.elements = append(path.elements, pathElement{funcName: "lineTo", args: []any{x, y}}) } func (path *pathData) ArcTo(x0, y0, x1, y1, radius float64) { if radius > 0 { - path.script.WriteString("\nctx.arcTo(") - 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(");") + path.elements = append(path.elements, pathElement{funcName: "arcTo", args: []any{x0, y0, x1, y1, radius}}) } } func (path *pathData) Arc(x, y, radius, startAngle, endAngle float64, clockwise bool) { 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 { - path.script.WriteString(",true);") + path.elements = append(path.elements, pathElement{funcName: "arc", args: []any{x, y, radius, startAngle, endAngle, true}}) } 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) { - path.script.WriteString("\nctx.bezierCurveTo(") - 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(");") + path.elements = append(path.elements, pathElement{funcName: "bezierCurveTo", args: []any{cp0x, cp0y, cp1x, cp1y, x, y}}) } func (path *pathData) QuadraticCurveTo(cpx, cpy, x, y float64) { - path.script.WriteString("\nctx.quadraticCurveTo(") - 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(");") + path.elements = append(path.elements, pathElement{funcName: "quadraticCurveTo", args: []any{cpx, cpy, x, y}}) } func (path *pathData) Ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle float64, clockwise bool) { 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 { - path.script.WriteString(",true);") + path.elements = append(path.elements, pathElement{funcName: "ellipse", args: []any{x, y, radiusX, radiusY, rotation, startAngle, endAngle, true}}) } 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() { - path.script.WriteString("\nctx.close();") + path.elements = append(path.elements, pathElement{funcName: "close", args: []any{}}) } -func (path *pathData) scriptText() string { - return path.script.String() +func (path *pathData) create(session Session) { + for _, element := range path.elements { + session.callCanvasFunc(element.funcName, element.args...) + } } diff --git a/resources.go b/resources.go index 16ec8f1..11d3e05 100644 --- a/resources.go +++ b/resources.go @@ -123,7 +123,7 @@ func scanEmbedImagesDir(fs *embed.FS, dir, prefix string) { } else { ext := strings.ToLower(filepath.Ext(name)) switch ext { - case ".png", ".jpg", ".jpeg", ".svg": + case ".png", ".jpg", ".jpeg", ".svg", ".gif", ".bmp": registerImage(fs, path, prefix+name) } } diff --git a/session.go b/session.go index 22521e9..a615ce5 100644 --- a/session.go +++ b/session.go @@ -18,6 +18,13 @@ type webBrige interface { removeProperty(htmlID, property string) readMessage() (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 htmlPropertyValue(htmlID, name string) string answerReceived(answer DataObject) @@ -115,6 +122,13 @@ type Session interface { runScript(script string) startUpdateScript(htmlID string) bool 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 htmlPropertyValue(htmlID, name string) string 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) { if session.brige != nil { session.brige.writeMessage(script) diff --git a/webBrige.go b/webBrige.go index a14fabb..bbc4dcb 100644 --- a/webBrige.go +++ b/webBrige.go @@ -13,13 +13,19 @@ import ( ) type wsBrige struct { - conn *websocket.Conn - answer map[int]chan DataObject - answerID int - answerMutex sync.Mutex - closed bool - buffer strings.Builder - updateScripts map[string]*strings.Builder + conn *websocket.Conn + answer map[int]chan DataObject + answerID int + answerMutex sync.Mutex + closed bool + buffer strings.Builder + canvasBuffer strings.Builder + canvasVarNumber int + updateScripts map[string]*strings.Builder +} + +type canvasVar struct { + name string } var upgrader = websocket.Upgrader{ @@ -119,6 +125,21 @@ func (brige *wsBrige) argToString(arg any) (string, bool) { case float64: 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: if n, ok := isInt(arg); ok { 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) { _, p, err := brige.conn.ReadMessage() if err != nil {