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}")
}