From 053e419dbbcea24a15514f15c94439a90d362687 Mon Sep 17 00:00:00 2001 From: Andrea Bogazzi Date: Sun, 17 Feb 2019 23:08:39 +0100 Subject: [PATCH] Refinements to strokeUniform property (#5527) * new Object property strokeUniform allows the stroke width to always stay the same width as object scales * fix for height * Update src/shapes/object.class.js Co-Authored-By: stefanhayden * trying the other function * different transformed function * added a visual test * fixed title * test svg * import value from svg * add svg import test * more tests --- HEADER.js | 2 +- src/mixins/object.svg_export.js | 2 + src/mixins/object_geometry.mixin.js | 40 +++++++++++----- src/parser.js | 4 ++ src/shapes/object.class.js | 4 +- test/unit/header.js | 2 +- test/visual/assets/vector-effect.svg | 6 +++ test/visual/generic_rendering.js | 68 +++++++++++++++++++++++++++ test/visual/golden/generic1.png | Bin 0 -> 377 bytes test/visual/golden/vector-effect.png | Bin 0 -> 423 bytes test/visual/svg_import.js | 1 + 11 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 test/visual/assets/vector-effect.svg create mode 100644 test/visual/generic_rendering.js create mode 100644 test/visual/golden/generic1.png create mode 100644 test/visual/golden/vector-effect.png diff --git a/HEADER.js b/HEADER.js index d447dd87cdf..2c9ab4d1417 100644 --- a/HEADER.js +++ b/HEADER.js @@ -55,7 +55,7 @@ fabric.SHARED_ATTRIBUTES = [ 'stroke', 'stroke-dasharray', 'stroke-linecap', 'stroke-dashoffset', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', - 'id', 'paint-order', + 'id', 'paint-order', 'vector-effect', 'instantiated_by_use', 'clip-path' ]; /* _FROM_SVG_END_ */ diff --git a/src/mixins/object.svg_export.js b/src/mixins/object.svg_export.js index 0d07eb8f09c..110a650c364 100644 --- a/src/mixins/object.svg_export.js +++ b/src/mixins/object.svg_export.js @@ -213,6 +213,7 @@ styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ', shadowInfo = withShadow ? 'style="' + this.getSvgFilter() + '" ' : '', clipPath = this.clipPath, + vectorEffect = this.strokeUniform ? 'vector-effect="non-scaling-stroke" ' : '', absoluteClipPath = this.clipPath && this.clipPath.absolutePositioned, commonPieces, markup = [], clipPathMarkup, // insert commons in the markup, style and svgCommons @@ -237,6 +238,7 @@ ); commonPieces = [ styleInfo, + vectorEffect, noStyle ? '' : this.addPaintOrder(), ' ', additionalTransform ? 'transform="' + additionalTransform + '" ' : '', ].join(''); diff --git a/src/mixins/object_geometry.mixin.js b/src/mixins/object_geometry.mixin.js index 63b7a73b95f..408b8a85099 100644 --- a/src/mixins/object_geometry.mixin.js +++ b/src/mixins/object_geometry.mixin.js @@ -588,18 +588,23 @@ if (typeof skewY === 'undefined') { skewY = this.skewY; } - var dimensions = this._getNonTransformedDimensions(); - if (skewX === 0 && skewY === 0) { - return { x: dimensions.x * this.scaleX, y: dimensions.y * this.scaleY }; - } - var dimX, dimY; + var dimensions = this._getNonTransformedDimensions(), dimX, dimY, + noSkew = skewX === 0 && skewY === 0; + if (this.strokeUniform) { - dimX = this.width / 2; - dimY = this.height / 2; + dimX = this.width; + dimY = this.height; + } + else { + dimX = dimensions.x; + dimY = dimensions.y; + } + if (noSkew) { + return this._finalizeDiemensions(dimX * this.scaleX, dimY * this.scaleY); } else { - dimX = dimensions.x / 2; - dimY = dimensions.y / 2; + dimX /= 2; + dimY /= 2; } var points = [ { @@ -624,12 +629,23 @@ points[i] = fabric.util.transformPoint(points[i], transformMatrix); } bbox = fabric.util.makeBoundingBoxFromPoints(points); + return this._finalizeDiemensions(bbox.width, bbox.height); + }, + + /* + * Calculate object bounding boxdimensions from its properties scale, skew. + * @param Number width width of the bbox + * @param Number height height of the bbox + * @private + * @return {Object} .x finalized width dimension + * @return {Object} .y finalized height dimension + */ + _finalizeDiemensions: function(width, height) { return this.strokeUniform ? - { x: bbox.width + this.strokeWidth, y: bbox.height + this.strokeWidth } + { x: width + this.strokeWidth, y: height + this.strokeWidth } : - { x: bbox.width, y: bbox.height }; + { x: width, y: height }; }, - /* * Calculate object dimensions for controls. include padding and canvas zoom * private diff --git a/src/parser.js b/src/parser.js index 2f0b5da96ac..85d3b17b4db 100644 --- a/src/parser.js +++ b/src/parser.js @@ -49,6 +49,7 @@ opacity: 'opacity', 'clip-path': 'clipPath', 'clip-rule': 'clipRule', + 'vector-effect': 'strokeUniform' }, colorAttributes = { @@ -80,6 +81,9 @@ if ((attr === 'fill' || attr === 'stroke') && value === 'none') { value = ''; } + else if (attr === 'vector-effect') { + value = value === 'non-scaling-stroke'; + } else if (attr === 'strokeDashArray') { if (value === 'none') { value = null; diff --git a/src/shapes/object.class.js b/src/shapes/object.class.js index fe7c1504f87..1abeb3207ff 100644 --- a/src/shapes/object.class.js +++ b/src/shapes/object.class.js @@ -607,7 +607,7 @@ 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + 'stroke strokeWidth strokeDashArray strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit ' + 'angle opacity fill globalCompositeOperation shadow clipTo visible backgroundColor ' + - 'skewX skewY fillRule paintFirst clipPath' + 'skewX skewY fillRule paintFirst clipPath strokeUniform' ).split(' '), /** @@ -618,7 +618,7 @@ * @type Array */ cacheProperties: ( - 'fill stroke strokeWidth strokeDashArray width height paintFirst' + + 'fill stroke strokeWidth strokeDashArray width height paintFirst strokeUniform' + ' strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit backgroundColor clipPath' ).split(' '), diff --git a/test/unit/header.js b/test/unit/header.js index 4f6cfc81ed3..dc6ba4dcded 100644 --- a/test/unit/header.js +++ b/test/unit/header.js @@ -6,7 +6,7 @@ assert.ok(typeof fabric.window !== 'undefined', 'window is set'); assert.ok(typeof fabric.isTouchSupported !== 'undefined', 'isTouchSupported is set'); assert.ok(typeof fabric.isLikelyNode !== 'undefined', 'isLikelyNode is set'); - assert.equal(fabric.SHARED_ATTRIBUTES.length, 18, 'SHARED_ATTRIBUTES is set'); + assert.equal(fabric.SHARED_ATTRIBUTES.length, 19, 'SHARED_ATTRIBUTES is set'); }); QUnit.test('initFilterBackend', function(assert) { diff --git a/test/visual/assets/vector-effect.svg b/test/visual/assets/vector-effect.svg new file mode 100644 index 00000000000..6f182b10289 --- /dev/null +++ b/test/visual/assets/vector-effect.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/visual/generic_rendering.js b/test/visual/generic_rendering.js new file mode 100644 index 00000000000..3988534a744 --- /dev/null +++ b/test/visual/generic_rendering.js @@ -0,0 +1,68 @@ +(function() { + if (fabric.isLikelyNode) { + if (process.env.launcher === 'Firefox') { + fabric.browserShadowBlurConstant = 0.9; + } + if (process.env.launcher === 'Node') { + fabric.browserShadowBlurConstant = 1; + } + if (process.env.launcher === 'Chrome') { + fabric.browserShadowBlurConstant = 1.5; + } + if (process.env.launcher === 'Edge') { + fabric.browserShadowBlurConstant = 1.75; + } + } + else { + if (navigator.userAgent.indexOf('Firefox') !== -1) { + fabric.browserShadowBlurConstant = 0.9; + } + if (navigator.userAgent.indexOf('Chrome') !== -1) { + fabric.browserShadowBlurConstant = 1.5; + } + if (navigator.userAgent.indexOf('Edge') !== -1) { + fabric.browserShadowBlurConstant = 1.75; + } + } + fabric.enableGLFiltering = false; + fabric.isWebglSupported = false; + fabric.Object.prototype.objectCaching = true; + var visualTestLoop; + if (fabric.isLikelyNode) { + visualTestLoop = global.visualTestLoop; + } + else { + visualTestLoop = window.visualTestLoop; + } + var fabricCanvas = this.canvas = new fabric.Canvas(null, { + enableRetinaScaling: false, renderOnAddRemove: false, width: 200, height: 200, + }); + + var tests = []; + + function generic1(canvas, callback) { + canvas.setDimensions({ width: 150, height: 60 }); + var rect = new fabric.Rect({ + width: 20, height: 40, strokeWidth: 2, scaleX: 6, scaleY: 0.5, strokeUniform: true, + fill: '', stroke: 'red' + }); + var rect2 = new fabric.Rect({ + width: 60, height: 60, top: 4, left: 4, strokeWidth: 2, scaleX: 2, + scaleY: 0.5, strokeUniform: false, fill: '', stroke: 'blue', + }); + canvas.add(rect); + canvas.add(rect2); + canvas.renderAll(); + callback(canvas.lowerCanvasEl); + } + + tests.push({ + test: 'Rect with strokeUniform: true', + code: generic1, + golden: 'generic1.png', + newModule: 'Generic rendering', + percentage: 0.09, + }); + + tests.forEach(visualTestLoop(fabricCanvas, QUnit)); +})(); diff --git a/test/visual/golden/generic1.png b/test/visual/golden/generic1.png new file mode 100644 index 0000000000000000000000000000000000000000..34e131a1a2707bcafa9087c3265f9e143fa37689 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^(}389gAGWwv^i%1DYhhUcNd2LAh=-f^2rPgjC!6f zjv*Cu-rm{h$K)u|cCo&vHNSc3zIw)`dwTCiH0)5+5WC|YbAF}5%{Mul;?taqZW>OG z<(qd%YOnf9k2ouFS>u_<&*#tOzWmJ6)6K|AJhtuYt(^wB+dS(dW}b{Wa`e=l*Z=I| zc8E+|dm?G}uV?%ECe3~06k*GK`El{~UsWRCa%A{ryFdKr=oUJ(sAYqOqK+4K=A32H z&k9Sv)=Wt>>)%ooTt3?;(r>~${%|An)68kd_WfP8W2x__%Qt-P+FaXeA1v|b_wBOl z{5$s+{#zkE^Y+v4GZV$?SdVFLm?!)CkY}~nv$(2v>*HIe2#Be+xN;^2Iz^x{>plhv hrmFB|xO@V)sUY@ZjSSo$r?Q95lL-sTy_Mq*+?LYf{Gt?Gu)q(oQE%KjD6# zcQ0h`%hk8`tt!_o+PbwkynZjkKlMbxLffMM3x9sGeBb|YYfe=7^54#}?^dnSE8Vg3 zRo~}(H};%~yCTJtw&q*6dYHkkH!p9QMJoy(QgVuL>EV6vy@PjObt&87-jN^!)78&qol`;+0D+gAk^lez literal 0 HcmV?d00001 diff --git a/test/visual/svg_import.js b/test/visual/svg_import.js index 8db822e6022..f9d4b844ccb 100644 --- a/test/visual/svg_import.js +++ b/test/visual/svg_import.js @@ -85,6 +85,7 @@ 'clippath-6', 'clippath-7', 'clippath-9', + 'vector-effect' //'clippath-8', ].map(createTestFromSVG);