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.
327 lines
6.9 KiB
CoffeeScript
327 lines
6.9 KiB
CoffeeScript
class SVGPath
|
|
@apply: (doc, path) ->
|
|
commands = parse path
|
|
apply commands, doc
|
|
|
|
parameters =
|
|
A: 7
|
|
a: 7
|
|
C: 6
|
|
c: 6
|
|
H: 1
|
|
h: 1
|
|
L: 2
|
|
l: 2
|
|
M: 2
|
|
m: 2
|
|
Q: 4
|
|
q: 4
|
|
S: 4
|
|
s: 4
|
|
T: 2
|
|
t: 2
|
|
V: 1
|
|
v: 1
|
|
Z: 0
|
|
z: 0
|
|
|
|
parse = (path) ->
|
|
ret = []
|
|
args = []
|
|
curArg = ""
|
|
foundDecimal = no
|
|
params = 0
|
|
|
|
for c in path
|
|
if parameters[c]?
|
|
params = parameters[c]
|
|
if cmd # save existing command
|
|
args[args.length] = +curArg if curArg.length > 0
|
|
ret[ret.length] = {cmd,args}
|
|
|
|
args = []
|
|
curArg = ""
|
|
foundDecimal = no
|
|
|
|
cmd = c
|
|
|
|
else if c in [" ", ","] or (c is "-" and curArg.length > 0 and curArg[curArg.length - 1] isnt 'e') or (c is "." and foundDecimal)
|
|
continue if curArg.length is 0
|
|
|
|
if args.length is params # handle reused commands
|
|
ret[ret.length] = {cmd,args}
|
|
args = [+curArg]
|
|
|
|
# handle assumed commands
|
|
cmd = "L" if cmd is "M"
|
|
cmd = "l" if cmd is "m"
|
|
|
|
else
|
|
args[args.length] = +curArg
|
|
|
|
foundDecimal = (c is ".")
|
|
|
|
# fix for negative numbers or repeated decimals with no delimeter between commands
|
|
curArg = if c in ['-', '.'] then c else ''
|
|
|
|
else
|
|
curArg += c
|
|
foundDecimal = true if c is '.'
|
|
|
|
# add the last command
|
|
if curArg.length > 0
|
|
if args.length is params # handle reused commands
|
|
ret[ret.length] = {cmd, args}
|
|
args = [+curArg]
|
|
|
|
# handle assumed commands
|
|
cmd = "L" if cmd is "M"
|
|
cmd = "l" if cmd is "m"
|
|
else
|
|
args[args.length] = +curArg
|
|
|
|
ret[ret.length] = {cmd,args}
|
|
|
|
return ret
|
|
|
|
cx = cy = px = py = sx = sy = 0
|
|
apply = (commands, doc) ->
|
|
# current point, control point, and subpath starting point
|
|
cx = cy = px = py = sx = sy = 0
|
|
|
|
# run the commands
|
|
for c, i in commands
|
|
runners[c.cmd]?(doc, c.args)
|
|
|
|
cx = cy = px = py = 0
|
|
|
|
runners =
|
|
M: (doc, a) ->
|
|
cx = a[0]
|
|
cy = a[1]
|
|
px = py = null
|
|
sx = cx
|
|
sy = cy
|
|
doc.moveTo cx, cy
|
|
|
|
m: (doc, a) ->
|
|
cx += a[0]
|
|
cy += a[1]
|
|
px = py = null
|
|
sx = cx
|
|
sy = cy
|
|
doc.moveTo cx, cy
|
|
|
|
C: (doc, a) ->
|
|
cx = a[4]
|
|
cy = a[5]
|
|
px = a[2]
|
|
py = a[3]
|
|
doc.bezierCurveTo a...
|
|
|
|
c: (doc, a) ->
|
|
doc.bezierCurveTo a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy, a[4] + cx, a[5] + cy
|
|
px = cx + a[2]
|
|
py = cy + a[3]
|
|
cx += a[4]
|
|
cy += a[5]
|
|
|
|
S: (doc, a) ->
|
|
if px is null
|
|
px = cx
|
|
py = cy
|
|
|
|
doc.bezierCurveTo cx-(px-cx), cy-(py-cy), a[0], a[1], a[2], a[3]
|
|
px = a[0]
|
|
py = a[1]
|
|
cx = a[2]
|
|
cy = a[3]
|
|
|
|
s: (doc, a) ->
|
|
if px is null
|
|
px = cx
|
|
py = cy
|
|
|
|
doc.bezierCurveTo cx-(px-cx), cy-(py-cy), cx + a[0], cy + a[1], cx + a[2], cy + a[3]
|
|
px = cx + a[0]
|
|
py = cy + a[1]
|
|
cx += a[2]
|
|
cy += a[3]
|
|
|
|
Q: (doc, a) ->
|
|
px = a[0]
|
|
py = a[1]
|
|
cx = a[2]
|
|
cy = a[3]
|
|
doc.quadraticCurveTo(a[0], a[1], cx, cy)
|
|
|
|
q: (doc, a) ->
|
|
doc.quadraticCurveTo(a[0] + cx, a[1] + cy, a[2] + cx, a[3] + cy)
|
|
px = cx + a[0]
|
|
py = cy + a[1]
|
|
cx += a[2]
|
|
cy += a[3]
|
|
|
|
T: (doc, a) ->
|
|
if px is null
|
|
px = cx
|
|
py = cy
|
|
else
|
|
px = cx-(px-cx)
|
|
py = cy-(py-cy)
|
|
|
|
doc.quadraticCurveTo(px, py, a[0], a[1])
|
|
px = cx-(px-cx)
|
|
py = cy-(py-cy)
|
|
cx = a[0]
|
|
cy = a[1]
|
|
|
|
t: (doc, a) ->
|
|
if px is null
|
|
px = cx
|
|
py = cy
|
|
else
|
|
px = cx-(px-cx)
|
|
py = cy-(py-cy)
|
|
|
|
doc.quadraticCurveTo(px, py, cx + a[0], cy + a[1])
|
|
cx += a[0]
|
|
cy += a[1]
|
|
|
|
A: (doc, a) ->
|
|
solveArc(doc, cx, cy, a)
|
|
cx = a[5]
|
|
cy = a[6]
|
|
|
|
a: (doc, a) ->
|
|
a[5] += cx
|
|
a[6] += cy
|
|
solveArc(doc, cx, cy, a)
|
|
cx = a[5]
|
|
cy = a[6]
|
|
|
|
L: (doc, a) ->
|
|
cx = a[0]
|
|
cy = a[1]
|
|
px = py = null
|
|
doc.lineTo(cx, cy)
|
|
|
|
l: (doc, a) ->
|
|
cx += a[0]
|
|
cy += a[1]
|
|
px = py = null
|
|
doc.lineTo(cx, cy)
|
|
|
|
H: (doc, a) ->
|
|
cx = a[0]
|
|
px = py = null
|
|
doc.lineTo(cx, cy)
|
|
|
|
h: (doc, a) ->
|
|
cx += a[0]
|
|
px = py = null
|
|
doc.lineTo(cx, cy)
|
|
|
|
V: (doc, a) ->
|
|
cy = a[0]
|
|
px = py = null
|
|
doc.lineTo(cx, cy)
|
|
|
|
v: (doc, a) ->
|
|
cy += a[0]
|
|
px = py = null
|
|
doc.lineTo(cx, cy)
|
|
|
|
Z: (doc) ->
|
|
doc.closePath()
|
|
cx = sx
|
|
cy = sy
|
|
|
|
z: (doc) ->
|
|
doc.closePath()
|
|
cx = sx
|
|
cy = sy
|
|
|
|
solveArc = (doc, x, y, coords) ->
|
|
[rx,ry,rot,large,sweep,ex,ey] = coords
|
|
segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y)
|
|
|
|
for seg in segs
|
|
bez = segmentToBezier seg...
|
|
doc.bezierCurveTo bez...
|
|
|
|
# from Inkscape svgtopdf, thanks!
|
|
arcToSegments = (x, y, rx, ry, large, sweep, rotateX, ox, oy) ->
|
|
th = rotateX * (Math.PI/180)
|
|
sin_th = Math.sin(th)
|
|
cos_th = Math.cos(th)
|
|
rx = Math.abs(rx)
|
|
ry = Math.abs(ry)
|
|
px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5
|
|
py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5
|
|
pl = (px*px) / (rx*rx) + (py*py) / (ry*ry)
|
|
if pl > 1
|
|
pl = Math.sqrt(pl)
|
|
rx *= pl
|
|
ry *= pl
|
|
|
|
a00 = cos_th / rx
|
|
a01 = sin_th / rx
|
|
a10 = (-sin_th) / ry
|
|
a11 = (cos_th) / ry
|
|
x0 = a00 * ox + a01 * oy
|
|
y0 = a10 * ox + a11 * oy
|
|
x1 = a00 * x + a01 * y
|
|
y1 = a10 * x + a11 * y
|
|
|
|
d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0)
|
|
sfactor_sq = 1 / d - 0.25
|
|
sfactor_sq = 0 if sfactor_sq < 0
|
|
sfactor = Math.sqrt(sfactor_sq)
|
|
sfactor = -sfactor if sweep is large
|
|
|
|
xc = 0.5 * (x0 + x1) - sfactor * (y1-y0)
|
|
yc = 0.5 * (y0 + y1) + sfactor * (x1-x0)
|
|
|
|
th0 = Math.atan2(y0-yc, x0-xc)
|
|
th1 = Math.atan2(y1-yc, x1-xc)
|
|
|
|
th_arc = th1-th0
|
|
if th_arc < 0 && sweep is 1
|
|
th_arc += 2*Math.PI
|
|
else if th_arc > 0 && sweep is 0
|
|
th_arc -= 2 * Math.PI
|
|
|
|
segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)))
|
|
result = []
|
|
|
|
for i in [0...segments]
|
|
th2 = th0 + i * th_arc / segments
|
|
th3 = th0 + (i+1) * th_arc / segments
|
|
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th]
|
|
|
|
return result
|
|
|
|
segmentToBezier = (cx, cy, th0, th1, rx, ry, sin_th, cos_th) ->
|
|
a00 = cos_th * rx
|
|
a01 = -sin_th * ry
|
|
a10 = sin_th * rx
|
|
a11 = cos_th * ry
|
|
|
|
th_half = 0.5 * (th1 - th0)
|
|
t = (8 / 3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half)
|
|
x1 = cx + Math.cos(th0) - t * Math.sin(th0)
|
|
y1 = cy + Math.sin(th0) + t * Math.cos(th0)
|
|
x3 = cx + Math.cos(th1)
|
|
y3 = cy + Math.sin(th1)
|
|
x2 = x3 + t * Math.sin(th1)
|
|
y2 = y3 - t * Math.cos(th1)
|
|
|
|
return [
|
|
a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
|
|
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
|
|
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
|
|
]
|
|
|
|
module.exports = SVGPath
|