diff --git a/src/color.class.js b/src/color.class.js index fb04b17c..b42699ce 100644 --- a/src/color.class.js +++ b/src/color.class.js @@ -33,6 +33,7 @@ /** * @private + * @param {String|Array} color Color value to parse */ _tryParsingColor: function(color) { var source; @@ -46,11 +47,58 @@ if (!source) { source = Color.sourceFromRgb(color); } + if (!source) { + source = Color.sourceFromHsl(color); + } if (source) { this.setSource(source); } }, + /** + * Adapted from https://github.com/mjijackson + * @private + * @param {Number} r Red color value + * @param {Number} g Green color value + * @param {Number} b Blue color value + * @return {Array} Hsl color + */ + _rgbToHsl: function(r, g, b) { + r /= 255, g /= 255, b /= 255; + + var h, s, l, + max = fabric.util.array.max([r, g, b]), + min = fabric.util.array.min([r, g, b]); + + l = (max + min) / 2; + + if (max === min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [ + Math.round(h * 360), + Math.round(s * 100), + Math.round(l * 100) + ]; + }, + /** * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) * @return {Array} @@ -85,6 +133,28 @@ return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; }, + /** + * Returns color represenation in HSL format + * @return {String} ex: hsl(0-360,0%-100%,0%-100%) + */ + toHsl: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; + }, + + /** + * Returns color represenation in HSLA format + * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) + */ + toHsla: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; + }, + /** * Returns color represenation in HEX format * @return {String} ex: FF5555 @@ -114,7 +184,7 @@ /** * Sets value of alpha channel for this color - * @param {Number} 0-1 + * @param {Number} alpha 0-1 * @return {fabric.Color} thisArg */ setAlpha: function(alpha) { @@ -138,6 +208,7 @@ /** * Transforms color to its black and white representation + * @param {Number} threshold * @return {fabric.Color} thisArg */ toBlackWhite: function(threshold) { @@ -179,11 +250,18 @@ }; /** - * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgb(255, 100, 10, 0.5), rgb(1,1,1)) + * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) * @static * @field */ - fabric.Color.reRGBa = /^rgba?\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d+(?:\.\d+)?))?\)$/; + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*,\s*(\d{1,3}\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + + /** + * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) + * @static + * @field + */ + fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; /** * Regex matching color in HEX format (ex: #FF5555, 010155, aff) @@ -216,6 +294,22 @@ 'yellow': '#FFFF00' }; + /** + * @private + * @param {Number} p + * @param {Number} q + * @param {Number} t + * @return {Number} + */ + function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + /** * Returns new color object, when given a color in RGB format * @param {String} color ex: rgb(0-255,0-255,0-255) @@ -227,16 +321,20 @@ /** * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format - * @param {String} color ex: rgb(0-255,0-255,0-255) + * @param {String} color ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) * @return {Array} source */ fabric.Color.sourceFromRgb = function(color) { var match = color.match(Color.reRGBa); if (match) { + var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), + g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), + b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + return [ - parseInt(match[1], 10), - parseInt(match[2], 10), - parseInt(match[3], 10), + parseInt(r, 10), + parseInt(g, 10), + parseInt(b, 10), match[4] ? parseFloat(match[4]) : 1 ]; } @@ -251,6 +349,60 @@ */ fabric.Color.fromRgba = Color.fromRgb; + /** + * Returns new color object, when given a color in HSL format + * @param {String} color ex: hsl(0-260,0%-100%,0%-100%) + * @return {fabric.Color} + */ + fabric.Color.fromHsl = function(color) { + return Color.fromSource(Color.sourceFromHsl(color)); + }; + + /** + * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. + * Adapted from https://github.com/mjijackson + * @param {String} color ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) + * @return {Array} source + * @see http://http://www.w3.org/TR/css3-color/#hsl-color + */ + fabric.Color.sourceFromHsl = function(color) { + var match = color.match(Color.reHSLa); + if (!match) return; + + var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), + r, g, b; + + if (s === 0) { + r = g = b = l; + } + else { + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s; + var p = l * 2 - q; + + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + match[4] ? parseFloat(match[4]) : 1 + ]; + }; + + /** + * Returns new color object, when given a color in HSLA format + * @static + * @function + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromHsla = Color.fromHsl; + /** * Returns new color object, when given a color in HEX format * @static @@ -286,6 +438,7 @@ /** * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) * @static + * @param {Array} source * @return {fabric.Color} */ fabric.Color.fromSource = function(source) { diff --git a/src/image.class.js b/src/image.class.js index 1b048ad7..17dc16d0 100644 --- a/src/image.class.js +++ b/src/image.class.js @@ -31,7 +31,7 @@ * Constructor * @param {HTMLImageElement | String} element Image element * @param {Object} [options] Options object - * @return {fabric.Image} + * @return {fabric.Image} thisArg */ initialize: function(element, options) { options || (options = { }); @@ -51,7 +51,7 @@ /** * Returns image element which this instance if based on - * @return {HTMLImageElement} image element + * @return {HTMLImageElement} Image element */ getElement: function() { return this._element; @@ -136,9 +136,10 @@ ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke; + ctx.beginPath(); ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); - ctx.beginPath(); + ctx.closePath(); ctx.restore(); }, @@ -152,6 +153,7 @@ w = this.width, h = this.height; + ctx.save(); ctx.lineWidth = this.strokeWidth; ctx.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; @@ -159,12 +161,14 @@ ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke; + ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray); ctx.closePath(); + ctx.restore(); }, /** @@ -372,7 +376,7 @@ /** * Returns complexity of an instance - * @return {Number} complexity + * @return {Number} complexity of this instance */ complexity: function() { return 1; @@ -455,7 +459,7 @@ * @param {SVGElement} element Element to parse * @param {Function} callback Callback to execute when fabric.Image object is created * @param {Object} [options] Options object - * @return {fabric.Image} + * @return {fabric.Image} Instance of fabric.Image */ fabric.Image.fromElement = function(element, callback, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); diff --git a/src/object.class.js b/src/object.class.js index 4bf1d19f..1e77c2b4 100644 --- a/src/object.class.js +++ b/src/object.class.js @@ -167,7 +167,7 @@ overlayFill: null, /** - * When `true`, an object is rendered via stroke and this property specifies its color + * When defined, an object is rendered via stroke and this property specifies its color * @type String * @default */ @@ -413,7 +413,7 @@ /** * Transforms context when rendering an object * @param {CanvasRenderingContext2D} ctx Context - * @param {Boolean} when true, context is transformed to object's top/left corner. This is used when rendering text on Node + * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node */ transform: function(ctx, fromLeft) { ctx.globalAlpha = this.opacity; @@ -429,8 +429,8 @@ /** * Returns an object representation of an instance - * @param {Array} propertiesToInclude - * @return {Object} object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { @@ -478,8 +478,8 @@ /** * Returns (dataless) object representation of an instance - * @param {Array} [propertiesToInclude] - * @return {Object} object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance */ toDatalessObject: function(propertiesToInclude) { // will be overwritten by subclasses @@ -540,6 +540,7 @@ /** * @private + * @param {Object} object */ _removeDefaultValues: function(object) { var defaultOptions = fabric.Object.prototype.options; @@ -572,7 +573,7 @@ /** * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. - * @param {String} name + * @param {String|Object} key (if object, iterate over the object properties) * @param {Object|Function} value (if function, the value is passed into it and its return value is used as a new one) * @return {fabric.Object} thisArg * @chainable @@ -596,8 +597,9 @@ /** * @private - * @param key - * @param value + * @param {String} key + * @param {Any} value + * @return {fabric.Object} thisArg */ _set: function(key, value) { var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); @@ -667,7 +669,6 @@ this.transform(ctx); } - ctx.save(); if (this.stroke) { ctx.lineWidth = this.strokeWidth; ctx.lineCap = this.strokeLineCap; @@ -697,7 +698,6 @@ this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); - ctx.restore(); if (this.active && !noTransform) { this.drawBorders(ctx); @@ -757,6 +757,7 @@ _renderStroke: function(ctx) { if (!this.stroke) return; + ctx.save(); if (this.strokeDashArray) { // Spec requires the concatenation of two copies the dash list when the number of elements is odd if (1 & this.strokeDashArray.length) { @@ -776,12 +777,13 @@ this._stroke ? this._stroke(ctx) : ctx.stroke(); } this._removeShadow(ctx); + ctx.restore(); }, /** * Clones an instance * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} propertiesToInclude + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the outpu * @return {fabric.Object} clone of an instance */ clone: function(callback, propertiesToInclude) { @@ -808,7 +810,7 @@ /** * Converts an object into a data-url-like string - * @param {Object} options + * @param {Object} options Options object * * `format` the format of the output image. Either "jpeg" or "png". * `quality` quality level (0..1) @@ -876,7 +878,7 @@ /** * Returns complexity of an instance - * @return {Number} complexity + * @return {Number} complexity of this instance */ complexity: function() { return 0; @@ -884,7 +886,7 @@ /** * Returns a JSON representation of an instance - * @param {Array} propertiesToInclude Any properties that you might want to additionally include in the output + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} JSON */ toJSON: function(propertiesToInclude) { diff --git a/src/parser.js b/src/parser.js index eaf87cfe..f578444a 100644 --- a/src/parser.js +++ b/src/parser.js @@ -11,34 +11,41 @@ extend = fabric.util.object.extend, capitalize = fabric.util.string.capitalize, clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, multiplyTransformMatrices = fabric.util.multiplyTransformMatrices; fabric.SHARED_ATTRIBUTES = [ "transform", - "fill", "fill-rule", "fill-opacity", + "fill", "fill-opacity", "fill-rule", "opacity", - "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-width" + "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width" ]; var attributesMap = { + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', 'cx': 'left', 'x': 'left', - 'cy': 'top', - 'y': 'top', 'r': 'radius', - 'fill-opacity': 'opacity', - 'fill-rule': 'fillRule', - 'stroke-width': 'strokeWidth', 'stroke-dasharray': 'strokeDashArray', 'stroke-linecap': 'strokeLineCap', 'stroke-linejoin': 'strokeLineJoin', 'stroke-miterlimit':'strokeMiterLimit', - 'transform': 'transformMatrix', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', 'text-decoration': 'textDecoration', - 'font-size': 'fontSize', - 'font-weight': 'fontWeight', - 'font-style': 'fontStyle', - 'font-family': 'fontFamily' + 'cy': 'top', + 'y': 'top', + 'transform': 'transformMatrix' + }; + + var colorAttributes = { + 'stroke': 'strokeOpacity', + 'fill': 'fillOpacity' }; function normalizeAttr(attr) { @@ -77,6 +84,22 @@ return (!isArray && isNaN(parsed) ? value : parsed); } + /** + * @private + * @param {Object} attributes Array of attributes to parse + */ + function _setStrokeFillOpacity(attributes) { + for (var attr in colorAttributes) { + if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') continue; + + var color = new fabric.Color(attributes[attr]); + attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); + + delete attributes[colorAttributes[attr]]; + } + return attributes; + } + /** * Returns an object of attributes' name/value, given element and an array of attribute names; * Parses parent "g" nodes recursively upwards. @@ -116,7 +139,7 @@ ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element))); - return extend(parentAttributes, ownAttributes); + return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); } /** diff --git a/src/path.class.js b/src/path.class.js index 2ab4fde0..312582e9 100644 --- a/src/path.class.js +++ b/src/path.class.js @@ -172,6 +172,7 @@ * Constructor * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) * @param {Object} [options] Options object + * @return {fabric.Path} thisArg */ initialize: function(path, options) { options = options || { }; @@ -203,6 +204,7 @@ /** * @private + * @param {Object} [options] Options object */ _initializePath: function (options) { var isWidthSet = 'width' in options, @@ -232,6 +234,7 @@ /** * @private + * @param {Boolean} positionSet When false, path offset is returned otherwise 0 */ _calculatePathOffset: function (positionSet) { return { @@ -242,6 +245,7 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx context to render path on */ _render: function(ctx) { var current, // current instruction @@ -540,7 +544,6 @@ } // ctx.globalCompositeOperation = this.fillRule; - ctx.save(); if (this.overlayFill) { ctx.fillStyle = this.overlayFill; } @@ -569,7 +572,6 @@ this._renderStroke(ctx); this.clipTo && ctx.restore(); this._removeShadow(ctx); - ctx.restore(); if (!noTransform && this.active) { this.drawBorders(ctx); @@ -589,7 +591,7 @@ /** * Returns object representation of an instance - * @param {Array} propertiesToInclude + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toObject: function(propertiesToInclude) { @@ -607,7 +609,7 @@ /** * Returns dataless object representation of an instance - * @param {Array} propertiesToInclude + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output * @return {Object} object representation of an instance */ toDatalessObject: function(propertiesToInclude) { @@ -657,7 +659,7 @@ /** * Returns number representation of an instance complexity - * @return {Number} complexity + * @return {Number} complexity of this instance */ complexity: function() { return this.path.length; diff --git a/src/text.class.js b/src/text.class.js index 2f816a4b..066510b5 100644 --- a/src/text.class.js +++ b/src/text.class.js @@ -37,8 +37,6 @@ 'textAlign', 'fontStyle', 'lineHeight', - 'stroke', - 'strokeWidth', 'backgroundColor', 'textBackgroundColor', 'useNative' @@ -52,6 +50,14 @@ */ fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'text', + /** * Font size (in pixels) * @type Number @@ -74,7 +80,7 @@ fontFamily: 'Times New Roman', /** - * Text decoration (e.g. underline, overline) + * Text decoration Possible values: "", "underline", "overline" or "line-through". * @type String * @default */ @@ -95,7 +101,7 @@ textAlign: 'left', /** - * Font style (e.g. italic) + * Font style . Possible values: "", "normal", "italic" or "oblique". * @type String * @default */ @@ -108,20 +114,6 @@ */ lineHeight: 1.3, - /** - * Stroke style. When specified, text is rendered with stroke - * @type String - * @default - */ - stroke: '', - - /** - * Stroke width - * @type Number - * @default - */ - strokeWidth: 1, - /** * Background color of an entire text box * @type String @@ -143,13 +135,6 @@ */ path: null, - /** - * Type of an object - * @type String - * @default - */ - type: 'text', - /** * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) * @type Boolean @@ -166,8 +151,8 @@ /** * Constructor - * @param {String} text - * @param {Object} [options] + * @param {String} text Text string + * @param {Object} [options] Options object * @return {fabric.Text} thisArg */ initialize: function(text, options) { @@ -224,6 +209,7 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderViaCufon: function(ctx) { var o = Cufon.textOptions || (Cufon.textOptions = { }); @@ -316,6 +302,8 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines */ _setBoundaries: function(ctx, textLines) { this._boundaries = [ ]; @@ -335,6 +323,7 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on */ _setTextStyles: function(ctx) { if (this.fill) { @@ -358,6 +347,9 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + * @return {Number} Height of fabric.Text object */ _getTextHeight: function(ctx, textLines) { return this.fontSize * textLines.length * this.lineHeight; @@ -365,6 +357,9 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + * @return {Number} Maximum width of fabric.Text object */ _getTextWidth: function(ctx, textLines) { var maxWidth = ctx.measureText(textLines[0]).width; @@ -380,46 +375,46 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on */ _setTextShadow: function(ctx) { - if (this.textShadow) { + if (!this.textShadow) return; - // "rgba(0,0,0,0.2) 2px 2px 10px" - // "rgb(0, 100, 0) 0 0 5px" - // "red 2px 2px 1px" - // "#f55 123 345 567" - var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/; + // "rgba(0,0,0,0.2) 2px 2px 10px" + // "rgb(0, 100, 0) 0 0 5px" + // "red 2px 2px 1px" + // "#f55 123 345 567" + var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/; - var shadowDeclaration = this.textShadow; - var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow); - var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, ''); + var shadowDeclaration = this.textShadow; + var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow); + var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, ''); - ctx.save(); - ctx.shadowColor = shadowColor; - ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10); - ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10); - ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10); + ctx.save(); + ctx.shadowColor = shadowColor; + ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10); + ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10); + ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10); - this._shadows = [{ - blur: ctx.shadowBlur, - color: ctx.shadowColor, - offX: ctx.shadowOffsetX, - offY: ctx.shadowOffsetY - }]; + this._shadows = [{ + blur: ctx.shadowBlur, + color: ctx.shadowColor, + offX: ctx.shadowOffsetX, + offY: ctx.shadowOffsetY + }]; - this._shadowOffsets = [[ - parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10) - ]]; - } + this._shadowOffsets = [[ + parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10) + ]]; }, /** * @private - * @param method - * @param ctx - * @param line - * @param left - * param top + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text */ _drawTextLine: function(method, ctx, line, left, top) { @@ -452,6 +447,10 @@ } }, + /** + * @private + * @return {Number} Left offset + */ _getLeftOffset: function() { if (fabric.isLikelyNode && (this.originX === 'left' || this.originX === 'center')) { return 0; @@ -459,6 +458,10 @@ return -this.width / 2; }, + /** + * @private + * @return {Number} Top offset + */ _getTopOffset: function() { if (fabric.isLikelyNode && (this.originY === 'top' || this.originY === 'center')) { return 0; @@ -468,51 +471,59 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines */ _renderTextFill: function(ctx, textLines) { - if (this.fill) { - this._boundaries = [ ]; - for (var i = 0, len = textLines.length; i < len; i++) { - this._drawTextLine( - 'fillText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + ((i + 1) * this.fontSize * this.lineHeight) - ); - } + if (!this.fill) return; + + this._boundaries = [ ]; + for (var i = 0, len = textLines.length; i < len; i++) { + this._drawTextLine( + 'fillText', + ctx, + textLines[i], + this._getLeftOffset(), + this._getTopOffset() + ((i + 1) * this.fontSize * this.lineHeight) + ); } }, /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines */ _renderTextStroke: function(ctx, textLines) { - if (this.stroke) { - if (this.strokeDashArray) { - // Spec requires the concatenation of two copies the dash list when the number of elements is odd - if (1 & this.strokeDashArray.length) { - this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); - } - supportsLineDash && ctx.setLineDash(this.strokeDashArray); - } + if (!this.stroke) return; - ctx.beginPath(); - for (var i = 0, len = textLines.length; i < len; i++) { - this._drawTextLine( - 'strokeText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + ((i + 1) * this.fontSize * this.lineHeight) - ); + ctx.save(); + if (this.strokeDashArray) { + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); } - ctx.closePath(); + supportsLineDash && ctx.setLineDash(this.strokeDashArray); } + + ctx.beginPath(); + for (var i = 0, len = textLines.length; i < len; i++) { + this._drawTextLine( + 'strokeText', + ctx, + textLines[i], + this._getLeftOffset(), + this._getTopOffset() + ((i + 1) * this.fontSize * this.lineHeight) + ); + } + ctx.closePath(); + ctx.restore(); }, /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines */ _renderTextBackground: function(ctx, textLines) { this._renderTextBoxBackground(ctx); @@ -521,52 +532,57 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderTextBoxBackground: function(ctx) { - if (this.backgroundColor) { - ctx.save(); - ctx.fillStyle = this.backgroundColor; + if (!this.backgroundColor) return; - ctx.fillRect( - this._getLeftOffset(), - this._getTopOffset(), - this.width, - this.height - ); + ctx.save(); + ctx.fillStyle = this.backgroundColor; - ctx.restore(); - } + ctx.fillRect( + this._getLeftOffset(), + this._getTopOffset(), + this.width, + this.height + ); + + ctx.restore(); }, /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines */ _renderTextLinesBackground: function(ctx, textLines) { - if (this.textBackgroundColor) { - ctx.save(); - ctx.fillStyle = this.textBackgroundColor; + if (!this.textBackgroundColor) return; - for (var i = 0, len = textLines.length; i < len; i++) { + ctx.save(); + ctx.fillStyle = this.textBackgroundColor; - if (textLines[i] !== '') { + for (var i = 0, len = textLines.length; i < len; i++) { - var lineWidth = this._getLineWidth(ctx, textLines[i]); - var lineLeftOffset = this._getLineLeftOffset(lineWidth); + if (textLines[i] !== '') { - ctx.fillRect( - this._getLeftOffset() + lineLeftOffset, - this._getTopOffset() + (i * this.fontSize * this.lineHeight), - lineWidth, - this.fontSize * this.lineHeight - ); - } + var lineWidth = this._getLineWidth(ctx, textLines[i]); + var lineLeftOffset = this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + this._getLeftOffset() + lineLeftOffset, + this._getTopOffset() + (i * this.fontSize * this.lineHeight), + lineWidth, + this.fontSize * this.lineHeight + ); } - ctx.restore(); } + ctx.restore(); }, /** * @private + * @param {Number} lineWidth Width of text line + * @return {Number} Line left offset */ _getLineLeftOffset: function(lineWidth) { if (this.textAlign === 'center') { @@ -580,8 +596,9 @@ /** * @private - * @param ctx - * @param line + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text line + * @return {Number} Line width */ _getLineWidth: function(ctx, line) { return this.textAlign === 'justify' @@ -591,8 +608,11 @@ /** * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines */ _renderTextDecoration: function(ctx, textLines) { + if (!this.textDecoration) return; var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; var _this = this; @@ -664,7 +684,7 @@ /** * Renders text instance on a specified context - * @param ctx {CanvasRenderingContext2D} context to render on + * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ render: function(ctx, noTransform) { @@ -682,8 +702,8 @@ /** * Returns object representation of an instance - * @param {Array} propertiesToInclude - * @return {Object} object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { @@ -697,8 +717,6 @@ textShadow: this.textShadow, textAlign: this.textAlign, path: this.path, - stroke: this.stroke, - strokeWidth: this.strokeWidth, backgroundColor: this.backgroundColor, textBackgroundColor: this.textBackgroundColor, useNative: this.useNative @@ -749,6 +767,9 @@ /** * @private + * @param {Number} lineTopOffset Line top offset + * @param {Array} textLines Array of all text lines + * @return {Array} */ _getSVGShadows: function(lineTopOffset, textLines) { var shadowSpans = [], j, i, jlen, ilen, lineTopOffsetMultiplier = 1; @@ -785,6 +806,10 @@ /** * @private + * @param {Number} lineTopOffset Line top offset + * @param {Number} textLeftOffset Text left offset + * @param {Array} textLines Array of all text lines + * @return {Object} */ _getSVGTextAndBg: function(lineTopOffset, textLeftOffset, textLines) { var textSpans = [ ], textBgRects = [ ], i, lineLeftOffset, len, lineTopOffsetMultiplier = 1; @@ -854,6 +879,8 @@ * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 * * @private + * @param {Any} value + * @return {String} */ _getFillAttributes: function(value) { var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; @@ -921,8 +948,8 @@ /** * Returns fabric.Text instance from an object representation * @static - * @param {Object} object to create an instance from - * @return {fabric.Text} an instance + * @param object {Object} object Object to create an instance from + * @return {fabric.Text} Instance of fabric.Text */ fabric.Text.fromObject = function(object) { return new fabric.Text(object.text, clone(object)); @@ -931,9 +958,9 @@ /** * Returns fabric.Text instance from an SVG element (not yet implemented) * @static - * @param element - * @param options - * @return {fabric.Text} an instance + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Text} Instance of fabric.Text */ fabric.Text.fromElement = function(element, options) { diff --git a/test/unit/circle.js b/test/unit/circle.js index 59d803e8..966e304d 100644 --- a/test/unit/circle.js +++ b/test/unit/circle.js @@ -44,11 +44,11 @@ ok(typeof circle.setRadius == 'function'); circle.setRadius(20); - equal(20, circle.getRadiusX()); - equal(20, circle.getRadiusY()); + equal(circle.getRadiusX(), 20); + equal(circle.getRadiusY(), 20); - equal(40, circle.getWidth()); - equal(40, circle.getHeight()); + equal(circle.getWidth(), 40); + equal(circle.getHeight(), 40); }); test('complexity', function() { @@ -97,8 +97,8 @@ circle.set('left', 100).set('top', 200).set('radius', 15); var augmentedProperties = fabric.util.object.extend(fabric.util.object.clone(defaultProperties), { - left: 100, - top: 200, + left: 100, + top: 200, radius: 15 }); @@ -113,7 +113,7 @@ left = 12, top = 15, fill = 'ff5555', - fillOpacity = 0.5, + opacity = 0.5, strokeWidth = 2, strokeDashArray = [5, 2], strokeLineCap = 'round', @@ -125,7 +125,7 @@ elCircle.setAttribute('cx', left); elCircle.setAttribute('cy', top); elCircle.setAttribute('fill', fill); - elCircle.setAttribute('fill-opacity', fillOpacity); + elCircle.setAttribute('opacity', opacity); elCircle.setAttribute('stroke-width', strokeWidth); elCircle.setAttribute('stroke-dasharray', '5, 2'); elCircle.setAttribute('stroke-linecap', strokeLineCap); @@ -139,7 +139,7 @@ equal(oCircle.get('left'), left); equal(oCircle.get('top'), top); equal(oCircle.get('fill'), fill); - equal(oCircle.get('opacity'), fillOpacity); + equal(oCircle.get('opacity'), opacity); equal(oCircle.get('strokeWidth'), strokeWidth); deepEqual(oCircle.get('strokeDashArray'), strokeDashArray); equal(oCircle.get('strokeLineCap'), strokeLineCap); @@ -193,7 +193,7 @@ var expected = circle.toObject(); var actual = fabric.Circle.fromObject(expected).toObject(); - deepEqual(expected, actual); + deepEqual(actual, expected); }); test('cloning and radius, width, height', function() { @@ -202,9 +202,9 @@ var clone = circle.clone(); - equal(40, clone.getWidth()); - equal(40, clone.getHeight()); + equal(clone.getWidth(), 40); + equal(clone.getHeight(), 40); - equal(10, clone.radius); + equal(clone.radius, 10); }); })(); \ No newline at end of file diff --git a/test/unit/color.js b/test/unit/color.js index 56cd30ba..d6638b92 100644 --- a/test/unit/color.js +++ b/test/unit/color.js @@ -12,6 +12,16 @@ ok(oColor); ok(oColor instanceof fabric.Color); equal(oColor.toRgb(), 'rgb(100,100,100)'); + + var oColor = new fabric.Color('rgba(100,100,100, 0.5)'); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgba(), 'rgba(100,100,100,0.5)'); + + var oColor = new fabric.Color('hsl(262,80%,12%)'); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toHsl(), 'hsl(262,80%,12%)'); }); test('getSource', function() { @@ -43,6 +53,22 @@ equal(oColor.toRgba(), 'rgba(0,0,0,0.5)'); }); + test('toHsl', function() { + var oColor = new fabric.Color('ffffff'); + ok(typeof oColor.toHsl == 'function'); + equal(oColor.toHsl(), 'hsl(0,0%,100%)'); + oColor.setSource([0,0,0,0.5]); + equal(oColor.toHsl(), 'hsl(0,0%,0%)'); + }); + + test('toHsla', function() { + var oColor = new fabric.Color('ffffff'); + ok(typeof oColor.toHsla == 'function'); + equal(oColor.toHsla(), 'hsla(0,0%,100%,1)'); + oColor.setSource([0,0,0,0.5]); + equal(oColor.toHsla(), 'hsla(0,0%,0%,0.5)'); + }); + test('toHex', function() { var oColor = new fabric.Color('ffffff'); ok(typeof oColor.toHex == 'function'); @@ -100,6 +126,36 @@ equal(oColor.toHex(), 'FFFFFF'); }); + test('fromRgb (with whitespaces)', function() { + ok(typeof fabric.Color.fromRgb == 'function'); + var originalRgb = 'rgb( 255 , 255 , 255 )'; + var oColor = fabric.Color.fromRgb(originalRgb); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgb(), 'rgb(255,255,255)'); + equal(oColor.toHex(), 'FFFFFF'); + }); + + test('fromRgb (percentage values)', function() { + ok(typeof fabric.Color.fromRgb == 'function'); + var originalRgb = 'rgb(100%,100%,100%)'; + var oColor = fabric.Color.fromRgb(originalRgb); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgb(), 'rgb(255,255,255)'); + equal(oColor.toHex(), 'FFFFFF'); + }); + + test('fromRgb (percentage values with whitespaces)', function() { + ok(typeof fabric.Color.fromRgb == 'function'); + var originalRgb = 'rgb( 100% , 100% , 100% )'; + var oColor = fabric.Color.fromRgb(originalRgb); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgb(), 'rgb(255,255,255)'); + equal(oColor.toHex(), 'FFFFFF'); + }); + test('fromRgba', function() { ok(typeof fabric.Color.fromRgba == 'function'); var originalRgba = 'rgba(255,255,255,0.5)'; @@ -111,6 +167,78 @@ equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); }); + test('fromRgba (with whitespaces)', function() { + var originalRgba = 'rgba( 255 , 255 , 255 , 0.5 )'; + oColor = fabric.Color.fromRgba(originalRgba); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); + equal(oColor.toHex(), 'FFFFFF'); + equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); + }); + + test('fromRgba (percentage values)', function() { + var originalRgba = 'rgba(100%,100%,100%,0.5)'; + oColor = fabric.Color.fromRgba(originalRgba); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); + equal(oColor.toHex(), 'FFFFFF'); + equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); + }); + + test('fromRgba (percentage values with whitespaces)', function() { + var originalRgba = 'rgba( 100% , 100% , 100% , 0.5 )'; + oColor = fabric.Color.fromRgba(originalRgba); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toRgba(), 'rgba(255,255,255,0.5)'); + equal(oColor.toHex(), 'FFFFFF'); + equal(oColor.getAlpha(), 0.5, 'alpha should be set properly'); + }); + + test('fromHsl', function() { + ok(typeof fabric.Color.fromHsl == 'function'); + var originalHsl = 'hsl(262,80%,12%)'; + var oColor = fabric.Color.fromHsl(originalHsl); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toHsl(), originalHsl); + equal(oColor.toHex(), '180637'); + }); + + test('fromHsl (with whitespaces)', function() { + ok(typeof fabric.Color.fromHsl == 'function'); + var originalHsl = 'hsl( 262 , 80% , 12% )'; + var oColor = fabric.Color.fromHsl(originalHsl); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toHsl(), 'hsl(262,80%,12%)'); + equal(oColor.toHex(), '180637'); + }); + + test('fromHsla', function() { + ok(typeof fabric.Color.fromHsla == 'function'); + var originalHsla = 'hsla(262,80%,12%,0.2)'; + var oColor = fabric.Color.fromHsla(originalHsla); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toHsla(), originalHsla); + equal(oColor.toHex(), '180637'); + equal(oColor.getAlpha(), 0.2, 'alpha should be set properly'); + }); + + test('fromHsla (with whitespaces)', function() { + ok(typeof fabric.Color.fromHsla == 'function'); + var originalHsla = 'hsla( 262 , 80% , 12% , 0.2 )'; + var oColor = fabric.Color.fromHsla(originalHsla); + ok(oColor); + ok(oColor instanceof fabric.Color); + equal(oColor.toHsla(), 'hsla(262,80%,12%,0.2)'); + equal(oColor.toHex(), '180637'); + equal(oColor.getAlpha(), 0.2, 'alpha should be set properly'); + }); + test('fromHex', function() { ok(typeof fabric.Color.fromHex == 'function'); var originalHex = 'FF5555'; @@ -123,24 +251,30 @@ test('sourceFromRgb', function() { ok(typeof fabric.Color.sourceFromRgb == 'function'); - deepEqual([255,255,255,1], fabric.Color.sourceFromRgb('rgb(255,255,255)')); - deepEqual([100,150,200,1], fabric.Color.sourceFromRgb('rgb(100,150,200)')); + deepEqual(fabric.Color.sourceFromRgb('rgb(255,255,255)'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromRgb('rgb(100,150,200)'), [100,150,200,1]); + }); + + test('sourceFromHsl', function() { + ok(typeof fabric.Color.sourceFromHsl == 'function'); + deepEqual(fabric.Color.sourceFromHsl('hsl(360,100%,100%)'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHsl('hsl(180,50%,40%)'), [51,153,153,1]); }); test('sourceFromHex', function() { ok(typeof fabric.Color.sourceFromHex == 'function'); // uppercase - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('FFFFFF')); - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('FFF')); - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('#FFFFFF')); - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('#FFF')); + deepEqual(fabric.Color.sourceFromHex('FFFFFF'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHex('FFF'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHex('#FFFFFF'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHex('#FFF'), [255,255,255,1]); // lowercase - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('#ffffff')); - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('#fff')); - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('ffffff')); - deepEqual([255,255,255,1], fabric.Color.sourceFromHex('fff')); + deepEqual(fabric.Color.sourceFromHex('#ffffff'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHex('#fff'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHex('ffffff'), [255,255,255,1]); + deepEqual(fabric.Color.sourceFromHex('fff'), [255,255,255,1]); }); test('fromSource', function() { diff --git a/test/unit/ellipse.js b/test/unit/ellipse.js index cdac4bb6..3418dd18 100644 --- a/test/unit/ellipse.js +++ b/test/unit/ellipse.js @@ -55,7 +55,7 @@ 'visible': true }; ok(typeof ellipse.toObject == 'function'); - deepEqual(defaultProperties, ellipse.toObject()); + deepEqual(ellipse.toObject(), defaultProperties); ellipse.set('left', 100).set('top', 200).set('rx', 15).set('ry', 25); @@ -66,7 +66,7 @@ ry: 25 }); - deepEqual(augmentedProperties, ellipse.toObject()); + deepEqual(ellipse.toObject(), augmentedProperties); }); test('render', function() { @@ -92,7 +92,7 @@ left = 12, top = 15, fill = 'ff5555', - fillOpacity = 0.5, + opacity = 0.5, strokeWidth = 2, strokeDashArray = [5, 2], strokeLineCap = 'round', @@ -104,7 +104,7 @@ elEllipse.setAttribute('cx', left); elEllipse.setAttribute('cy', top); elEllipse.setAttribute('fill', fill); - elEllipse.setAttribute('fill-opacity', fillOpacity); + elEllipse.setAttribute('opacity', opacity); elEllipse.setAttribute('stroke-width', strokeWidth); elEllipse.setAttribute('stroke-dasharray', '5, 2'); elEllipse.setAttribute('stroke-linecap', strokeLineCap); @@ -119,7 +119,7 @@ equal(oEllipse.get('left'), left); equal(oEllipse.get('top'), top); equal(oEllipse.get('fill'), fill); - equal(oEllipse.get('opacity'), fillOpacity); + equal(oEllipse.get('opacity'), opacity); equal(oEllipse.get('strokeWidth'), strokeWidth); deepEqual(oEllipse.get('strokeDashArray'), strokeDashArray); equal(oEllipse.get('strokeLineCap'), strokeLineCap); @@ -150,6 +150,6 @@ var expected = ellipse.toObject(); var actual = fabric.Ellipse.fromObject(expected).toObject(); - deepEqual(expected, actual); + deepEqual(actual, expected); }); })(); \ No newline at end of file diff --git a/test/unit/parser.js b/test/unit/parser.js index 21d3d408..b688297d 100644 --- a/test/unit/parser.js +++ b/test/unit/parser.js @@ -8,7 +8,7 @@ 'cy': 103, 'y': 104, 'r': 105, - 'fill-opacity': 0.45, + 'opacity': 0.45, 'fill-rule': 'foo', 'stroke-width': 4 }; @@ -26,17 +26,17 @@ ok(fabric.parseAttributes); var element = makeElement(); - var attributeNames = 'cx cy x y r fill-opacity fill-rule stroke-width transform fill fill-rule'.split(' '); + var attributeNames = 'cx cy x y r opacity fill-rule stroke-width transform fill fill-rule'.split(' '); var parsedAttributes = fabric.parseAttributes(element, attributeNames); - deepEqual({ + deepEqual(parsedAttributes, { left: 102, top: 104, radius: 105, opacity: 0.45, fillRule: 'foo', strokeWidth: 4 - }, parsedAttributes); + }); }); test('parseAttributesNoneValues', function() { @@ -44,27 +44,27 @@ element.setAttribute('fill', 'none'); element.setAttribute('stroke', 'none'); - deepEqual({ fill: '', stroke: '' }, fabric.parseAttributes(element, 'fill stroke'.split(' '))); + deepEqual(fabric.parseAttributes(element, 'fill stroke'.split(' ')), { fill: '', stroke: '' }); }); test('parseAttributesFillRule', function() { var element = fabric.document.createElement('path'); element.setAttribute('fill-rule', 'evenodd'); - deepEqual({ fillRule: 'destination-over' }, fabric.parseAttributes(element, ['fill-rule'])); + deepEqual(fabric.parseAttributes(element, ['fill-rule']), { fillRule: 'destination-over' }); }); test('parseAttributesFillRuleWithoutTransformation', function() { var element = fabric.document.createElement('path'); element.setAttribute('fill-rule', 'inherit'); - deepEqual({ fillRule: 'inherit' }, fabric.parseAttributes(element, ['fill-rule'])); + deepEqual(fabric.parseAttributes(element, ['fill-rule']), { fillRule: 'inherit' }); }); test('parseAttributesTransform', function() { var element = fabric.document.createElement('path'); element.setAttribute('transform', 'translate(5, 10)'); - deepEqual({ transformMatrix: [1, 0, 0, 1, 5, 10] }, fabric.parseAttributes(element, ['transform'])); + deepEqual(fabric.parseAttributes(element, ['transform']), { transformMatrix: [1, 0, 0, 1, 5, 10] }); }); test('parseAttributesWithParent', function() { @@ -79,8 +79,8 @@ parent.setAttribute('y', '200'); grandParent.setAttribute('fill', 'red'); - deepEqual({ fill: 'red', left: 100, top: 200 }, - fabric.parseAttributes(element, 'x y fill'.split(' '))); + deepEqual(fabric.parseAttributes(element, 'x y fill'.split(' ')), + { fill: 'red', left: 100, top: 200 }); }); asyncTest('parseElements', function() { @@ -132,7 +132,7 @@ 'width': 103.45, 'height': 20 }; - deepEqual(expectedObject, fabric.parseStyleAttribute(element)); + deepEqual(fabric.parseStyleAttribute(element), expectedObject); }); test('parseStyleAttribute with one pair', function() { @@ -142,7 +142,7 @@ var expectedObject = { 'left': 10 }; - deepEqual(expectedObject, fabric.parseStyleAttribute(element)); + deepEqual(fabric.parseStyleAttribute(element), expectedObject); }); test('parseStyleAttribute with value normalization', function() { @@ -152,7 +152,7 @@ var expectedObject = { 'fill': '' }; - deepEqual(expectedObject, fabric.parseStyleAttribute(element)); + deepEqual(fabric.parseStyleAttribute(element), expectedObject); }); test('parseStyleAttribute with short font declaration', function() { @@ -164,7 +164,7 @@ 'fontStyle': 'italic', 'fontFamily': 'Arial,Helvetica,sans-serif' }; - deepEqual(expectedObject, fabric.parseStyleAttribute(element)); + deepEqual(fabric.parseStyleAttribute(element), expectedObject); }); test('parseAttributes (style to have higher priority than attribute)', function() { @@ -175,7 +175,21 @@ var expectedObject = { 'fill': 'red' }; - deepEqual(expectedObject, fabric.parseAttributes(element, ['path'])); + deepEqual(fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES), expectedObject); + }); + + test('parseAttributes stroke-opacity and fill-opacity', function() { + var element = fabric.document.createElement('path'); + element.setAttribute('style', 'fill:rgb(100,200,50);fill-opacity:0.2;'); + element.setAttribute('stroke', 'green'); + element.setAttribute('stroke-opacity', '0.5'); + element.setAttribute('fill', 'green'); + + var expectedObject = { + 'fill': 'rgba(100,200,50,0.2)', + 'stroke': 'rgba(0,128,0,0.5)' + }; + deepEqual(fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES), expectedObject); }); test('parsePointsAttribute', function() { @@ -210,44 +224,44 @@ element.setAttribute('transform', 'translate(5,10)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([1,0,0,1,5,10], parsedValue); + deepEqual(parsedValue, [1,0,0,1,5,10]); element.setAttribute('transform', 'translate(-10,-20)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([1,0,0,1,-10,-20], parsedValue); + deepEqual(parsedValue, [1,0,0,1,-10,-20]); var ANGLE = 90; element.setAttribute('transform', 'rotate(' + ANGLE + ')'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([Math.cos(ANGLE), Math.sin(ANGLE), -Math.sin(ANGLE), Math.cos(ANGLE), 0, 0], parsedValue); + deepEqual(parsedValue, [Math.cos(ANGLE), Math.sin(ANGLE), -Math.sin(ANGLE), Math.cos(ANGLE), 0, 0]); element.setAttribute('transform', 'scale(3.5)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([3.5,0,0,3.5,0,0], parsedValue); + deepEqual(parsedValue, [3.5,0,0,3.5,0,0]); element.setAttribute('transform', 'scale(2 13)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([2,0,0,13,0,0], parsedValue); + deepEqual(parsedValue, [2,0,0,13,0,0]); element.setAttribute('transform', 'skewX(2)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([1,0,2,1,0,0], parsedValue); + deepEqual(parsedValue, [1,0,2,1,0,0]); element.setAttribute('transform', 'skewY(234.111)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([1,234.111,0,1,0,0], parsedValue); + deepEqual(parsedValue, [1,234.111,0,1,0,0]); element.setAttribute('transform', 'matrix(1,2,3,4,5,6)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([1,2,3,4,5,6], parsedValue); + deepEqual(parsedValue, [1,2,3,4,5,6]); element.setAttribute('transform', 'translate(21,31) translate(11,22)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([1,0,0,1,32,53], parsedValue); + deepEqual(parsedValue, [1,0,0,1,32,53]); element.setAttribute('transform', 'scale(2 13) translate(5,15) skewX(11.22)'); var parsedValue = fabric.parseTransformAttribute(element.getAttribute('transform')); - deepEqual([2,0,22.44,13,10,195], parsedValue); + deepEqual(parsedValue, [2,0,22.44,13,10,195]); }); @@ -321,7 +335,7 @@ el.setAttribute('opacity', opacityValue); var obj = fabric.Rect.fromElement(el); - equal(parseFloat(opacityValue), obj.opacity, + equal(obj.opacity, parseFloat(opacityValue), 'opacity should be parsed correctly from "opacity" attribute of ' + tagNames[i] + ' element'); } }); diff --git a/test/unit/path.js b/test/unit/path.js index 785c038d..0a907f0f 100644 --- a/test/unit/path.js +++ b/test/unit/path.js @@ -78,25 +78,25 @@ test('toString', function() { var path = makePathObject(); ok(typeof path.toString == 'function'); - equal('#', path.toString()); + equal(path.toString(), '#'); }); test('toObject', function() { var path = makePathObject(); ok(typeof path.toObject == 'function'); - deepEqual(REFERENCE_PATH_OBJECT, path.toObject()); + deepEqual(path.toObject(), REFERENCE_PATH_OBJECT); }); test('toDatalessObject', function() { var path = makePathObject(); ok(typeof path.toDatalessObject == 'function'); - deepEqual(REFERENCE_PATH_OBJECT, path.toDatalessObject()); + deepEqual(path.toDatalessObject(), REFERENCE_PATH_OBJECT); var src = 'http://example.com/'; path.setSourcePath(src); - deepEqual(fabric.util.object.extend(fabric.util.object.clone(REFERENCE_PATH_OBJECT), { - path: src - }), path.toDatalessObject()); + deepEqual(path.toDatalessObject(), fabric.util.object.extend(fabric.util.object.clone(REFERENCE_PATH_OBJECT), { + path: src + })); }); test('complexity', function() { @@ -108,7 +108,7 @@ ok(typeof fabric.Path.fromObject == 'function'); var path = fabric.Path.fromObject(REFERENCE_PATH_OBJECT); ok(path instanceof fabric.Path); - deepEqual(REFERENCE_PATH_OBJECT, path.toObject()); + deepEqual(path.toObject(), REFERENCE_PATH_OBJECT); }); test('fromElement', function() { @@ -117,7 +117,7 @@ elPath.setAttribute('d', 'M 100 100 L 300 100 L 200 300 z'); elPath.setAttribute('fill', 'red'); - elPath.setAttribute('fill-opacity', '1'); + elPath.setAttribute('opacity', '1'); elPath.setAttribute('stroke', 'blue'); elPath.setAttribute('stroke-width', '1'); elPath.setAttribute('stroke-dasharray', '5, 2'); @@ -132,13 +132,13 @@ var path = fabric.Path.fromElement(elPath); ok(path instanceof fabric.Path); - deepEqual(fabric.util.object.extend(REFERENCE_PATH_OBJECT, { + deepEqual(path.toObject(), fabric.util.object.extend(REFERENCE_PATH_OBJECT, { strokeDashArray: [5, 2], strokeLineCap: 'round', strokeLineJoin: 'bevil', strokeMiterLimit: 5, transformMatrix: [2, 0, 0, 2, 0, 0] - }), path.toObject()); + })); var ANGLE = 90; @@ -146,8 +146,8 @@ path = fabric.Path.fromElement(elPath); deepEqual( - [ Math.cos(ANGLE), Math.sin(ANGLE), -Math.sin(ANGLE), Math.cos(ANGLE), 0, 0 ], - path.get('transformMatrix') + path.get('transformMatrix'), + [ Math.cos(ANGLE), Math.sin(ANGLE), -Math.sin(ANGLE), Math.cos(ANGLE), 0, 0 ] ); }); @@ -155,16 +155,16 @@ var el = getPathElement('M100 100 l 200 200 300 300 400 -50 z'); var obj = fabric.Path.fromElement(el); - deepEqual(['M', 100, 100], obj.path[0]); - deepEqual(['l', 200, 200], obj.path[1]); - deepEqual(['l', 300, 300], obj.path[2]); - deepEqual(['l', 400, -50], obj.path[3]); + deepEqual(obj.path[0], ['M', 100, 100]); + deepEqual(obj.path[1], ['l', 200, 200]); + deepEqual(obj.path[2], ['l', 300, 300]); + deepEqual(obj.path[3], ['l', 400, -50]); el = getPathElement('c 0,-53.25604 43.17254,-96.42858 96.42857,-96.42857 53.25603,0 96.42857,43.17254 96.42857,96.42857'); obj = fabric.Path.fromElement(el); - deepEqual(['c', 0, -53.25604, 43.17254, -96.42858, 96.42857, -96.42857], obj.path[0]); - deepEqual(['c', 53.25603, 0, 96.42857, 43.17254, 96.42857, 96.42857], obj.path[1]); + deepEqual(obj.path[0], ['c', 0, -53.25604, 43.17254, -96.42858, 96.42857, -96.42857]); + deepEqual(obj.path[1], ['c', 53.25603, 0, 96.42857, 43.17254, 96.42857, 96.42857]); }); test('compressed path commands', function() { @@ -172,9 +172,9 @@ var el = getPathElement('M56.224 84.12c-.047.132-.138.221-.322.215.046-.131.137-.221.322-.215z'); var obj = fabric.Path.fromElement(el); - deepEqual(['M', 56.224, 84.12], obj.path[0]); - deepEqual(['c', -0.047, 0.132, -0.138, 0.221, -0.322, 0.215], obj.path[1]); - deepEqual(['c', 0.046, -0.131, 0.137, -0.221, 0.322, -0.215], obj.path[2]); - deepEqual(['z'], obj.path[3]); + deepEqual(obj.path[0], ['M', 56.224, 84.12]); + deepEqual(obj.path[1], ['c', -0.047, 0.132, -0.138, 0.221, -0.322, 0.215]); + deepEqual(obj.path[2], ['c', 0.046, -0.131, 0.137, -0.221, 0.322, -0.215]); + deepEqual(obj.path[3], ['z']); }); })(); diff --git a/test/unit/path_group.js b/test/unit/path_group.js index 2ae0bf67..06460782 100644 --- a/test/unit/path_group.js +++ b/test/unit/path_group.js @@ -38,7 +38,7 @@ el.setAttribute('d', path); el.setAttribute('fill', 'rgb(255,0,0)'); el.setAttribute('stroke', 'blue'); - el.setAttribute('troke-width', 3); + el.setAttribute('stroke-width', 3); return el; } @@ -77,7 +77,7 @@ paths[0].group = null; paths[1].group = null; - deepEqual(paths, pathGroup.getObjects()); + deepEqual(pathGroup.getObjects(), paths); }); test('toObject', function() { @@ -115,7 +115,7 @@ 'paths': 'http://example.com/', 'sourcePath': 'http://example.com/' }); - deepEqual(expectedObject, pathGroup.toDatalessObject()); + deepEqual(pathGroup.toDatalessObject(), expectedObject); }); test('toString', function() { diff --git a/test/unit/polygon.js b/test/unit/polygon.js index e13bef5d..eb74c4e7 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -51,7 +51,7 @@ ok(polygon instanceof fabric.Object); equal(polygon.type, 'polygon'); - deepEqual([ { x: -5, y: -5 }, { x: 5, y: 5 } ], polygon.get('points')); + deepEqual(polygon.get('points'), [ { x: -5, y: -5 }, { x: 5, y: 5 } ]); }); test('complexity', function() { @@ -74,7 +74,7 @@ ok(typeof fabric.Polygon.fromObject == 'function'); var polygon = fabric.Polygon.fromObject(REFERENCE_OBJECT); ok(polygon instanceof fabric.Polygon); - deepEqual(REFERENCE_OBJECT, polygon.toObject()); + deepEqual(polygon.toObject(), REFERENCE_OBJECT); }); test('fromElement', function() { @@ -93,12 +93,12 @@ points: [ { x: 10, y: 12 }, { x: 20, y: 22 } ] }); - deepEqual(expected, polygon.toObject()); + deepEqual(polygon.toObject(), expected); var elPolygonWithAttrs = fabric.document.createElement('polygon'); elPolygonWithAttrs.setAttribute('points', '10,10 20,20 30,30 10,10'); elPolygonWithAttrs.setAttribute('fill', 'rgb(255,255,255)'); - elPolygonWithAttrs.setAttribute('fill-opacity', '0.34'); + elPolygonWithAttrs.setAttribute('opacity', '0.34'); elPolygonWithAttrs.setAttribute('stroke-width', '3'); elPolygonWithAttrs.setAttribute('stroke', 'blue'); elPolygonWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2)'); @@ -115,21 +115,21 @@ { x: 10, y: 10 } ]; - deepEqual(fabric.util.object.extend(REFERENCE_OBJECT, { - 'width': 20, - 'height': 20, - 'fill': 'rgb(255,255,255)', - 'stroke': 'blue', - 'strokeWidth': 3, - 'strokeDashArray': [5, 2], - 'strokeLineCap': 'round', - 'strokeLineJoin': 'bevil', + deepEqual(polygonWithAttrs.toObject(), fabric.util.object.extend(REFERENCE_OBJECT, { + 'width': 20, + 'height': 20, + 'fill': 'rgb(255,255,255)', + 'stroke': 'blue', + 'strokeWidth': 3, + 'strokeDashArray': [5, 2], + 'strokeLineCap': 'round', + 'strokeLineJoin': 'bevil', 'strokeMiterLimit': 5, - 'opacity': 0.34, - 'points': expectedPoints - }), polygonWithAttrs.toObject()); + 'opacity': 0.34, + 'points': expectedPoints + })); - deepEqual([ 2, 0, 0, 2, -10, -20 ], polygonWithAttrs.get('transformMatrix')); + deepEqual(polygonWithAttrs.get('transformMatrix'), [ 2, 0, 0, 2, -10, -20 ]); var elPolygonWithoutPoints = fabric.document.createElement('polygon'); diff --git a/test/unit/polyline.js b/test/unit/polyline.js index f4f8a1cd..4eeb5037 100644 --- a/test/unit/polyline.js +++ b/test/unit/polyline.js @@ -91,7 +91,7 @@ var elPolylineWithAttrs = fabric.document.createElement('polyline'); elPolylineWithAttrs.setAttribute('points', '10,10 20,20 30,30 10,10'); elPolylineWithAttrs.setAttribute('fill', 'rgb(255,255,255)'); - elPolylineWithAttrs.setAttribute('fill-opacity', '0.34'); + elPolylineWithAttrs.setAttribute('opacity', '0.34'); elPolylineWithAttrs.setAttribute('stroke-width', '3'); elPolylineWithAttrs.setAttribute('stroke', 'blue'); elPolylineWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2)'); @@ -118,7 +118,7 @@ 'points': expectedPoints })); - deepEqual([ 2, 0, 0, 2, -10, -20 ], polylineWithAttrs.get('transformMatrix')); + deepEqual(polylineWithAttrs.get('transformMatrix'), [ 2, 0, 0, 2, -10, -20 ]); var elPolylineWithoutPoints = fabric.document.createElement('polyline'); diff --git a/test/unit/rect.js b/test/unit/rect.js index 71696cf1..3e0376d5 100644 --- a/test/unit/rect.js +++ b/test/unit/rect.js @@ -89,7 +89,7 @@ elRectWithAttrs.setAttribute('rx', 11); elRectWithAttrs.setAttribute('ry', 12); elRectWithAttrs.setAttribute('fill', 'rgb(255,255,255)'); - elRectWithAttrs.setAttribute('fill-opacity', 0.45); + elRectWithAttrs.setAttribute('opacity', 0.45); elRectWithAttrs.setAttribute('stroke', 'blue'); elRectWithAttrs.setAttribute('stroke-width', 3); elRectWithAttrs.setAttribute('stroke-dasharray', '5, 2'); @@ -102,20 +102,20 @@ ok(rectWithAttrs instanceof fabric.Rect); var expectedObject = fabric.util.object.extend(REFERENCE_RECT, { - left: 121, - top: 186.5, - width: 222, - height: 333, - fill: 'rgb(255,255,255)', - opacity: 0.45, - stroke: 'blue', - strokeWidth: 3, - strokeDashArray: [5, 2], - strokeLineCap: 'round', - strokeLineJoin: 'bevil', + left: 121, + top: 186.5, + width: 222, + height: 333, + fill: 'rgb(255,255,255)', + opacity: 0.45, + stroke: 'blue', + strokeWidth: 3, + strokeDashArray: [5, 2], + strokeLineCap: 'round', + strokeLineJoin: 'bevil', strokeMiterLimit: 5, - rx: 11, - ry: 12 + rx: 11, + ry: 12 }); deepEqual(rectWithAttrs.toObject(), expectedObject); }); @@ -136,6 +136,6 @@ var rect = new fabric.Rect({ width: 100, height: 100, rx: 20, ry: 30 }); var svg = rect.toSVG(); - equal('', svg); + equal(svg, ''); }); })(); \ No newline at end of file diff --git a/test/unit/text.js b/test/unit/text.js index 6cd9c37f..694168ce 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -16,7 +16,7 @@ 'height': 52, 'fill': 'rgb(0,0,0)', 'overlayFill': null, - 'stroke': '', + 'stroke': null, 'strokeWidth': 1, 'strokeDashArray': null, 'strokeLineCap': 'butt', @@ -98,9 +98,9 @@ text.set({ opacity: 0.123, fill: 'red', fontFamily: 'blah' }); - equal(0.123, text.getOpacity()); - equal('red', text.getFill()); - equal('blah', text.get('fontFamily')); + equal(text.getOpacity(), 0.123); + equal(text.getFill(), 'red'); + equal(text.get('fontFamily'), 'blah'); }); test('setColor', function(){ @@ -166,7 +166,7 @@ elTextWithAttrs.setAttribute('x', 10); elTextWithAttrs.setAttribute('y', 20); elTextWithAttrs.setAttribute('fill', 'rgb(255,255,255)'); - elTextWithAttrs.setAttribute('fill-opacity', 0.45); + elTextWithAttrs.setAttribute('opacity', 0.45); elTextWithAttrs.setAttribute('stroke', 'blue'); elTextWithAttrs.setAttribute('stroke-width', 3); elTextWithAttrs.setAttribute('stroke-dasharray', '5, 2'); @@ -215,10 +215,10 @@ test('dimensions after text change', function() { var text = new fabric.Text('x'); - equal(20, text.width); + equal(text.width, 20); text.setText('xx'); - equal(40, text.width); + equal(text.width, 40); }); test('setting fontFamily', function() { @@ -226,7 +226,7 @@ text.path = 'foobar.js'; text.set('fontFamily', 'foobar'); - equal('foobar', text.get('fontFamily')); + equal(text.get('fontFamily'), 'foobar'); }); })();