Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
23 changes: 16 additions & 7 deletions src/shapes/polyline.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,24 @@
_setPositionDimensions: function(options) {
options || (options = {});
var calcDim = this._calcDimensions(options), correctLeftTop,
correctSize = this.exactBoundingBox ? this.strokeWidth : 0;
this.width = calcDim.width - correctSize;
this.height = calcDim.height - correctSize;
correctSizeX = this.exactBoundingBox
? this.strokeUniform
? this.strokeWidth/this.scaleX
: this.strokeWidth
: 0,
correctSizeY = this.exactBoundingBox
? this.strokeUniform
? this.strokeWidth/this.scaleY
: this.strokeWidth
: 0;
this.width = calcDim.width - correctSizeX;
this.height = calcDim.height - correctSizeY;
if (!options.fromSVG) {
correctLeftTop = this.translateToGivenOrigin(
{
// this looks bad, but is one way to keep it optional for now.
x: calcDim.left - this.strokeWidth / 2 + correctSize / 2,
y: calcDim.top - this.strokeWidth / 2 + correctSize / 2
x: calcDim.left - this.strokeWidth / 2 + correctSizeX / 2,
y: calcDim.top - this.strokeWidth / 2 + correctSizeY / 2
},
'left',
'top',
Expand All @@ -108,8 +117,8 @@
this.top = options.fromSVG ? calcDim.top : correctLeftTop.y;
}
this.pathOffset = {
x: calcDim.left + this.width / 2 + correctSize / 2,
y: calcDim.top + this.height / 2 + correctSize / 2
x: calcDim.left + this.width / 2 + correctSizeX / 2,
y: calcDim.top + this.height / 2 + correctSizeY / 2
};
},

Expand Down
211 changes: 183 additions & 28 deletions src/util/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
* @namespace fabric.util
*/
fabric.util = {

/**
* Calculate the cos of an angle, avoiding returning floats for known results
* @static
Expand Down Expand Up @@ -158,25 +157,29 @@
},

/**
* Calculates angle between 2 vectors using dot product
* Calculates angle between 2 vectors using atan2
* @static
* @memberOf fabric.util
* @param {Point} a
* @param {Point} b
* @returns the angle in radian between the vectors
*/
calcAngleBetweenVectors: function (a, b) {
return Math.acos((a.x * b.x + a.y * b.y) / (Math.hypot(a.x, a.y) * Math.hypot(b.x, b.y)));
var dot = a.x * b.x + a.y * b.y,
det = a.x * b.y - a.y * b.x;

return Math.atan2(det, dot);
},

/**
* @static
* @memberOf fabric.util
* @param {Point} v
* @returns {Point} vector representing the unit vector of pointing to the direction of `v`
* @returns {Point} vector representing the unit vector pointing to the direction of `v`
*/
getHatVector: function (v) {
return new fabric.Point(v.x, v.y).scalarMultiply(1 / Math.hypot(v.x, v.y));
var hypot = Math.sqrt(v.x * v.x + v.y * v.y);
return new fabric.Point(v.x, v.y).scalarMultiply(1 / hypot);
},

/**
Expand All @@ -190,21 +193,38 @@
getBisector: function (A, B, C) {
var AB = fabric.util.createVector(A, B), AC = fabric.util.createVector(A, C);
var alpha = fabric.util.calcAngleBetweenVectors(AB, AC);
// check if alpha is relative to AB->BC
var ro = fabric.util.calcAngleBetweenVectors(fabric.util.rotateVector(AB, alpha), AC);
var phi = alpha * (ro === 0 ? 1 : -1) / 2;
var phi = alpha / 2;
return {
vector: fabric.util.getHatVector(fabric.util.rotateVector(AB, phi)),
angle: alpha
angle: Math.abs(alpha)
};
},

/**
* Project stroke width on points returning 2 projections for each point as follows:
* @static
* @memberOf fabric.util
* @param {Point} vector
* @param {Boolean} counterClockwise the direction of the orthogonal vector
* @returns {Point} the unit orthogonal vector
*/
getOrthogonalUnitVector: function(vector, counterClockwise = true) {
return fabric.util.getHatVector(
new fabric.Point(
counterClockwise ? -vector.y : vector.y,
counterClockwise ? vector.x : -vector.x
)
)
},

/**
* Project stroke width on points returning projections for each point as follows:
* - `miter`: 2 points corresponding to the outer boundary and the inner boundary of stroke.
* - `bevel`: 2 points corresponding to the bevel boundaries, tangent to the bisector.
* - `bevel`: 4 points corresponding to the bevel possible boundaries, orthogonal to the stroke.
* - `round`: same as `bevel`
* Used to calculate object's bounding box
*
* @see https://github.com/fabricjs/fabric.js/pull/8083
*
* @static
* @memberOf fabric.util
* @param {Point[]} points
Expand All @@ -221,50 +241,185 @@
projectStrokeOnPoints: function (points, options, openPath) {
var coords = [], s = options.strokeWidth / 2,
strokeUniformScalar = options.strokeUniform ?
new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1),
getStrokeHatVector = function (v) {
var scalar = s / (Math.hypot(v.x, v.y));
return new fabric.Point(v.x * scalar * strokeUniformScalar.x, v.y * scalar * strokeUniformScalar.y);
};
new fabric.Point(1 / options.scaleX, 1 / options.scaleY) : new fabric.Point(1, 1);

if (points.length <= 1) {return coords;}

points.forEach(function (p, index) {
var A = new fabric.Point(p.x, p.y), B, C;
if (index === 0) {
C = points[index + 1];
B = openPath ? getStrokeHatVector(fabric.util.createVector(C, A)).addEquals(A) : points[points.length - 1];
B = openPath ? A : points[points.length - 1];
}
else if (index === points.length - 1) {
B = points[index - 1];
C = openPath ? getStrokeHatVector(fabric.util.createVector(B, A)).addEquals(A) : points[0];
C = openPath ? A : points[0];
}
else {
B = points[index - 1];
C = points[index + 1];
}
var bisector = fabric.util.getBisector(A, B, C),
bisectorVector = bisector.vector,

if (openPath && index === 0) {
var scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY),
scaledC = new fabric.Point(C.x * options.scaleX, C.y * options.scaleY);

var vector = fabric.util.createVector(
options.strokeUniform ? scaledA : A,
options.strokeUniform ? scaledC : C
),
hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector),
orthogonalVector = new fabric.Point(
hatOrthogonalVector.x * s * strokeUniformScalar.x,
hatOrthogonalVector.y * s * strokeUniformScalar.y
);

coords.push(A.add(orthogonalVector));
coords.push(A.subtract(orthogonalVector));
return;
}

if (openPath && index === points.length - 1) {
var scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY),
scaledB = new fabric.Point(B.x * options.scaleX, B.y * options.scaleY);

var vector = fabric.util.createVector(
options.strokeUniform ? scaledA : A,
options.strokeUniform ? scaledB : B
),
hatOrthogonalVector = fabric.util.getOrthogonalUnitVector(vector),
orthogonalVector = new fabric.Point(
hatOrthogonalVector.x * s * strokeUniformScalar.x,
hatOrthogonalVector.y * s * strokeUniformScalar.y
);

coords.push(A.add(orthogonalVector));
coords.push(A.subtract(orthogonalVector));
return;
}

var bisector,
scaledA,
scaledB,
scaledC;
if (options.strokeUniform) {
scaledA = new fabric.Point(A.x * options.scaleX, A.y * options.scaleY);
scaledB = new fabric.Point(B.x * options.scaleX, B.y * options.scaleY);
scaledC = new fabric.Point(C.x * options.scaleX, C.y * options.scaleY);

bisector = fabric.util.getBisector(scaledA, scaledB, scaledC);
} else {
bisector = fabric.util.getBisector(A, B, C);
}

var bisectorVector = bisector.vector,
alpha = bisector.angle,
scalar,
miterVector;

if (options.strokeLineJoin === 'miter') {
scalar = -s / Math.sin(alpha / 2);

miterVector = new fabric.Point(
bisectorVector.x * scalar * strokeUniformScalar.x,
bisectorVector.y * scalar * strokeUniformScalar.y
);
if (Math.hypot(miterVector.x, miterVector.y) / s <= options.strokeMiterLimit) {

var strokeMiterLimit;
if (options.strokeUniform) {
var miterLimitLenght = options.strokeMiterLimit * s,
miterLimitVector = new fabric.Point(
bisectorVector.x * miterLimitLenght * strokeUniformScalar.x,
bisectorVector.y * miterLimitLenght * strokeUniformScalar.y
);
strokeMiterLimit = Math.hypot(miterLimitVector.x, miterLimitVector.y) / s;
strokeMiterLimit = Math.sqrt(miterLimitVector.x * miterLimitVector.x + miterLimitVector.y * miterLimitVector.y) / s;
} else {
strokeMiterLimit = options.strokeMiterLimit;
}

if (Math.sqrt(miterVector.x * miterVector.x + miterVector.y * miterVector.y) / s <= strokeMiterLimit) {
coords.push(A.add(miterVector));
coords.push(A.subtract(miterVector));
return;
}
}

if (options.strokeLineJoin === 'bevel' || options.strokeLineJoin === 'miter') { // miter greater than stroke miter limit

var AB = fabric.util.createVector(
options.strokeUniform ? scaledA : A,
options.strokeUniform ? scaledB : B
),
hatOrthogonalAB = fabric.util.getOrthogonalUnitVector(AB),
orthogonalAB = new fabric.Point(
hatOrthogonalAB.x * s * strokeUniformScalar.x,
hatOrthogonalAB.y * s * strokeUniformScalar.y
);

var AC = fabric.util.createVector(
options.strokeUniform ? scaledA : A,
options.strokeUniform ? scaledC : C
),
hatOrthogonalAC = fabric.util.getOrthogonalUnitVector(AC),
orthogonalAC = new fabric.Point(
hatOrthogonalAC.x * s * strokeUniformScalar.x,
hatOrthogonalAC.y * s * strokeUniformScalar.y
);


coords.push(A.add(orthogonalAB));
coords.push(A.subtract(orthogonalAB));

coords.push(A.add(orthogonalAC));
coords.push(A.subtract(orthogonalAC));

return;
}

if (options.strokeLineJoin === 'round') {

if (alpha > PiBy2) {
var AB = fabric.util.createVector(
options.strokeUniform ? scaledA : A,
options.strokeUniform ? scaledB : B
),
hatOrthogonalAB = fabric.util.getOrthogonalUnitVector(AB)
orthogonalAB = new fabric.Point(
hatOrthogonalAB.x * s * strokeUniformScalar.x,
hatOrthogonalAB.y * s * strokeUniformScalar.y
);

var AC = fabric.util.createVector(
options.strokeUniform ? scaledA : A,
options.strokeUniform ? scaledC : C
),
hatOrthogonalAC = fabric.util.getOrthogonalUnitVector(AC),
orthogonalAC = new fabric.Point(
hatOrthogonalAC.x * s * strokeUniformScalar.x,
hatOrthogonalAC.y * s * strokeUniformScalar.y
)

coords.push(A.add(orthogonalAB));
coords.push(A.subtract(orthogonalAB));

coords.push(A.add(orthogonalAC));
coords.push(A.subtract(orthogonalAC));

return;
}

var radiusOnAxisX = new fabric.Point(s * strokeUniformScalar.x, 0),
radiusOnAxisY = new fabric.Point(0, s * strokeUniformScalar.y);

coords.push(A.add(radiusOnAxisX));
coords.push(A.subtract(radiusOnAxisX));

coords.push(A.add(radiusOnAxisY));
coords.push(A.subtract(radiusOnAxisY));

return;
}
scalar = -s * Math.SQRT2;
miterVector = new fabric.Point(
bisectorVector.x * scalar * strokeUniformScalar.x,
bisectorVector.y * scalar * strokeUniformScalar.y
);
coords.push(A.add(miterVector));
coords.push(A.subtract(miterVector));
});
return coords;
},
Expand Down
Loading