Skip to content

Commit

Permalink
Paint order (#4303)
Browse files Browse the repository at this point in the history
* makes fabric aware of the paint-order svg2 spec to swap the paint order of stroke and fill
  • Loading branch information
stefanhayden authored and asturur committed Sep 17, 2017
1 parent ddb4027 commit 0fc7134
Show file tree
Hide file tree
Showing 27 changed files with 117 additions and 34 deletions.
2 changes: 1 addition & 1 deletion HEADER.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fabric.SHARED_ATTRIBUTES = [
"stroke", "stroke-dasharray", "stroke-linecap",
"stroke-linejoin", "stroke-miterlimit",
"stroke-opacity", "stroke-width",
"id",
"id", "paint-order",
"instantiated_by_use"
];
/* _FROM_SVG_END_ */
Expand Down
4 changes: 4 additions & 0 deletions src/mixins/object.svg_export.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@
markup.push(this.shadow.toSVG(this));
}
return markup;
},

addPaintOrder: function() {
return this.paintFirst !== 'fill' ? ' paint-order="' + this.paintFirst + '" ' : '';
}
});
})();
Expand Down
12 changes: 12 additions & 0 deletions src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'font-size': 'fontSize',
'font-style': 'fontStyle',
'font-weight': 'fontWeight',
'paint-order': 'paintFirst',
'stroke-dasharray': 'strokeDashArray',
'stroke-linecap': 'strokeLineCap',
'stroke-linejoin': 'strokeLineJoin',
Expand Down Expand Up @@ -109,6 +110,17 @@
else if (attr === 'textAnchor' /* text-anchor */) {
value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center';
}
else if (attr === 'paintFirst') {
var fillIndex = value.indexOf('fill');
var strokeIndex = value.indexOf('stroke');
var value = 'fill';
if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) {
value = 'stroke';
}
else if (fillIndex === -1 && strokeIndex > -1) {
value = 'stroke';
}
}
else {
parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize);
}
Expand Down
6 changes: 3 additions & 3 deletions src/shapes/circle.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@
*/
_render: function(ctx) {
ctx.beginPath();
ctx.arc(0,
ctx.arc(
0,
0,
this.radius,
this.startAngle,
this.endAngle, false);
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},

/**
Expand Down
8 changes: 4 additions & 4 deletions src/shapes/ellipse.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@
'" ry="', this.ry,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
this.getSvgTransformMatrix(),
'"/>\n'
this.getSvgTransformMatrix(), '"',
this.addPaintOrder(),
'/>\n'
);

return reviver ? reviver(markup.join('')) : markup.join('');
Expand All @@ -148,8 +149,7 @@
piBy2,
false);
ctx.restore();
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
});

Expand Down
28 changes: 27 additions & 1 deletion src/shapes/object.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,13 @@
*/
__corner: 0,

/**
* Determins if the fill or the stroke is drawn first (one of "fill" or "stroke")
* @type String
* @default
*/
paintFirst: 'fill',

/**
* List of properties to consider when checking if state
* of an object is changed (fabric.Object#hasStateChanged)
Expand All @@ -565,7 +572,7 @@
'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' +
'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' +
'angle opacity fill globalCompositeOperation shadow clipTo visible backgroundColor ' +
'skewX skewY fillRule'
'skewX skewY fillRule paintFirst'
).split(' '),

/**
Expand Down Expand Up @@ -787,6 +794,7 @@
clipTo: this.clipTo && String(this.clipTo),
backgroundColor: this.backgroundColor,
fillRule: this.fillRule,
paintFirst: this.paintFirst,
globalCompositeOperation: this.globalCompositeOperation,
transformMatrix: this.transformMatrix ? this.transformMatrix.concat() : null,
skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS),
Expand Down Expand Up @@ -997,6 +1005,9 @@
* @returns false
*/
needsItsOwnCache: function() {
if (this.paintFirst === 'stroke' && typeof this.shadow === 'object') {
return true;
}
return false;
},

Expand Down Expand Up @@ -1234,6 +1245,21 @@
return { offsetX: offsetX, offsetY: offsetY };
},

/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderPaintInOrder: function(ctx) {
if (this.paintFirst === 'stroke') {
this._renderStroke(ctx);
this._renderFill(ctx);
}
else {
this._renderFill(ctx);
this._renderStroke(ctx);
}
},

/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to render on
Expand Down
4 changes: 2 additions & 2 deletions src/shapes/path.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,7 @@
*/
_render: function(ctx) {
this._renderPathCommands(ctx);
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},

/**
Expand Down Expand Up @@ -491,6 +490,7 @@
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(), addTransform,
this.getSvgTransformMatrix(), '" stroke-linecap="round" ',
this.addPaintOrder(),
'/>\n'
);

Expand Down
3 changes: 1 addition & 2 deletions src/shapes/polygon.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@
return;
}
ctx.closePath();
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},

/**
Expand Down
8 changes: 4 additions & 4 deletions src/shapes/polyline.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@
'points="', points.join(''),
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
' ', this.getSvgTransformMatrix(),
'"/>\n'
' ', this.getSvgTransformMatrix(), '"',
this.addPaintOrder(),
'/>\n'
);

return reviver ? reviver(markup.join('')) : markup.join('');
Expand Down Expand Up @@ -179,8 +180,7 @@
if (!this.commonRender(ctx)) {
return;
}
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},

/**
Expand Down
8 changes: 4 additions & 4 deletions src/shapes/rect.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@

ctx.closePath();

this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},

/**
Expand Down Expand Up @@ -163,8 +162,9 @@
'" width="', this.width, '" height="', this.height,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
this.getSvgTransformMatrix(),
'"/>\n');
this.getSvgTransformMatrix(), '"',
this.addPaintOrder(),
'/>\n');

return reviver ? reviver(markup.join('')) : markup.join('');
},
Expand Down
10 changes: 8 additions & 2 deletions src/shapes/text.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,14 @@
* @param {CanvasRenderingContext2D} ctx Context to render on
*/
_renderText: function(ctx) {
this._renderTextFill(ctx);
this._renderTextStroke(ctx);
if (this.paintFirst === 'stroke') {
this._renderTextStroke(ctx);
this._renderTextFill(ctx);
}
else {
this._renderTextFill(ctx);
this._renderTextStroke(ctx);
}
},

/**
Expand Down
8 changes: 4 additions & 4 deletions src/shapes/triangle.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
ctx.lineTo(widthBy2, heightBy2);
ctx.closePath();

this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},

/**
Expand Down Expand Up @@ -90,8 +89,9 @@
'<polygon ', this.getSvgId(),
'points="', points,
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(),
'"/>'
'" transform="', this.getSvgTransform(), '"',
this.addPaintOrder(),
'/>'
);

return reviver ? reviver(markup.join('')) : markup.join('');
Expand Down
1 change: 1 addition & 0 deletions test/unit/activeselection.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
'flipY': false,
'opacity': 1,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,
Expand Down
4 changes: 2 additions & 2 deletions test/unit/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@
var PATH_DATALESS_JSON = '{"version":"' + fabric.version + '","objects":[{"type":"path","version":"' + fabric.version + '","originX":"left","originY":"top","left":100,"top":100,"width":200,"height":200,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,' +
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"sourcePath":"http://example.com/"}]}';
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"sourcePath":"http://example.com/"}]}';

var RECT_JSON = '{"version":"' + fabric.version + '","objects":[{"type":"rect","version":"' + fabric.version + '","originX":"left","originY":"top","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,' +
'"shadow":null,' +
'"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0}],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}';
'"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0}],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}';

function _createImageElement() {
return fabric.isLikelyNode ? new (require(fabric.canvasModule).Image)() : fabric.document.createElement('img');
Expand Down
7 changes: 4 additions & 3 deletions test/unit/canvas_static.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,17 @@
var PATH_DATALESS_JSON = '{"version":"' + fabric.version + '","objects":[{"type":"path","version":"' + fabric.version + '","originX":"left","originY":"top","left":100,"top":100,"width":200,"height":200,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,' +
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"sourcePath":"http://example.com/"}]}';
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"sourcePath":"http://example.com/"}]}';

var RECT_JSON = '{"version":"' + fabric.version + '","objects":[{"type":"rect","version":"' + fabric.version + '","originX":"left","originY":"top","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,' +
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0}],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}';
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0}],"background":"#ff5555","overlay":"rgba(0,0,0,0.2)"}';

var RECT_JSON_WITH_PADDING = '{"version":"' + fabric.version + '","objects":[{"type":"rect","version":"' + fabric.version + '","originX":"left","originY":"top","left":0,"top":0,"width":10,"height":20,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,' +
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0,"padding":123,"foo":"bar"}]}';
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"rx":0,"ry":0,"padding":123,"foo":"bar"}]}';

function getAbsolutePath(path) {
var isAbsolute = /^https?:/.test(path);
Expand Down Expand Up @@ -98,6 +98,7 @@
'clipTo': null,
'filters': [],
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'crossOrigin': '',
Expand Down
1 change: 1 addition & 0 deletions test/unit/circle.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'radius': 0,
'startAngle': 0,
Expand Down
1 change: 1 addition & 0 deletions test/unit/ellipse.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
'visible': true,
'backgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'clipTo': null,
'transformMatrix': null
Expand Down
1 change: 1 addition & 0 deletions test/unit/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
'flipY': false,
'opacity': 1,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,
Expand Down
1 change: 1 addition & 0 deletions test/unit/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
'clipTo': null,
'filters': [],
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
Expand Down
1 change: 1 addition & 0 deletions test/unit/itext.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
'backgroundColor': '',
'textBackgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
Expand Down
1 change: 1 addition & 0 deletions test/unit/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
'clipTo': null,
'backgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
Expand Down
6 changes: 4 additions & 2 deletions test/unit/object.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@
var emptyObjectJSON = '{"type":"object","version":"' + fabric.version + '","originX":"left","originY":"top","left":0,"top":0,"width":0,"height":0,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,' +
'"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over",' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over",' +
'"transformMatrix":null,"skewX":0,"skewY":0}';

var augmentedJSON = '{"type":"object","version":"' + fabric.version + '","originX":"left","originY":"top","left":0,"top":0,"width":122,"height":0,"fill":"rgb(0,0,0)",' +
'"stroke":null,"strokeWidth":1,"strokeDashArray":[5,2],"strokeLineCap":"round","strokeLineJoin":"bevil","strokeMiterLimit":5,' +
'"scaleX":1.3,"scaleY":1,"angle":0,"flipX":false,"flipY":true,"opacity":0.88,' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","globalCompositeOperation":"source-over",' +
'"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over",' +
'"transformMatrix":null,"skewX":0,"skewY":0}';

var cObj = new fabric.Object();
Expand Down Expand Up @@ -203,6 +203,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
Expand Down Expand Up @@ -236,6 +237,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,
Expand Down
1 change: 1 addition & 0 deletions test/unit/path.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
Expand Down
Loading

0 comments on commit 0fc7134

Please sign in to comment.