package rui import ( "fmt" "strconv" "strings" ) const ( // MiterJoin - Connected segments are joined by extending their outside edges // to connect at a single point, with the effect of filling an additional // lozenge-shaped area. This setting is affected by the miterLimit property MiterJoin = 0 // RoundJoin - rounds off the corners of a shape by filling an additional sector // of disc centered at the common endpoint of connected segments. // The radius for these rounded corners is equal to the line width. RoundJoin = 1 // BevelJoin - Fills an additional triangular area between the common endpoint // of connected segments, and the separate outside rectangular corners of each segment. BevelJoin = 2 // ButtCap - the ends of lines are squared off at the endpoints. Default value. ButtCap = 0 // RoundCap - the ends of lines are rounded. RoundCap = 1 // SquareCap - the ends of lines are squared off by adding a box with an equal width // and half the height of the line's thickness. SquareCap = 2 // AlphabeticBaseline - the text baseline is the normal alphabetic baseline. Default value. AlphabeticBaseline = 0 // TopBaseline - the text baseline is the top of the em square. TopBaseline = 1 // MiddleBaseline - the text baseline is the middle of the em square. MiddleBaseline = 2 // BottomBaseline - the text baseline is the bottom of the bounding box. // This differs from the ideographic baseline in that the ideographic baseline doesn't consider descenders. BottomBaseline = 3 // HangingBaseline - the text baseline is the hanging baseline. (Used by Tibetan and other Indic scripts.) HangingBaseline = 4 // IdeographicBaseline - the text baseline is the ideographic baseline; this is // the bottom of the body of the characters, if the main body of characters protrudes // beneath the alphabetic baseline. (Used by Chinese, Japanese, and Korean scripts.) IdeographicBaseline = 5 // StartAlign - the text is aligned at the normal start of the line (left-aligned // for left-to-right locales, right-aligned for right-to-left locales). StartAlign = 3 // EndAlign - the text is aligned at the normal end of the line (right-aligned // for left-to-right locales, left-aligned for right-to-left locales). EndAlign = 4 ) // GradientPoint defined by an offset and a color, to a linear or radial gradient type GradientPoint struct { // Offset - a number between 0 and 1, inclusive, representing the position of the color stop Offset float64 // Color - the color of the stop Color Color } // FontParams defined optionally font properties type FontParams struct { // Italic - if true then a font is italic Italic bool // SmallCaps - if true then a font uses small-caps glyphs SmallCaps bool // Weight - a font weight. Valid values: 0...9, there // 0 - a weight does not specify; // 1 - a minimal weight; // 4 - a normal weight; // 7 - a bold weight; // 9 - a maximal weight. Weight int // LineHeight - the height (relative to the font size of the element itself) of a line box. LineHeight SizeUnit } // Canvas is a drawing interface type Canvas interface { // View return the view for the drawing View() CanvasView // Width returns the width in pixels of the canvas area Width() float64 // Height returns the height in pixels of the canvas area Height() float64 // Save saves the entire state of the canvas by pushing the current state onto a stack. Save() // Restore restores the most recently saved canvas state by popping the top entry // in the drawing state stack. If there is no saved state, this method does nothing. Restore() // ClipPath turns the rectangle into the current clipping region. It replaces any previous clipping region. ClipRect(x, y, width, height float64) // ClipPath turns the path into the current clipping region. It replaces any previous clipping region. ClipPath(path Path) // SetScale adds a scaling transformation to the canvas units horizontally and/or vertically. // x - scaling factor in the horizontal direction. A negative value flips pixels across // the vertical axis. A value of 1 results in no horizontal scaling; // y - scaling factor in the vertical direction. A negative value flips pixels across // the horizontal axis. A value of 1 results in no vertical scaling. SetScale(x, y float64) // SetTranslation adds a translation transformation to the current matrix. // x - distance to move in the horizontal direction. Positive values are to the right, and negative to the left; // y - distance to move in the vertical direction. Positive values are down, and negative are up. SetTranslation(x, y float64) // SetRotation adds a rotation to the transformation matrix. // angle - the rotation angle, clockwise in radians SetRotation(angle float64) // SetTransformation multiplies the current transformation with the matrix described by the arguments // of this method. This lets you scale, rotate, translate (move), and skew the context. // The transformation matrix is described by: // ⎡ xScale xSkew dx ⎤ // ⎢ ySkew yScale dy ⎥ // ⎣ 0 0 1 ⎦ // xScale, yScale - horizontal and vertical scaling. A value of 1 results in no scaling; // xSkew, ySkew - horizontal and vertical skewing; // dx, dy - horizontal and vertical translation (moving). SetTransformation(xScale, yScale, xSkew, ySkew, dx, dy float64) // ResetTransformation resets the current transform to the identity matrix ResetTransformation() // SetSolidColorFillStyle sets the color to use inside shapes SetSolidColorFillStyle(color Color) // SetSolidColorStrokeStyle sets color to use for the strokes (outlines) around shapes SetSolidColorStrokeStyle(color Color) // SetLinearGradientFillStyle sets a gradient along the line connecting two given coordinates to use inside shapes // x0, y0 - coordinates of the start point; // x1, y1 - coordinates of the end point; // startColor, endColor - the start and end color // stopPoints - the array of stop points SetLinearGradientFillStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) // SetLinearGradientStrokeStyle sets a gradient along the line connecting two given coordinates to use for the strokes (outlines) around shapes // x0, y0 - coordinates of the start point; // x1, y1 - coordinates of the end point; // color0, color1 - the start and end color // stopPoints - the array of stop points SetLinearGradientStrokeStyle(x0, y0 float64, color0 Color, x1, y1 float64, color1 Color, stopPoints []GradientPoint) // SetRadialGradientFillStyle sets a radial gradient using the size and coordinates of two circles // to use inside shapes // x0, y0 - coordinates of the center of the start circle; // r0 - the radius of the start circle; // x1, y1 - coordinates the center of the end circle; // r1 - the radius of the end circle; // color0, color1 - the start and end color // stopPoints - the array of stop points SetRadialGradientFillStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) // SetRadialGradientStrokeStyle sets a radial gradient using the size and coordinates of two circles // to use for the strokes (outlines) around shapes // x0, y0 - coordinates of the center of the start circle; // r0 - the radius of the start circle; // x1, y1 - coordinates the center of the end circle; // r1 - the radius of the end circle; // color0, color1 - the start and end color // stopPoints - the array of stop points SetRadialGradientStrokeStyle(x0, y0, r0 float64, color0 Color, x1, y1, r1 float64, color1 Color, stopPoints []GradientPoint) // SetImageFillStyle set the image as the filling pattern. // repeate - indicating how to repeat the pattern's image. Possible values are: // NoRepeat (0) - neither direction, // RepeatXY (1) - both directions, // RepeatX (2) - horizontal only, // RepeatY (3) - vertical only. SetImageFillStyle(image Image, repeat int) // SetLineWidth the line width, in coordinate space units. Zero, negative, Infinity, and NaN values are ignored. SetLineWidth(width float64) // SetLineJoin sets the shape used to join two line segments where they meet. // Valid values: MiterJoin (0), RoundJoin (1), BevelJoin (2). All other values are ignored. SetLineJoin(join int) // SetLineJoin sets the shape used to draw the end points of lines. // Valid values: ButtCap (0), RoundCap (1), SquareCap (2). All other values are ignored. SetLineCap(cap int) // SetLineDash sets the line dash pattern used when stroking lines. // dash - an array of values that specify alternating lengths of lines and gaps which describe the pattern. // offset - the line dash offset SetLineDash(dash []float64, offset float64) // SetFont sets the current text style to use when drawing text SetFont(name string, size SizeUnit) // SetFontWithParams sets the current text style to use when drawing text SetFontWithParams(name string, size SizeUnit, params FontParams) // TextWidth calculates the width of the text drawn by a given font TextWidth(text string, fontName string, fontSize SizeUnit) float64 // SetTextBaseline sets the current text baseline used when drawing text. Valid values: // AlphabeticBaseline (0), TopBaseline (1), MiddleBaseline (2), BottomBaseline (3), // HangingBaseline (4), and IdeographicBaseline (5). All other values are ignored. SetTextBaseline(baseline int) // SetTextAlign sets the current text alignment used when drawing text. Valid values: // LeftAlign (0), RightAlign (1), CenterAlign (2), StartAlign (3), and EndAlign(4). All other values are ignored. SetTextAlign(align int) // SetShadow sets shadow parameters: // offsetX, offsetY - the distance that shadows will be offset horizontally and vertically; // blur - the amount of blur applied to shadows. Must be non-negative; // color - the color of shadows. SetShadow(offsetX, offsetY, blur float64, color Color) // ResetShadow sets shadow parameters to default values (invisible shadow) ResetShadow() // ClearRect erases the pixels in a rectangular area by setting them to transparent black ClearRect(x, y, width, height float64) // FillRect draws a rectangle that is filled according to the current FillStyle. FillRect(x, y, width, height float64) // StrokeRect draws a rectangle that is stroked (outlined) according to the current strokeStyle // and other context settings StrokeRect(x, y, width, height float64) // FillAndStrokeRect draws a rectangle that is filled according to the current FillStyle and // is stroked (outlined) according to the current strokeStyle and other context settings FillAndStrokeRect(x, y, width, height float64) // FillRoundedRect draws a rounded rectangle that is filled according to the current FillStyle. FillRoundedRect(x, y, width, height, r float64) // StrokeRoundedRect draws a rounded rectangle that is stroked (outlined) according // to the current strokeStyle and other context settings StrokeRoundedRect(x, y, width, height, r float64) // FillAndStrokeRoundedRect draws a rounded rectangle that is filled according to the current FillStyle // and is stroked (outlined) according to the current strokeStyle and other context settings FillAndStrokeRoundedRect(x, y, width, height, r float64) // FillEllipse draws a ellipse that is filled according to the current FillStyle. // x, y - coordinates of the ellipse's center; // radiusX - the ellipse's major-axis radius. Must be non-negative; // radiusY - the ellipse's minor-axis radius. Must be non-negative; // rotation - the rotation of the ellipse, expressed in radians. FillEllipse(x, y, radiusX, radiusY, rotation float64) // StrokeRoundedRect draws a ellipse that is stroked (outlined) according // to the current strokeStyle and other context settings StrokeEllipse(x, y, radiusX, radiusY, rotation float64) // FillAndStrokeEllipse draws a ellipse that is filled according to the current FillStyle // and is stroked (outlined) according to the current strokeStyle and other context settings FillAndStrokeEllipse(x, y, radiusX, radiusY, rotation float64) // FillPath draws a path that is filled according to the current FillStyle. FillPath(path Path) // StrokePath draws a path that is stroked (outlined) according to the current strokeStyle // and other context settings StrokePath(path Path) // FillAndStrokeRect draws a path that is filled according to the current FillStyle and // is stroked (outlined) according to the current strokeStyle and other context settings FillAndStrokePath(path Path) // DrawLine draws a line according to the current strokeStyle and other context settings DrawLine(x0, y0, x1, y1 float64) // FillText draws a text string at the specified coordinates, filling the string's characters // with the current FillStyle FillText(x, y float64, text string) // StrokeText strokes — that is, draws the outlines of — the characters of a text string // at the specified coordinates StrokeText(x, y float64, text string) // DrawImage draws the image at the (x, y) position DrawImage(x, y float64, image Image) // DrawImageInRect draws the image in the rectangle (x, y, width, height), scaling in height and width if necessary DrawImageInRect(x, y, width, height float64, image Image) // DrawImageFragment draws the frament (described by srcX, srcY, srcWidth, srcHeight) of image // 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 } type canvasData struct { view CanvasView script strings.Builder } 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);") */ return canvas } func (canvas *canvasData) finishDraw() string { canvas.script.WriteString("\n") return canvas.script.String() } func (canvas *canvasData) View() CanvasView { return canvas.view } func (canvas *canvasData) Width() float64 { if canvas.view != nil { return canvas.view.Frame().Width } return 0 } func (canvas *canvasData) Height() float64 { if canvas.view != nil { return canvas.view.Frame().Height } return 0 } func (canvas *canvasData) Save() { canvas.script.WriteString("\nctx.save();") } func (canvas *canvasData) Restore() { canvas.script.WriteString("\nctx.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();") } func (canvas *canvasData) ClipPath(path Path) { canvas.script.WriteString(path.scriptText()) canvas.script.WriteString("\nctx.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(");") } 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(");") } func (canvas *canvasData) SetRotation(angle float64) { canvas.script.WriteString("\nctx.rotate(") canvas.script.WriteString(strconv.FormatFloat(angle, 'g', -1, 64)) canvas.script.WriteString(");") } 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(");") } func (canvas *canvasData) ResetTransformation() { 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(`";`) } func (canvas *canvasData) SetSolidColorStrokeStyle(color Color) { canvas.script.WriteString("\nctx.strokeStyle = \"") canvas.script.WriteString(color.cssString()) canvas.script.WriteString(`";`) } 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("');") 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.script.WriteString("\ngradient.addColorStop(1, '") canvas.script.WriteString(color1.cssString()) canvas.script.WriteString("');") } 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;") } 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;") } 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("');") 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.script.WriteString("\ngradient.addColorStop(1, '") canvas.script.WriteString(color1.cssString()) canvas.script.WriteString("');") } 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;") } 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;") } func (canvas *canvasData) SetImageFillStyle(image Image, repeat int) { if image == nil || image.LoadingStatus() != ImageReady { return } var repeatText string switch repeat { case NoRepeat: repeatText = "no-repeat" case RepeatXY: repeatText = "repeat" case RepeatX: repeatText = "repeat-x" case RepeatY: repeatText = "repeat-y" default: 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}") } 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("';") } } func (canvas *canvasData) SetLineJoin(join int) { switch join { case MiterJoin: canvas.script.WriteString("\nctx.lineJoin = 'miter';") case RoundJoin: canvas.script.WriteString("\nctx.lineJoin = 'round';") case BevelJoin: canvas.script.WriteString("\nctx.lineJoin = 'bevel';") } } func (canvas *canvasData) SetLineCap(cap int) { switch cap { case ButtCap: canvas.script.WriteString("\nctx.lineCap = 'butt';") case RoundCap: canvas.script.WriteString("\nctx.lineCap = 'round';") case SquareCap: canvas.script.WriteString("\nctx.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("]);") if offset >= 0 { canvas.script.WriteString("\nctx.lineDashOffset = '") 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, ",") 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) } } script.WriteString("';") } func (canvas *canvasData) SetFont(name string, size SizeUnit) { canvas.script.WriteString("\nctx.font = '") canvas.script.WriteString(size.cssString("1em")) canvas.writeFont(name, &canvas.script) } 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("1em")) 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.writeFont(name, script) } func (canvas *canvasData) SetFontWithParams(name string, size SizeUnit, params FontParams) { canvas.setFontWithParams(name, size, params, &canvas.script) } func (canvas *canvasData) TextWidth(text string, fontName string, fontSize SizeUnit) float64 { buffer := allocStringBuilder() defer freeStringBuilder(buffer) canvas.setFontWithParams(fontName, fontSize, FontParams{}, buffer) fontParams := buffer.String() buffer.Reset() canvas.writeStringArgs(text, buffer) str := buffer.String() script := fmt.Sprintf(` var w = 0; const canvas = document.getElementById('%s'); if (canvas) { const ctx = canvas.getContext('2d'); if (ctx) { ctx.save() const dpr = window.devicePixelRatio || 1; ctx.scale(dpr, dpr); %s; w = ctx.measureText('%s').width; ctx.restore(); } } sendMessage('answer{width=' + w + ', answerID=' + answerID + '}'); `, canvas.View().htmlID(), fontParams, str) result := canvas.View().Session().runGetterScript(script) switch result.Tag() { case "answer": if value, ok := result.PropertyValue("width"); ok { w, err := strconv.ParseFloat(value, 32) if err == nil { return w } ErrorLog(err.Error()) } case "error": if text, ok := result.PropertyValue("errorText"); ok { ErrorLog(text) } else { ErrorLog("error") } default: ErrorLog("Unknown answer: " + result.Tag()) } return 0 } func (canvas *canvasData) SetTextBaseline(baseline int) { switch baseline { case AlphabeticBaseline: canvas.script.WriteString("\nctx.textBaseline = 'alphabetic';") case TopBaseline: canvas.script.WriteString("\nctx.textBaseline = 'top';") case MiddleBaseline: canvas.script.WriteString("\nctx.textBaseline = 'middle';") case BottomBaseline: canvas.script.WriteString("\nctx.textBaseline = 'bottom';") case HangingBaseline: canvas.script.WriteString("\nctx.textBaseline = 'hanging';") case IdeographicBaseline: canvas.script.WriteString("\nctx.textBaseline = 'ideographic';") } } func (canvas *canvasData) SetTextAlign(align int) { switch align { case LeftAlign: canvas.script.WriteString("\nctx.textAlign = 'left';") case RightAlign: canvas.script.WriteString("\nctx.textAlign = 'right';") case CenterAlign: canvas.script.WriteString("\nctx.textAlign = 'center';") case StartAlign: canvas.script.WriteString("\nctx.textAlign = 'start';") case EndAlign: canvas.script.WriteString("\nctx.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(";") } } 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)) } func (canvas *canvasData) ClearRect(x, y, width, height float64) { canvas.script.WriteString("\nctx.clearRect(") canvas.writeRectArgs(x, y, width, height) canvas.script.WriteString(");") } func (canvas *canvasData) FillRect(x, y, width, height float64) { canvas.script.WriteString("\nctx.fillRect(") canvas.writeRectArgs(x, y, width, height) canvas.script.WriteString(");") } func (canvas *canvasData) StrokeRect(x, y, width, height float64) { canvas.script.WriteString("\nctx.strokeRect(") canvas.writeRectArgs(x, y, width, height) canvas.script.WriteString(");") } func (canvas *canvasData) FillAndStrokeRect(x, y, width, height float64) { canvas.FillRect(x, y, width, height) canvas.StrokeRect(x, y, width, height) } func (canvas *canvasData) writeRoundedRect(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) bottom := strconv.FormatFloat(y+height, 'g', -1, 64) leftR := strconv.FormatFloat(x+r, 'g', -1, 64) topR := strconv.FormatFloat(y+r, 'g', -1, 64) 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();") } func (canvas *canvasData) FillRoundedRect(x, y, width, height, r float64) { canvas.writeRoundedRect(x, y, width, height, r) canvas.script.WriteString("\nctx.fill();") } func (canvas *canvasData) StrokeRoundedRect(x, y, width, height, r float64) { canvas.writeRoundedRect(x, y, width, height, r) canvas.script.WriteString("\nctx.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();") } 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) FillEllipse(x, y, radiusX, radiusY, rotation float64) { if radiusX >= 0 && radiusY >= 0 { canvas.writeEllipse(x, y, radiusX, radiusY, rotation) canvas.script.WriteString("\nctx.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();") } } 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();") } } func (canvas *canvasData) writePointArgs(x, y float64) { canvas.script.WriteString(strconv.FormatFloat(x, 'g', -1, 64)) canvas.script.WriteRune(',') canvas.script.WriteString(strconv.FormatFloat(y, 'g', -1, 64)) } func (canvas *canvasData) writeStringArgs(text string, script *strings.Builder) { //rText := []rune(text) for _, ch := range text { switch ch { case '\t': script.WriteString(`\t`) case '\n': script.WriteString(`\n`) case '\r': script.WriteString(`\r`) case '\\': script.WriteString(`\\`) case '"': script.WriteString(`\"`) case '\'': script.WriteString(`\'`) default: if ch < ' ' { script.WriteString(fmt.Sprintf("\\x%02X", int(ch))) } else { script.WriteRune(ch) } } } } 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(");") } 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(");") } func (canvas *canvasData) FillPath(path Path) { canvas.script.WriteString(path.scriptText()) canvas.script.WriteString("\nctx.fill();") } func (canvas *canvasData) StrokePath(path Path) { canvas.script.WriteString(path.scriptText()) canvas.script.WriteString("\nctx.stroke();") } func (canvas *canvasData) FillAndStrokePath(path Path) { canvas.script.WriteString(path.scriptText()) canvas.script.WriteString("\nctx.fill();\nctx.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();") } 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}") } 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}") } 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}") }