mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-05-14 08:43:12 +00:00
Add support for elliptical arc commands (a, A) in SVG paths. Thanks @devongovett for initial code (and algorithm). Add 4 different arc test files (can be loaded in kitchensink demo).
This commit is contained in:
parent
5fc29b1dbb
commit
e3c311dfb5
8 changed files with 317 additions and 17 deletions
5
demos/kitchensink/assets/104.svg
Normal file
5
demos/kitchensink/assets/104.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" width="200" height="200" viewBox="0 0 200 200">
|
||||
<path d="M10,150 A15 15 180 0 1 70 140 A15 25 180 0 0 130 130 A15 55 180 0 1 190 120" stroke="red" stroke-width="4" fill="none" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
7
demos/kitchensink/assets/105.svg
Normal file
7
demos/kitchensink/assets/105.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="320" height="320"
|
||||
xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<path d="M160,160 h-150 a150,150 0 1,0 150,-150 z" fill="red" stroke="blue" stroke-width="5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 333 B |
10
demos/kitchensink/assets/106.svg
Normal file
10
demos/kitchensink/assets/106.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg viewBox = "0 0 1100 400" version = "1.1">
|
||||
<g stroke = "black" stroke-width = "3" fill = "none">
|
||||
<path d = "M 50 200 a 100 50 0 1 1 250 50"/>
|
||||
<path d = "M 400 100 a 100 50 30 1 1 250 50"/>
|
||||
<path d = "M 400 300 a 100 50 45 1 1 250 50"/>
|
||||
<path d = "M 750 200 a 100 50 135 1 1 250 50"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 457 B |
7
demos/kitchensink/assets/107.svg
Normal file
7
demos/kitchensink/assets/107.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<svg width="325px" height="325px" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 Z" fill="green"/>
|
||||
<path d="M230 80 A 45 45, 0, 1, 0, 275 125 L 275 80 Z" fill="red"/>
|
||||
<path d="M80 230 A 45 45, 0, 0, 1, 125 275 L 125 230 Z" fill="purple"/>
|
||||
<path d="M230 230 A 45 45, 0, 1, 1, 275 275 L 275 230 Z" fill="blue"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
|
|
@ -80,10 +80,7 @@
|
|||
<li><button class="shape" id="shape85"><strong>xxx</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape86"><strong>xxx</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape87"><strong>xxx</strong> paths</button></li> -->
|
||||
<li><button class="shape" id="shape54">Image (<strong>1</strong> path)</button></li>
|
||||
<li><button class="shape" id="shape74">Gradient (<strong>1</strong> path)</button></li>
|
||||
<li><button class="shape" id="shape66">Gradient (<strong>1</strong> path)</button></li>
|
||||
<li><button class="shape" id="shape75">Gradient (<strong>1</strong> path)</button></li>
|
||||
|
||||
<li><button class="shape" id="shape25"><strong>36</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape36"><strong>41</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape58"><strong>54</strong> paths</button></li>
|
||||
|
|
@ -153,8 +150,17 @@
|
|||
<li><button class="shape" id="shape44"><strong>22375</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape72"><strong>29303</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape48"><strong>41787</strong> paths</button></li>
|
||||
<li><button class="shape" id="shape103"><strong>xxx</strong> paths</button></li>
|
||||
<!-- <li><button class="shape" id="shape100"><strong>x</strong> paths</button></li> -->
|
||||
|
||||
<li><button class="shape" id="shape54">Image</button></li>
|
||||
<li><button class="shape" id="shape74">Gradient 1</button></li>
|
||||
<li><button class="shape" id="shape66">Gradient 2</button></li>
|
||||
<li><button class="shape" id="shape75">Gradient 3</button></li>
|
||||
<li><button class="shape" id="shape104">Arc(s) 1</button></li>
|
||||
<li><button class="shape" id="shape105">Arc(s) 2</button></li>
|
||||
<li><button class="shape" id="shape106">Arc(s) 3</button></li>
|
||||
<li><button class="shape" id="shape107">Arc(s) 4</button></li>
|
||||
|
||||
<!-- <li><button class="shape" id="shape103"><strong>xxx</strong> paths</button></li> -->
|
||||
</ul>
|
||||
|
||||
<ul>
|
||||
|
|
|
|||
140
dist/all.js
vendored
140
dist/all.js
vendored
|
|
@ -8434,6 +8434,111 @@ fabric.util.animate = animate;
|
|||
|
||||
(function(global) {
|
||||
|
||||
function drawArc(ctx, x, y, coords) {
|
||||
var rx = coords[0];
|
||||
var ry = coords[1];
|
||||
var rot = coords[2];
|
||||
var large = coords[3];
|
||||
var sweep = coords[4];
|
||||
var ex = coords[5];
|
||||
var ey = coords[6];
|
||||
var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
|
||||
for (var i=0; i<segs.length; i++) {
|
||||
var bez = segmentToBezier.apply(this, segs[i]);
|
||||
ctx.bezierCurveTo.apply(ctx, bez);
|
||||
}
|
||||
}
|
||||
|
||||
var arcToSegmentsCache = { },
|
||||
segmentToBezierCache = { },
|
||||
_join = Array.prototype.join,
|
||||
argsString;
|
||||
|
||||
function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
|
||||
argsString = _join.call(arguments);
|
||||
if (arcToSegmentsCache[argsString]) {
|
||||
return arcToSegmentsCache[argsString];
|
||||
}
|
||||
|
||||
var th = rotateX * (Math.PI/180);
|
||||
var sin_th = Math.sin(th);
|
||||
var cos_th = Math.cos(th);
|
||||
rx = Math.abs(rx);
|
||||
ry = Math.abs(ry);
|
||||
var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
|
||||
var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
|
||||
var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry);
|
||||
if (pl > 1) {
|
||||
pl = Math.sqrt(pl);
|
||||
rx *= pl;
|
||||
ry *= pl;
|
||||
}
|
||||
|
||||
var a00 = cos_th / rx;
|
||||
var a01 = sin_th / rx;
|
||||
var a10 = (-sin_th) / ry;
|
||||
var a11 = (cos_th) / ry;
|
||||
var x0 = a00 * ox + a01 * oy;
|
||||
var y0 = a10 * ox + a11 * oy;
|
||||
var x1 = a00 * x + a01 * y;
|
||||
var y1 = a10 * x + a11 * y;
|
||||
|
||||
var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0);
|
||||
var sfactor_sq = 1 / d - 0.25;
|
||||
if (sfactor_sq < 0) sfactor_sq = 0;
|
||||
var sfactor = Math.sqrt(sfactor_sq);
|
||||
if (sweep == large) sfactor = -sfactor;
|
||||
var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0);
|
||||
var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0);
|
||||
|
||||
var th0 = Math.atan2(y0-yc, x0-xc);
|
||||
var th1 = Math.atan2(y1-yc, x1-xc);
|
||||
|
||||
var th_arc = th1-th0;
|
||||
if (th_arc < 0 && sweep == 1){
|
||||
th_arc += 2*Math.PI;
|
||||
} else if (th_arc > 0 && sweep == 0) {
|
||||
th_arc -= 2 * Math.PI;
|
||||
}
|
||||
|
||||
var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
|
||||
var result = [];
|
||||
for (var i=0; i<segments; i++) {
|
||||
var th2 = th0 + i * th_arc / segments;
|
||||
var th3 = th0 + (i+1) * th_arc / segments;
|
||||
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
|
||||
}
|
||||
|
||||
return (arcToSegmentsCache[argsString] = result);
|
||||
}
|
||||
|
||||
function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
|
||||
argsString = _join.call(arguments);
|
||||
if (segmentToBezierCache[argsString]) {
|
||||
return segmentToBezierCache[argsString];
|
||||
}
|
||||
|
||||
var a00 = cos_th * rx;
|
||||
var a01 = -sin_th * ry;
|
||||
var a10 = sin_th * rx;
|
||||
var a11 = cos_th * ry;
|
||||
|
||||
var th_half = 0.5 * (th1 - th0);
|
||||
var t = (8/3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half);
|
||||
var x1 = cx + Math.cos(th0) - t * Math.sin(th0);
|
||||
var y1 = cy + Math.sin(th0) + t * Math.cos(th0);
|
||||
var x3 = cx + Math.cos(th1);
|
||||
var y3 = cy + Math.sin(th1);
|
||||
var x2 = x3 + t * Math.sin(th1);
|
||||
var y2 = y3 - t * Math.cos(th1);
|
||||
|
||||
return (segmentToBezierCache[argsString] = [
|
||||
a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
|
||||
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
|
||||
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
|
||||
]);
|
||||
}
|
||||
|
||||
"use strict";
|
||||
|
||||
var fabric = global.fabric || (global.fabric = { }),
|
||||
|
|
@ -8707,9 +8812,31 @@ fabric.util.animate = animate;
|
|||
break;
|
||||
|
||||
case 'a':
|
||||
drawArc(ctx, x + l, y + t, [
|
||||
current[1],
|
||||
current[2],
|
||||
current[3],
|
||||
current[4],
|
||||
current[5],
|
||||
current[6] + x + l,
|
||||
current[7] + y + t
|
||||
]);
|
||||
x += current[6];
|
||||
y += current[7];
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
drawArc(ctx, x + l, y + t, [
|
||||
current[1],
|
||||
current[2],
|
||||
current[3],
|
||||
current[4],
|
||||
current[5],
|
||||
current[6] + l,
|
||||
current[7] + t
|
||||
]);
|
||||
x = current[6];
|
||||
y = current[7];
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
|
|
@ -8824,17 +8951,22 @@ fabric.util.animate = animate;
|
|||
_parsePath: function() {
|
||||
var result = [ ],
|
||||
currentPath,
|
||||
chunks;
|
||||
chunks,
|
||||
parsed;
|
||||
|
||||
for (var i = 0, j, chunksParsed, len = this.path.length; i < len; i++) {
|
||||
currentPath = this.path[i];
|
||||
chunks = currentPath.slice(1).trim().replace(/(\d)-/g, '$1###-').split(/\s|,|###/);
|
||||
j = chunks.length, chunksParsed = [ currentPath.charAt(0) ];
|
||||
while (j--) {
|
||||
chunksParsed[j+1] = parseFloat(chunks[j]);
|
||||
chunksParsed = [ currentPath.charAt(0) ];
|
||||
for (var j = 0, jlen = chunks.length; j < jlen; j++) {
|
||||
parsed = parseFloat(chunks[j]);
|
||||
if (!isNaN(parsed)) {
|
||||
chunksParsed.push(parsed);
|
||||
}
|
||||
}
|
||||
result.push(chunksParsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*! Fabric.js Copyright 2008-2011, Bitsonnet (Juriy Zaytsev, Maxim Chernyak) */
|
||||
|
||||
var fabric = fabric || { version: "0.3.2" };
|
||||
var fabric = fabric || { version: "0.3.3" };
|
||||
|
||||
/**
|
||||
* Wrapper around `console.log` (when available)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,112 @@
|
|||
|
||||
(function(global) {
|
||||
|
||||
function drawArc(ctx, x, y, coords) {
|
||||
var rx = coords[0];
|
||||
var ry = coords[1];
|
||||
var rot = coords[2];
|
||||
var large = coords[3];
|
||||
var sweep = coords[4];
|
||||
var ex = coords[5];
|
||||
var ey = coords[6];
|
||||
var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
|
||||
for (var i=0; i<segs.length; i++) {
|
||||
var bez = segmentToBezier.apply(this, segs[i]);
|
||||
ctx.bezierCurveTo.apply(ctx, bez);
|
||||
}
|
||||
}
|
||||
|
||||
var arcToSegmentsCache = { },
|
||||
segmentToBezierCache = { },
|
||||
_join = Array.prototype.join,
|
||||
argsString;
|
||||
|
||||
// Copied from Inkscape svgtopdf, thanks!
|
||||
function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
|
||||
argsString = _join.call(arguments);
|
||||
if (arcToSegmentsCache[argsString]) {
|
||||
return arcToSegmentsCache[argsString];
|
||||
}
|
||||
|
||||
var th = rotateX * (Math.PI/180);
|
||||
var sin_th = Math.sin(th);
|
||||
var cos_th = Math.cos(th);
|
||||
rx = Math.abs(rx);
|
||||
ry = Math.abs(ry);
|
||||
var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
|
||||
var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
|
||||
var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry);
|
||||
if (pl > 1) {
|
||||
pl = Math.sqrt(pl);
|
||||
rx *= pl;
|
||||
ry *= pl;
|
||||
}
|
||||
|
||||
var a00 = cos_th / rx;
|
||||
var a01 = sin_th / rx;
|
||||
var a10 = (-sin_th) / ry;
|
||||
var a11 = (cos_th) / ry;
|
||||
var x0 = a00 * ox + a01 * oy;
|
||||
var y0 = a10 * ox + a11 * oy;
|
||||
var x1 = a00 * x + a01 * y;
|
||||
var y1 = a10 * x + a11 * y;
|
||||
|
||||
var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0);
|
||||
var sfactor_sq = 1 / d - 0.25;
|
||||
if (sfactor_sq < 0) sfactor_sq = 0;
|
||||
var sfactor = Math.sqrt(sfactor_sq);
|
||||
if (sweep == large) sfactor = -sfactor;
|
||||
var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0);
|
||||
var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0);
|
||||
|
||||
var th0 = Math.atan2(y0-yc, x0-xc);
|
||||
var th1 = Math.atan2(y1-yc, x1-xc);
|
||||
|
||||
var th_arc = th1-th0;
|
||||
if (th_arc < 0 && sweep == 1){
|
||||
th_arc += 2*Math.PI;
|
||||
} else if (th_arc > 0 && sweep == 0) {
|
||||
th_arc -= 2 * Math.PI;
|
||||
}
|
||||
|
||||
var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
|
||||
var result = [];
|
||||
for (var i=0; i<segments; i++) {
|
||||
var th2 = th0 + i * th_arc / segments;
|
||||
var th3 = th0 + (i+1) * th_arc / segments;
|
||||
result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
|
||||
}
|
||||
|
||||
return (arcToSegmentsCache[argsString] = result);
|
||||
}
|
||||
|
||||
function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
|
||||
argsString = _join.call(arguments);
|
||||
if (segmentToBezierCache[argsString]) {
|
||||
return segmentToBezierCache[argsString];
|
||||
}
|
||||
|
||||
var a00 = cos_th * rx;
|
||||
var a01 = -sin_th * ry;
|
||||
var a10 = sin_th * rx;
|
||||
var a11 = cos_th * ry;
|
||||
|
||||
var th_half = 0.5 * (th1 - th0);
|
||||
var t = (8/3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half);
|
||||
var x1 = cx + Math.cos(th0) - t * Math.sin(th0);
|
||||
var y1 = cy + Math.sin(th0) + t * Math.cos(th0);
|
||||
var x3 = cx + Math.cos(th1);
|
||||
var y3 = cy + Math.sin(th1);
|
||||
var x2 = x3 + t * Math.sin(th1);
|
||||
var y2 = y3 - t * Math.cos(th1);
|
||||
|
||||
return (segmentToBezierCache[argsString] = [
|
||||
a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
|
||||
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
|
||||
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
|
||||
]);
|
||||
}
|
||||
|
||||
"use strict";
|
||||
|
||||
var fabric = global.fabric || (global.fabric = { }),
|
||||
|
|
@ -280,11 +386,33 @@
|
|||
break;
|
||||
|
||||
case 'a':
|
||||
// TODO (kangax): implement arc (relative)
|
||||
// TODO: optimize this
|
||||
drawArc(ctx, x + l, y + t, [
|
||||
current[1],
|
||||
current[2],
|
||||
current[3],
|
||||
current[4],
|
||||
current[5],
|
||||
current[6] + x + l,
|
||||
current[7] + y + t
|
||||
]);
|
||||
x += current[6];
|
||||
y += current[7];
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
// TODO (kangax): implement arc (absolute)
|
||||
// TODO: optimize this
|
||||
drawArc(ctx, x + l, y + t, [
|
||||
current[1],
|
||||
current[2],
|
||||
current[3],
|
||||
current[4],
|
||||
current[5],
|
||||
current[6] + l,
|
||||
current[7] + t
|
||||
]);
|
||||
x = current[6];
|
||||
y = current[7];
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
|
|
@ -400,19 +528,24 @@
|
|||
_parsePath: function() {
|
||||
var result = [ ],
|
||||
currentPath,
|
||||
chunks;
|
||||
chunks,
|
||||
parsed;
|
||||
|
||||
// use plain loop for performance reasons.
|
||||
// this chunk of code can be called thousands of times per second (when parsing large shapes)
|
||||
for (var i = 0, j, chunksParsed, len = this.path.length; i < len; i++) {
|
||||
currentPath = this.path[i];
|
||||
chunks = currentPath.slice(1).trim().replace(/(\d)-/g, '$1###-').split(/\s|,|###/);
|
||||
j = chunks.length, chunksParsed = [ currentPath.charAt(0) ];
|
||||
while (j--) {
|
||||
chunksParsed[j+1] = parseFloat(chunks[j]);
|
||||
chunksParsed = [ currentPath.charAt(0) ];
|
||||
for (var j = 0, jlen = chunks.length; j < jlen; j++) {
|
||||
parsed = parseFloat(chunks[j]);
|
||||
if (!isNaN(parsed)) {
|
||||
chunksParsed.push(parsed);
|
||||
}
|
||||
}
|
||||
result.push(chunksParsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue