You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
5.1 KiB
CoffeeScript
205 lines
5.1 KiB
CoffeeScript
SVGPath = require '../path'
|
|
{number} = require '../object'
|
|
|
|
# This constant is used to approximate a symmetrical arc using a cubic
|
|
# Bezier curve.
|
|
KAPPA = 4.0 * ((Math.sqrt(2) - 1.0) / 3.0)
|
|
module.exports =
|
|
initVector: ->
|
|
@_ctm = [1, 0, 0, 1, 0, 0] # current transformation matrix
|
|
@_ctmStack = []
|
|
|
|
save: ->
|
|
@_ctmStack.push @_ctm.slice()
|
|
# TODO: save/restore colorspace and styles so not setting it unnessesarily all the time?
|
|
@addContent 'q'
|
|
|
|
restore: ->
|
|
@_ctm = @_ctmStack.pop() or [1, 0, 0, 1, 0, 0]
|
|
@addContent 'Q'
|
|
|
|
closePath: ->
|
|
@addContent 'h'
|
|
|
|
lineWidth: (w) ->
|
|
@addContent "#{number(w)} w"
|
|
|
|
_CAP_STYLES:
|
|
BUTT: 0
|
|
ROUND: 1
|
|
SQUARE: 2
|
|
|
|
lineCap: (c) ->
|
|
c = @_CAP_STYLES[c.toUpperCase()] if typeof c is 'string'
|
|
@addContent "#{c} J"
|
|
|
|
_JOIN_STYLES:
|
|
MITER: 0
|
|
ROUND: 1
|
|
BEVEL: 2
|
|
|
|
lineJoin: (j) ->
|
|
j = @_JOIN_STYLES[j.toUpperCase()] if typeof j is 'string'
|
|
@addContent "#{j} j"
|
|
|
|
miterLimit: (m) ->
|
|
@addContent "#{number(m)} M"
|
|
|
|
dash: (length, options = {}) ->
|
|
return this unless length?
|
|
if Array.isArray length
|
|
length = (number(v) for v in length).join(' ')
|
|
phase = options.phase or 0
|
|
@addContent "[#{length}] #{number(phase)} d"
|
|
else
|
|
space = options.space ? length
|
|
phase = options.phase or 0
|
|
@addContent "[#{number(length)} #{number(space)}] #{number(phase)} d"
|
|
|
|
undash: ->
|
|
@addContent "[] 0 d"
|
|
|
|
moveTo: (x, y) ->
|
|
@addContent "#{number(x)} #{number(y)} m"
|
|
|
|
lineTo: (x, y) ->
|
|
@addContent "#{number(x)} #{number(y)} l"
|
|
|
|
bezierCurveTo: (cp1x, cp1y, cp2x, cp2y, x, y) ->
|
|
@addContent "#{number(cp1x)} #{number(cp1y)} #{number(cp2x)} #{number(cp2y)} #{number(x)} #{number(y)} c"
|
|
|
|
quadraticCurveTo: (cpx, cpy, x, y) ->
|
|
@addContent "#{number(cpx)} #{number(cpy)} #{number(x)} #{number(y)} v"
|
|
|
|
rect: (x, y, w, h) ->
|
|
@addContent "#{number(x)} #{number(y)} #{number(w)} #{number(h)} re"
|
|
|
|
roundedRect: (x, y, w, h, r = 0) ->
|
|
r = Math.min(r, 0.5 * w, 0.5 * h)
|
|
|
|
# amount to inset control points from corners (see `ellipse`)
|
|
c = r * (1.0 - KAPPA)
|
|
|
|
@moveTo x + r, y
|
|
@lineTo x + w - r, y
|
|
@bezierCurveTo x + w - c, y, x + w, y + c, x + w, y + r
|
|
@lineTo x + w, y + h - r
|
|
@bezierCurveTo x + w, y + h - c, x + w - c, y + h, x + w - r, y + h
|
|
@lineTo x + r, y + h
|
|
@bezierCurveTo x + c, y + h, x, y + h - c, x, y + h - r
|
|
@lineTo x, y + r
|
|
@bezierCurveTo x, y + c, x + c, y, x + r, y
|
|
@closePath()
|
|
|
|
ellipse: (x, y, r1, r2 = r1) ->
|
|
# based on http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas/2173084#2173084
|
|
x -= r1
|
|
y -= r2
|
|
ox = r1 * KAPPA
|
|
oy = r2 * KAPPA
|
|
xe = x + r1 * 2
|
|
ye = y + r2 * 2
|
|
xm = x + r1
|
|
ym = y + r2
|
|
|
|
@moveTo(x, ym)
|
|
@bezierCurveTo(x, ym - oy, xm - ox, y, xm, y)
|
|
@bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym)
|
|
@bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye)
|
|
@bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym)
|
|
@closePath()
|
|
|
|
circle: (x, y, radius) ->
|
|
@ellipse x, y, radius
|
|
|
|
polygon: (points...) ->
|
|
@moveTo points.shift()...
|
|
@lineTo point... for point in points
|
|
@closePath()
|
|
|
|
path: (path) ->
|
|
SVGPath.apply this, path
|
|
return this
|
|
|
|
_windingRule: (rule) ->
|
|
if /even-?odd/.test(rule)
|
|
return '*'
|
|
|
|
return ''
|
|
|
|
fill: (color, rule) ->
|
|
if /(even-?odd)|(non-?zero)/.test(color)
|
|
rule = color
|
|
color = null
|
|
|
|
@fillColor color if color
|
|
@addContent 'f' + @_windingRule(rule)
|
|
|
|
stroke: (color) ->
|
|
@strokeColor color if color
|
|
@addContent 'S'
|
|
|
|
fillAndStroke: (fillColor, strokeColor = fillColor, rule) ->
|
|
isFillRule = /(even-?odd)|(non-?zero)/
|
|
if isFillRule.test(fillColor)
|
|
rule = fillColor
|
|
fillColor = null
|
|
|
|
if isFillRule.test(strokeColor)
|
|
rule = strokeColor
|
|
strokeColor = fillColor
|
|
|
|
if fillColor
|
|
@fillColor fillColor
|
|
@strokeColor strokeColor
|
|
|
|
@addContent 'B' + @_windingRule(rule)
|
|
|
|
clip: (rule) ->
|
|
@addContent 'W' + @_windingRule(rule) + ' n'
|
|
|
|
transform: (m11, m12, m21, m22, dx, dy) ->
|
|
# keep track of the current transformation matrix
|
|
m = @_ctm
|
|
[m0, m1, m2, m3, m4, m5] = m
|
|
m[0] = m0 * m11 + m2 * m12
|
|
m[1] = m1 * m11 + m3 * m12
|
|
m[2] = m0 * m21 + m2 * m22
|
|
m[3] = m1 * m21 + m3 * m22
|
|
m[4] = m0 * dx + m2 * dy + m4
|
|
m[5] = m1 * dx + m3 * dy + m5
|
|
|
|
values = (number(v) for v in [m11, m12, m21, m22, dx, dy]).join(' ')
|
|
@addContent "#{values} cm"
|
|
|
|
translate: (x, y) ->
|
|
@transform 1, 0, 0, 1, x, y
|
|
|
|
rotate: (angle, options = {}) ->
|
|
rad = angle * Math.PI / 180
|
|
cos = Math.cos(rad)
|
|
sin = Math.sin(rad)
|
|
x = y = 0
|
|
|
|
if options.origin?
|
|
[x, y] = options.origin
|
|
x1 = x * cos - y * sin
|
|
y1 = x * sin + y * cos
|
|
x -= x1
|
|
y -= y1
|
|
|
|
@transform cos, sin, -sin, cos, x, y
|
|
|
|
scale: (xFactor, yFactor = xFactor, options = {}) ->
|
|
if arguments.length is 2
|
|
yFactor = xFactor
|
|
options = yFactor
|
|
|
|
x = y = 0
|
|
if options.origin?
|
|
[x, y] = options.origin
|
|
x -= xFactor * x
|
|
y -= yFactor * y
|
|
|
|
@transform xFactor, 0, 0, yFactor, x, y
|