Paint order (#4303)

* makes fabric aware of the paint-order svg2 spec to swap the paint order of stroke and fill
This commit is contained in:
Stefan Hayden 2017-09-17 16:47:35 -04:00 committed by Andrea Bogazzi
parent ddb402730d
commit 0fc71349a6
27 changed files with 117 additions and 34 deletions

View file

@ -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_ */

View file

@ -195,6 +195,10 @@
markup.push(this.shadow.toSVG(this));
}
return markup;
},
addPaintOrder: function() {
return this.paintFirst !== 'fill' ? ' paint-order="' + this.paintFirst + '" ' : '';
}
});
})();

View file

@ -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',
@ -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);
}

View file

@ -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);
},
/**

View file

@ -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('');
@ -148,8 +149,7 @@
piBy2,
false);
ctx.restore();
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
});

View file

@ -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)
@ -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(' '),
/**
@ -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),
@ -997,6 +1005,9 @@
* @returns false
*/
needsItsOwnCache: function() {
if (this.paintFirst === 'stroke' && typeof this.shadow === 'object') {
return true;
}
return false;
},
@ -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

View file

@ -430,8 +430,7 @@
*/
_render: function(ctx) {
this._renderPathCommands(ctx);
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
/**
@ -491,6 +490,7 @@
'" style="', this.getSvgStyles(),
'" transform="', this.getSvgTransform(), addTransform,
this.getSvgTransformMatrix(), '" stroke-linecap="round" ',
this.addPaintOrder(),
'/>\n'
);

View file

@ -34,8 +34,7 @@
return;
}
ctx.closePath();
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
/**

View file

@ -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('');
@ -179,8 +180,7 @@
if (!this.commonRender(ctx)) {
return;
}
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
/**

View file

@ -117,8 +117,7 @@
ctx.closePath();
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
/**
@ -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('');
},

View file

@ -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);
}
},
/**

View file

@ -50,8 +50,7 @@
ctx.lineTo(widthBy2, heightBy2);
ctx.closePath();
this._renderFill(ctx);
this._renderStroke(ctx);
this._renderPaintInOrder(ctx);
},
/**
@ -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('');

View file

@ -82,6 +82,7 @@
'flipY': false,
'opacity': 1,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,

View file

@ -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');

View file

@ -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);
@ -98,6 +98,7 @@
'clipTo': null,
'filters': [],
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'crossOrigin': '',

View file

@ -105,6 +105,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'radius': 0,
'startAngle': 0,

View file

@ -51,6 +51,7 @@
'visible': true,
'backgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'clipTo': null,
'transformMatrix': null

View file

@ -189,6 +189,7 @@
'flipY': false,
'opacity': 1,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,

View file

@ -43,6 +43,7 @@
'clipTo': null,
'filters': [],
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,

View file

@ -45,6 +45,7 @@
'backgroundColor': '',
'textBackgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,

View file

@ -31,6 +31,7 @@
'clipTo': null,
'backgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,

View file

@ -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();
@ -203,6 +203,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
@ -236,6 +237,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'skewX': 0,

View file

@ -28,6 +28,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,

View file

@ -35,6 +35,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,

View file

@ -35,6 +35,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,

View file

@ -27,6 +27,7 @@
'backgroundColor': '',
'clipTo': null,
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'transformMatrix': null,
'rx': 0,
@ -192,10 +193,30 @@
assert.equal(svg, '<rect x="-50" y="-50" rx="0" ry="0" width="100" height="100" style="stroke: rgb(255,0,0); stroke-opacity: 0.5; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(50 50)"/>\n');
});
QUnit.test('toSVG with paintFirst set to stroke', function(assert) {
var rect = new fabric.Rect({ width: 100, height: 100, paintFirst: 'stroke' });
var svg = rect.toSVG();
assert.equal(svg, '<rect x="-50" y="-50" rx="0" ry="0" width="100" height="100" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform="translate(50.5 50.5)" paint-order="stroke" />\n');
});
QUnit.test('toObject without default values', function(assert) {
var options = { type: 'rect', width: 69, height: 50, left: 10, top: 20, version: fabric.version, };
var rect = new fabric.Rect(options);
rect.includeDefaultValues = false;
assert.deepEqual(rect.toObject(), options);
});
QUnit.test('paintFirst life cycle', function(assert) {
var done = assert.async();
var svg = '<svg><rect x="10" y="10" height="50" width="55" fill="red" stroke="blue" paint-order="stroke" /></svg>';
fabric.loadSVGFromString(svg, function(envlivedObjects) {
var rect = envlivedObjects[0];
var rectObject = rect.toObject();
var rectSvg = rect.toSVG();
assert.equal(rect.paintFirst, 'stroke');
assert.equal(rectObject.paintFirst, 'stroke');
assert.ok(rectSvg.indexOf('paint-order="stroke"') > -1);
done();
});
});
})();

View file

@ -50,6 +50,7 @@
'textAlign': 'left',
'textBackgroundColor': '',
'fillRule': 'nonzero',
'paintFirst': 'fill',
'globalCompositeOperation': 'source-over',
'skewX': 0,
'skewY': 0,
@ -250,6 +251,7 @@
strokeLineJoin: 'bevil',
strokeMiterLimit: 5,
fontFamily: 'Monaco',
paintFirst: 'fill',
fontStyle: 'italic',
fontWeight: 'bold',
fontSize: 123,