diff --git a/src/base_brush.class.js b/src/base_brush.class.js index 1825f116..9f87533b 100644 --- a/src/base_brush.class.js +++ b/src/base_brush.class.js @@ -7,38 +7,58 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype /** * Color of a brush * @type String + * @default */ - color: 'rgb(0, 0, 0)', + color: 'rgb(0, 0, 0)', /** * Width of a brush * @type Number + * @default */ - width: 1, + width: 1, /** * Shadow blur of a brush * @type Number + * @default */ - shadowBlur: 0, + shadowBlur: 0, /** * Shadow color of a brush * @type String + * @default */ - shadowColor: '', + shadowColor: '', /** * Shadow offset x of a brush * @type Number + * @default */ - shadowOffsetX: 0, + shadowOffsetX: 0, /** * Shadow offset y of a brush * @type Number + * @default */ - shadowOffsetY: 0, + shadowOffsetY: 0, + + /** + * Line endings style of a brush (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'round', + + /** + * Corner style of a brush (one of "bevil", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'round', /** * Sets brush styles @@ -48,7 +68,8 @@ fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype ctx.strokeStyle = this.color; ctx.lineWidth = this.width; - ctx.lineCap = ctx.lineJoin = 'round'; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; }, /** diff --git a/src/circle.class.js b/src/circle.class.js index 265dd353..13fccff7 100644 --- a/src/circle.class.js +++ b/src/circle.class.js @@ -21,6 +21,7 @@ /** * Type of an object * @type String + * @default */ type: 'circle', @@ -132,7 +133,10 @@ * @static * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement */ - fabric.Circle.ATTRIBUTE_NAMES = 'cx cy r fill fill-opacity opacity stroke stroke-width transform'.split(' '); + fabric.Circle.ATTRIBUTE_NAMES = ( + 'cx cy r fill fill-opacity opacity stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * Returns {@link fabric.Circle} instance from an SVG element diff --git a/src/ellipse.class.js b/src/ellipse.class.js index 54ec115b..703e12c0 100644 --- a/src/ellipse.class.js +++ b/src/ellipse.class.js @@ -22,20 +22,23 @@ /** * Type of an object * @type String + * @default */ type: 'ellipse', /** * Horizontal radius * @type Number + * @default */ - rx: 0, + rx: 0, /** * Vertical radius * @type Number + * @default */ - ry: 0, + ry: 0, /** * Constructor @@ -138,7 +141,10 @@ * @static * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement */ - fabric.Ellipse.ATTRIBUTE_NAMES = 'cx cy rx ry fill fill-opacity opacity stroke stroke-width transform'.split(' '); + fabric.Ellipse.ATTRIBUTE_NAMES = ( + 'cx cy rx ry fill fill-opacity opacity stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * Returns {@link fabric.Ellipse} instance from an SVG element diff --git a/src/group.class.js b/src/group.class.js index cdcddf89..b06ba15e 100644 --- a/src/group.class.js +++ b/src/group.class.js @@ -35,6 +35,7 @@ /** * Type of an object * @type String + * @default */ type: 'group', diff --git a/src/image.class.js b/src/image.class.js index 53080a5b..56ce8580 100644 --- a/src/image.class.js +++ b/src/image.class.js @@ -23,6 +23,7 @@ /** * Type of an object * @type String + * @default */ type: 'image', @@ -104,6 +105,7 @@ this.transform(ctx); } + ctx.save(); this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx); @@ -112,6 +114,7 @@ } this._renderStroke(ctx); this.clipTo && ctx.restore(); + ctx.restore(); if (this.active && !noTransform) { this.drawBorders(ctx); @@ -127,7 +130,12 @@ _stroke: function(ctx) { ctx.save(); ctx.lineWidth = this.strokeWidth; - ctx.strokeStyle = this.stroke; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + 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(); @@ -145,7 +153,12 @@ h = this.height; ctx.lineWidth = this.strokeWidth; - ctx.strokeStyle = this.stroke; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + 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); @@ -434,7 +447,7 @@ * @static * @see http://www.w3.org/TR/SVG/struct.html#ImageElement */ - fabric.Image.ATTRIBUTE_NAMES = 'x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href'.split(' '); + fabric.Image.ATTRIBUTE_NAMES = 'x y width height fill fill-opacity opacity transform xlink:href'.split(' '); /** * Returns {@link fabric.Image} instance from an SVG element diff --git a/src/line.class.js b/src/line.class.js index 1b11b0e7..add3e896 100644 --- a/src/line.class.js +++ b/src/line.class.js @@ -22,6 +22,7 @@ /** * Type of an object * @type String + * @default */ type: 'line', @@ -175,7 +176,10 @@ * @static * @see http://www.w3.org/TR/SVG/shapes.html#LineElement */ - fabric.Line.ATTRIBUTE_NAMES = 'x1 y1 x2 y2 stroke stroke-width transform'.split(' '); + fabric.Line.ATTRIBUTE_NAMES = ( + 'x1 y1 x2 y2 stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * Returns fabric.Line instance from an SVG element diff --git a/src/object.class.js b/src/object.class.js index 905a5446..4bf1d19f 100644 --- a/src/object.class.js +++ b/src/object.class.js @@ -22,147 +22,191 @@ /** * Type of an object (rect, circle, path, etc.) * @type String + * @default */ type: 'object', /** * Horizontal origin of transformation of an object (one of "left", "right", "center") * @type String + * @default */ originX: 'center', /** * Vertical origin of transformation of an object (one of "top", "bottom", "center") * @type String + * @default */ originY: 'center', /** * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} * @type Number + * @default */ top: 0, /** * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} * @type Number + * @default */ left: 0, /** * Object width * @type Number + * @default */ width: 0, /** * Object height * @type Number + * @default */ height: 0, /** * Object scale factor (horizontal) * @type Number + * @default */ scaleX: 1, /** * Object scale factor (vertical) * @type Number + * @default */ scaleY: 1, /** * When true, an object is rendered as flipped horizontally * @type Boolean + * @default */ flipX: false, /** * When true, an object is rendered as flipped vertically * @type Boolean + * @default */ flipY: false, /** * Opacity of an object * @type Number + * @default */ opacity: 1, /** * Angle of rotation of an object (in degrees) * @type Number + * @default */ angle: 0, /** * Size of object's corners (in pixels) * @type Number + * @default */ cornerSize: 12, /** * When true, object's corners are rendered as transparent inside (i.e. stroke instead of fill) * @type Boolean + * @default */ transparentCorners: true, /** * Padding between object and its borders (in pixels) * @type Number + * @default */ padding: 0, /** * Border color of an object (when it's active) * @type String + * @default */ borderColor: 'rgba(102,153,255,0.75)', /** * Corner color of an object (when it's active) * @type String + * @default */ cornerColor: 'rgba(102,153,255,0.5)', /** * Color of object's fill * @type String + * @default */ fill: 'rgb(0,0,0)', /** * Fill rule used to fill an object * @type String + * @default */ fillRule: 'source-over', /** * Overlay fill (takes precedence over fill value) * @type String + * @default */ overlayFill: null, /** * When `true`, an object is rendered via stroke and this property specifies its color * @type String + * @default */ stroke: null, /** * Width of a stroke used to render this object * @type Number + * @default */ strokeWidth: 1, /** - * Array specifying dash pattern of an object's stroke + * Array specifying dash pattern of an object's stroke (stroke must be defined) * @type Array */ strokeDashArray: null, + /** + * Line endings style of an object's stroke (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'butt', + + /** + * Corner style of an object's stroke (one of "bevil", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'miter', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke + * @type Number + * @default + */ + strokeMiterLimit: 10, + /** * Shadow object representing shadow of this shape * @type fabric.Shadow @@ -172,12 +216,14 @@ /** * Border opacity when object is active and moving * @type Number + * @default */ borderOpacityWhenMoving: 0.4, /** * Border scale factor * @type Number + * @default */ borderScaleFactor: 1, @@ -190,54 +236,63 @@ /** * Minimum allowed scale value of an object * @type Number + * @default */ minScaleLimit: 0.01, /** * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection) * @type Boolean + * @default */ selectable: true, /** * When set to `false`, an object is not rendered on canvas * @type Boolean + * @default */ visible: true, /** * When set to `false`, object's controls are not displayed and can not be used to manipulate object * @type Boolean + * @default */ hasControls: true, /** * When set to `false`, object's borders are not rendered * @type Boolean + * @default */ hasBorders: true, /** * When set to `false`, object's rotating point will not be visible or selectable * @type Boolean + * @default */ hasRotatingPoint: true, /** * Offset for object's rotating point (when enabled via `hasRotatingPoint`) * @type Number + * @default */ rotatingPointOffset: 40, /** * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box * @type Boolean + * @default */ perPixelTargetFind: false, /** * When `false`, default object's values are not included in its serialization * @type Boolean + * @default */ includeDefaultValues: true, @@ -250,38 +305,44 @@ /** * When `true`, object horizontal movement is locked * @type Boolean + * @default */ - lockMovementX: false, + lockMovementX: false, /** * When `true`, object vertical movement is locked * @type Boolean + * @default */ - lockMovementY: false, + lockMovementY: false, /** * When `true`, object rotation is locked * @type Boolean + * @default */ - lockRotation: false, + lockRotation: false, /** * When `true`, object horizontal scaling is locked * @type Boolean + * @default */ - lockScalingX: false, + lockScalingX: false, /** * When `true`, object vertical scaling is locked * @type Boolean + * @default */ - lockScalingY: false, + lockScalingY: false, /** * When `true`, object non-uniform scaling is locked * @type Boolean + * @default */ - lockUniScaling: false, + lockUniScaling: false, /** * List of properties to consider when checking if state @@ -386,8 +447,11 @@ fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, overlayFill: this.overlayFill, stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, - strokeWidth: this.strokeWidth, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS), @@ -431,6 +495,9 @@ "stroke: ", (this.stroke ? this.stroke : 'none'), "; ", "stroke-width: ", (this.strokeWidth ? this.strokeWidth : '0'), "; ", "stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : ''), "; ", + "stroke-linecap: ", (this.strokeLineCap ? this.strokeLineCap : 'butt'), "; ", + "stroke-linejoin: ", (this.strokeLineJoin ? this.strokeLineJoin : 'miter'), "; ", + "stroke-miterlimit: ", (this.strokeMiterLimit ? this.strokeMiterLimit : '4'), "; ", "fill: ", (this.fill ? (this.fill && this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) : 'none'), "; ", "opacity: ", (typeof this.opacity !== 'undefined' ? this.opacity : '1'), ";", (this.visible ? '' : " visibility: hidden;") @@ -600,14 +667,15 @@ this.transform(ctx); } - if (this.stroke || this.strokeDashArray) { + ctx.save(); + if (this.stroke) { ctx.lineWidth = this.strokeWidth; - if (this.stroke && this.stroke.toLive) { - ctx.strokeStyle = this.stroke.toLive(ctx); - } - else { - ctx.strokeStyle = this.stroke; - } + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + ctx.strokeStyle = this.stroke.toLive + ? this.stroke.toLive(ctx) + : this.stroke; } if (this.overlayFill) { @@ -629,6 +697,7 @@ this._render(ctx, noTransform); this.clipTo && ctx.restore(); this._removeShadow(ctx); + ctx.restore(); if (this.active && !noTransform) { this.drawBorders(ctx); @@ -686,7 +755,7 @@ * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderStroke: function(ctx) { - if (!this.stroke && !this.strokeDashArray) return; + if (!this.stroke) return; if (this.strokeDashArray) { // Spec requires the concatenation of two copies the dash list when the number of elements is odd diff --git a/src/parser.js b/src/parser.js index f69a5971..66617b06 100644 --- a/src/parser.js +++ b/src/parser.js @@ -22,6 +22,10 @@ 'fill-opacity': 'opacity', 'fill-rule': 'fillRule', 'stroke-width': 'strokeWidth', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit':'strokeMiterLimit', 'transform': 'transformMatrix', 'text-decoration': 'textDecoration', 'font-size': 'fontSize', @@ -39,20 +43,31 @@ } function normalizeValue(attr, value, parentAttributes) { + var isArray; + if ((attr === 'fill' || attr === 'stroke') && value === 'none') { - return ''; + value = ''; } - if (attr === 'fill-rule') { - return (value === 'evenodd') ? 'destination-over' : value; + else if (attr === 'fillRule') { + value = (value === 'evenodd') ? 'destination-over' : value; } - if (attr === 'transform') { + else if (attr === 'strokeDashArray') { + value = value.replace(/,/g, ' ').split(/\s+/); + } + else if (attr === 'transformMatrix') { if (parentAttributes && parentAttributes.transformMatrix) { - return multiplyTransformMatrices( + value = multiplyTransformMatrices( parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); } - return fabric.parseTransformAttribute(value); + value = fabric.parseTransformAttribute(value); } - return value; + + isArray = Object.prototype.toString.call(value) === '[object Array]'; + + // TODO: need to normalize em, %, pt, etc. to px (!) + var parsed = isArray ? value.map(parseFloat) : parseFloat(value); + + return (!isArray && isNaN(parsed) ? value : parsed); } /** @@ -71,7 +86,6 @@ } var value, - parsed, parentAttributes = { }; // if there's a parent container (`g` node), parse its attributes recursively upwards @@ -81,12 +95,11 @@ var ownAttributes = attributes.reduce(function(memo, attr) { value = element.getAttribute(attr); - parsed = parseFloat(value); if (value) { - value = normalizeValue(attr, value, parentAttributes); attr = normalizeAttr(attr); + value = normalizeValue(attr, value, parentAttributes); - memo[attr] = isNaN(parsed) ? value : parsed; + memo[attr] = value; } return memo; }, { }); @@ -324,24 +337,23 @@ */ function parseStyleAttribute(element) { var oStyle = { }, - style = element.getAttribute('style'); + style = element.getAttribute('style'), + attr, value; if (!style) return oStyle; if (typeof style === 'string') { style.replace(/;$/, '').split(';').forEach(function (chunk) { - var pair = chunk.split(':'); - var attr = normalizeAttr(pair[0].trim().toLowerCase()); - var value = normalizeValue(attr, pair[1].trim()); + + attr = normalizeAttr(pair[0].trim().toLowerCase()); + value = normalizeValue(attr, pair[1].trim()); if (attr === 'font') { parseFontDeclaration(value, oStyle); } else { - // TODO: need to normalize em, %, pt, etc. to px (!) - var parsed = parseFloat(value); - oStyle[attr] = isNaN(parsed) ? value : parsed; + oStyle[attr] = value; } }); } @@ -349,16 +361,14 @@ for (var prop in style) { if (typeof style[prop] === 'undefined') continue; - var attr = normalizeAttr(prop.toLowerCase()); - var value = normalizeValue(attr, style[prop]); + attr = normalizeAttr(prop.toLowerCase()); + value = normalizeValue(attr, style[prop]); if (attr === 'font') { parseFontDeclaration(value, oStyle); } else { - // TODO: need to normalize em, %, pt, etc. to px (!) - var parsed = parseFloat(value); - oStyle[attr] = isNaN(parsed) ? value : parsed; + oStyle[attr] = value; } } } diff --git a/src/path.class.js b/src/path.class.js index 4322f62a..848e46aa 100644 --- a/src/path.class.js +++ b/src/path.class.js @@ -164,6 +164,7 @@ /** * Type of an object * @type String + * @default */ type: 'path', @@ -539,6 +540,7 @@ } // ctx.globalCompositeOperation = this.fillRule; + ctx.save(); if (this.overlayFill) { ctx.fillStyle = this.overlayFill; } @@ -549,27 +551,25 @@ } if (this.stroke) { + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke; } + this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); - ctx.beginPath(); this._render(ctx); this._renderFill(ctx); - - if (this.stroke) { - ctx.strokeStyle = this.stroke; - ctx.lineWidth = this.strokeWidth; - ctx.lineCap = ctx.lineJoin = 'round'; - this._renderStroke(ctx); - } + this._renderStroke(ctx); this.clipTo && ctx.restore(); - this._removeShadow(ctx); + ctx.restore(); if (!noTransform && this.active) { this.drawBorders(ctx); @@ -796,7 +796,10 @@ * @static * @see http://www.w3.org/TR/SVG/paths.html#PathElement */ - fabric.Path.ATTRIBUTE_NAMES = 'd fill fill-opacity opacity fill-rule stroke stroke-width transform'.split(' '); + fabric.Path.ATTRIBUTE_NAMES = ( + 'd fill fill-opacity opacity fill-rule stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * Creates an instance of fabric.Path from an SVG element diff --git a/src/path_group.class.js b/src/path_group.class.js index 045eca7d..cabb1bfd 100644 --- a/src/path_group.class.js +++ b/src/path_group.class.js @@ -24,12 +24,14 @@ /** * Type of an object * @type String + * @default */ type: 'path-group', /** * Fill value * @type String + * @default */ fill: '', diff --git a/src/pencil_brush.class.js b/src/pencil_brush.class.js index bcd36814..09cf4d29 100644 --- a/src/pencil_brush.class.js +++ b/src/pencil_brush.class.js @@ -209,6 +209,8 @@ path.fill = null; path.stroke = this.color; path.strokeWidth = this.width; + path.strokeLineCap = this.strokeLineCap; + path.strokeLineJoin = this.strokeLineJoin; path.setShadow({ color: this.shadowColor || this.color, blur: this.shadowBlur, diff --git a/src/polygon.class.js b/src/polygon.class.js index 387f2555..b658bba6 100644 --- a/src/polygon.class.js +++ b/src/polygon.class.js @@ -23,6 +23,7 @@ /** * Type of an object * @type String + * @default */ type: 'polygon', @@ -161,7 +162,10 @@ * @static * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement */ - fabric.Polygon.ATTRIBUTE_NAMES = 'fill fill-opacity opacity stroke stroke-width transform'.split(' '); + fabric.Polygon.ATTRIBUTE_NAMES = ( + 'fill fill-opacity opacity stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * Returns {@link fabric.Polygon} instance from an SVG element diff --git a/src/polyline.class.js b/src/polyline.class.js index 9ef226b6..4b0ef280 100644 --- a/src/polyline.class.js +++ b/src/polyline.class.js @@ -20,6 +20,7 @@ /** * Type of an object * @type String + * @default */ type: 'polyline', @@ -132,7 +133,10 @@ * @static * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement */ - fabric.Polyline.ATTRIBUTE_NAMES = 'fill fill-opacity opacity stroke stroke-width transform'.split(' '); + fabric.Polyline.ATTRIBUTE_NAMES = ( + 'fill fill-opacity opacity stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * Returns fabric.Polyline instance from an SVG element diff --git a/src/rect.class.js b/src/rect.class.js index 372263ff..0c435645 100644 --- a/src/rect.class.js +++ b/src/rect.class.js @@ -21,20 +21,23 @@ /** * Type of an object * @type String + * @default */ type: 'rect', /** * Horizontal border radius * @type Number + * @default */ - rx: 0, + rx: 0, /** * Vertical border radius * @type Number + * @default */ - ry: 0, + ry: 0, /** * Used to specify dash pattern for stroke on this object @@ -210,7 +213,10 @@ * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) * @static */ - fabric.Rect.ATTRIBUTE_NAMES = 'x y width height rx ry fill fill-opacity opacity stroke stroke-width transform'.split(' '); + fabric.Rect.ATTRIBUTE_NAMES = ( + 'x y width height rx ry fill fill-opacity opacity stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform' + ).split(' '); /** * @private diff --git a/src/text.class.js b/src/text.class.js index fee71793..f8d01abe 100644 --- a/src/text.class.js +++ b/src/text.class.js @@ -5,7 +5,8 @@ var fabric = global.fabric || (global.fabric = { }), extend = fabric.util.object.extend, clone = fabric.util.object.clone, - toFixed = fabric.util.toFixed; + toFixed = fabric.util.toFixed, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); if (fabric.Text) { fabric.warn('fabric.Text is already defined'); @@ -54,90 +55,105 @@ /** * Font size (in pixels) * @type Number + * @default */ fontSize: 40, /** * Font weight (e.g. bold, normal, 400, 600, 800) * @type Number + * @default */ fontWeight: 'normal', /** * Font family * @type String + * @default */ fontFamily: 'Times New Roman', /** * Text decoration (e.g. underline, overline) * @type String + * @default */ textDecoration: '', /** * Text shadow * @type String | null + * @default */ textShadow: '', /** * Text alignment. Possible values: "left", "center", or "right". * @type String + * @default */ textAlign: 'left', /** * Font style (e.g. italic) * @type String + * @default */ fontStyle: '', /** * Line height * @type Number + * @default */ 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 + * @default */ backgroundColor: '', /** * Background color of text lines * @type String + * @default */ textBackgroundColor: '', /** * URL of a font file, when using Cufon * @type String | null + * @default */ 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 + * @default */ useNative: true, @@ -278,12 +294,14 @@ ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); } + ctx.save(); this._setTextShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._renderTextFill(ctx, textLines); this._renderTextStroke(ctx, textLines); this.clipTo && ctx.restore(); this.textShadow && ctx.restore(); + ctx.restore(); if (this.textAlign !== 'left' && this.textAlign !== 'justify') { ctx.restore(); @@ -319,11 +337,20 @@ * @private */ _setTextStyles: function(ctx) { - ctx.fillStyle = this.fill.toLive - ? this.fill.toLive(ctx) - : this.fill; - ctx.strokeStyle = this.stroke; - ctx.lineWidth = this.strokeWidth; + if (this.fill) { + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) + : this.fill; + } + if (this.stroke) { + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + ctx.strokeStyle = this.stroke.toLive + ? this.stroke.toLive(ctx) + : this.stroke; + } ctx.textBaseline = 'alphabetic'; ctx.textAlign = this.textAlign; ctx.font = this._getFontDeclaration(); @@ -443,15 +470,17 @@ * @private */ _renderTextFill: function(ctx, textLines) { - this._boundaries = [ ]; - for (var i = 0, len = textLines.length; i < len; i++) { - this._drawTextLine( - 'fillText', - ctx, - textLines[i], - this._getLeftOffset(), - this._getTopOffset() + (i * this.fontSize * this.lineHeight) + this.fontSize - ); + 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 * this.fontSize * this.lineHeight) + this.fontSize + ); + } } }, @@ -460,6 +489,14 @@ */ _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); + } + ctx.beginPath(); for (var i = 0, len = textLines.length; i < len; i++) { this._drawTextLine( @@ -870,9 +907,11 @@ * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) * @static */ - fabric.Text.ATTRIBUTE_NAMES = - ('x y fill fill-opacity opacity stroke stroke-width transform ' + - 'font-family font-style font-weight font-size text-decoration').split(' '); + fabric.Text.ATTRIBUTE_NAMES = ( + 'x y fill fill-opacity opacity stroke stroke-width stroke-dasharray ' + + 'stroke-linejoin stroke-linecap stroke-miterlimit transform ' + + 'font-family font-style font-weight font-size text-decoration' + ).split(' '); /** * Returns fabric.Text instance from an object representation diff --git a/src/triangle.class.js b/src/triangle.class.js index 5f9a0ff1..016e166f 100644 --- a/src/triangle.class.js +++ b/src/triangle.class.js @@ -20,6 +20,7 @@ /** * Type of an object * @type String + * @default */ type: 'triangle', diff --git a/test/unit/canvas.js b/test/unit/canvas.js index 886af76f..8f503d62 100644 --- a/test/unit/canvas.js +++ b/test/unit/canvas.js @@ -22,12 +22,12 @@ '13.99], ["z", null]]}], "background": "#ff5555"}'; var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+ - '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+ - '"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,'+ + '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+ + '"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,'+ '"perPixelTargetFind":false,"shadow":null,"visible":true,"path":"http://example.com/"}],"background":""}'; var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+ - '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+ + '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,'+ '"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,'+ '"visible":true,"rx":0,"ry":0}],"background":"#ff5555"}'; diff --git a/test/unit/canvas_static.js b/test/unit/canvas_static.js index 6b1dd643..1cd2f644 100644 --- a/test/unit/canvas_static.js +++ b/test/unit/canvas_static.js @@ -20,19 +20,19 @@ '13.99], ["z", null]]}], "background": "#ff5555"}'; var PATH_DATALESS_JSON = '{"objects":[{"type":"path","originX":"center","originY":"center","left":200,"top":200,"width":200,"height":200,"fill":"rgb(0,0,0)",'+ - '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,'+ - '"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,'+ + '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+ + '"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,'+ '"perPixelTargetFind":false,"shadow":null,"visible":true,"path":"http://example.com/"}],"background":""}'; var RECT_JSON = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":10,"fill":"rgb(0,0,0)","overlayFill":null,'+ - '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+ - '"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true,"rx":0,"ry":0}],'+ - '"background":"#ff5555"}'; + '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+ + '"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+ + '"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true,"rx":0,"ry":0}],"background":"#ff5555"}'; var RECT_JSON_WITH_PADDING = '{"objects":[{"type":"rect","originX":"center","originY":"center","left":0,"top":0,"width":10,"height":20,"fill":"rgb(0,0,0)","overlayFill":null,'+ - '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,'+ - '"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true,"padding":123,"foo":"bar","rx":0,"ry":0}],'+ - '"background":""}'; + '"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+ + '"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+ + '"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true,"padding":123,"foo":"bar","rx":0,"ry":0}],"background":""}'; // force creation of static canvas // TODO: fix this diff --git a/test/unit/circle.js b/test/unit/circle.js index 7a479fdf..59d803e8 100644 --- a/test/unit/circle.js +++ b/test/unit/circle.js @@ -60,33 +60,36 @@ test('toObject', function() { var circle = new fabric.Circle(); var defaultProperties = { - 'type': 'circle', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 0, - 'height': 0, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'circle', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 0, + 'height': 0, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true, - 'radius': 0 + 'shadow': null, + 'visible': true, + 'radius': 0 }; ok(typeof circle.toObject == 'function'); deepEqual(circle.toObject(), defaultProperties); @@ -105,13 +108,17 @@ test('fromElement', function() { ok(typeof fabric.Circle.fromElement == 'function'); - var elCircle = fabric.document.createElement('circle'), - radius = 10, - left = 12, - top = 15, - fill = 'ff5555', - fillOpacity = 0.5, - strokeWidth = 2; + var elCircle = fabric.document.createElement('circle'), + radius = 10, + left = 12, + top = 15, + fill = 'ff5555', + fillOpacity = 0.5, + strokeWidth = 2, + strokeDashArray = [5, 2], + strokeLineCap = 'round', + strokeLineJoin = 'bevil', + strokeMiterLimit = 5; elCircle.setAttribute('r', radius); @@ -120,6 +127,10 @@ elCircle.setAttribute('fill', fill); elCircle.setAttribute('fill-opacity', fillOpacity); elCircle.setAttribute('stroke-width', strokeWidth); + elCircle.setAttribute('stroke-dasharray', '5, 2'); + elCircle.setAttribute('stroke-linecap', strokeLineCap); + elCircle.setAttribute('stroke-linejoin', strokeLineJoin); + elCircle.setAttribute('stroke-miterlimit', strokeMiterLimit); var oCircle = fabric.Circle.fromElement(elCircle); ok(oCircle instanceof fabric.Circle); @@ -130,6 +141,10 @@ equal(oCircle.get('fill'), fill); equal(oCircle.get('opacity'), fillOpacity); equal(oCircle.get('strokeWidth'), strokeWidth); + deepEqual(oCircle.get('strokeDashArray'), strokeDashArray); + equal(oCircle.get('strokeLineCap'), strokeLineCap); + equal(oCircle.get('strokeLineJoin'), strokeLineJoin); + equal(oCircle.get('strokeMiterLimit'), strokeMiterLimit); elFaultyCircle = fabric.document.createElement('circle'); elFaultyCircle.setAttribute('r', '-10'); diff --git a/test/unit/ellipse.js b/test/unit/ellipse.js index 68f9c8c5..cdac4bb6 100644 --- a/test/unit/ellipse.js +++ b/test/unit/ellipse.js @@ -22,34 +22,37 @@ test('toObject', function() { var ellipse = new fabric.Ellipse(); var defaultProperties = { - 'type': 'ellipse', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 0, - 'height': 0, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'rx': 0, - 'ry': 0, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'ellipse', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 0, + 'height': 0, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'rx': 0, + 'ry': 0, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; ok(typeof ellipse.toObject == 'function'); deepEqual(defaultProperties, ellipse.toObject()); @@ -83,14 +86,18 @@ test('fromElement', function() { ok(typeof fabric.Ellipse.fromElement == 'function'); - var elEllipse = fabric.document.createElement('ellipse'), - rx = 5, - ry = 7, - left = 12, - top = 15, - fill = 'ff5555', - fillOpacity = 0.5, - strokeWidth = 2; + var elEllipse = fabric.document.createElement('ellipse'), + rx = 5, + ry = 7, + left = 12, + top = 15, + fill = 'ff5555', + fillOpacity = 0.5, + strokeWidth = 2, + strokeDashArray = [5, 2], + strokeLineCap = 'round', + strokeLineJoin = 'bevil', + strokeMiterLimit = 5; elEllipse.setAttribute('rx', rx); elEllipse.setAttribute('ry', ry); @@ -99,6 +106,10 @@ elEllipse.setAttribute('fill', fill); elEllipse.setAttribute('fill-opacity', fillOpacity); elEllipse.setAttribute('stroke-width', strokeWidth); + elEllipse.setAttribute('stroke-dasharray', '5, 2'); + elEllipse.setAttribute('stroke-linecap', strokeLineCap); + elEllipse.setAttribute('stroke-linejoin', strokeLineJoin); + elEllipse.setAttribute('stroke-miterlimit', strokeMiterLimit); var oEllipse = fabric.Ellipse.fromElement(elEllipse); ok(oEllipse instanceof fabric.Ellipse); @@ -110,6 +121,10 @@ equal(oEllipse.get('fill'), fill); equal(oEllipse.get('opacity'), fillOpacity); equal(oEllipse.get('strokeWidth'), strokeWidth); + deepEqual(oEllipse.get('strokeDashArray'), strokeDashArray); + equal(oEllipse.get('strokeLineCap'), strokeLineCap); + equal(oEllipse.get('strokeLineJoin'), strokeLineJoin); + equal(oEllipse.get('strokeMiterLimit'), strokeMiterLimit); }); test('fromObject', function() { diff --git a/test/unit/group.js b/test/unit/group.js index b68710c5..1f8f1d4c 100644 --- a/test/unit/group.js +++ b/test/unit/group.js @@ -133,33 +133,36 @@ var clone = group.toObject(); var expectedObject = { - 'type': 'group', - 'originX': 'center', - 'originY': 'center', - 'left': 80, - 'top': 117.5, - 'width': 70, - 'height': 45, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'group', + 'originX': 'center', + 'originY': 'center', + 'left': 80, + 'top': 117.5, + 'width': 70, + 'height': 45, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'objects': clone.objects + 'shadow': null, + 'visible': true, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'objects': clone.objects }; deepEqual(clone, expectedObject); @@ -336,7 +339,7 @@ var group = makeGroupWith2Objects(); ok(typeof group.toSVG == 'function'); - var expectedSVG = ''; + var expectedSVG = ''; equal(group.toSVG(), expectedSVG); }); diff --git a/test/unit/image.js b/test/unit/image.js index 0d77a660..6be55c71 100644 --- a/test/unit/image.js +++ b/test/unit/image.js @@ -15,34 +15,37 @@ IMG_HEIGHT = 110; var REFERENCE_IMG_OBJECT = { - 'type': 'image', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects - 'height': IMG_HEIGHT, // or does it now? - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'src': fabric.isLikelyNode ? undefined : IMG_SRC, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'image', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': IMG_WIDTH, // node-canvas doesn't seem to allow setting width/height on image objects + 'height': IMG_HEIGHT, // or does it now? + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'src': fabric.isLikelyNode ? undefined : IMG_SRC, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true, - 'filters': [] + 'shadow': null, + 'visible': true, + 'filters': [] }; function _createImageElement() { diff --git a/test/unit/line.js b/test/unit/line.js index 263f153c..a33935be 100644 --- a/test/unit/line.js +++ b/test/unit/line.js @@ -1,36 +1,39 @@ (function(){ var LINE_OBJECT = { - 'type': 'line', - 'originX': 'center', - 'originY': 'center', - 'left': 12, - 'top': 13, - 'width': 2, - 'height': 2, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'x1': 11, - 'y1': 12, - 'x2': 13, - 'y2': 14, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'line', + 'originX': 'center', + 'originY': 'center', + 'left': 12, + 'top': 13, + 'width': 2, + 'height': 2, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'x1': 11, + 'y1': 12, + 'x2': 13, + 'y2': 14, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; QUnit.module('fabric.Line'); @@ -78,13 +81,17 @@ test('fromElement', function() { ok(typeof fabric.Line.fromElement == 'function'); - var lineEl = fabric.document.createElement('line'), - x1 = 11, - y1 = 23, - x2 = 34, - y2 = 7, - stroke = 'ff5555', - strokeWidth = 2; + var lineEl = fabric.document.createElement('line'), + x1 = 11, + y1 = 23, + x2 = 34, + y2 = 7, + stroke = 'ff5555', + strokeWidth = 2, + strokeDashArray = [5, 2], + strokeLineCap = 'round', + strokeLineJoin = 'bevil', + strokeMiterLimit = 5; lineEl.setAttribute('x1', x1); lineEl.setAttribute('x2', x2); @@ -92,6 +99,10 @@ lineEl.setAttribute('y2', y2); lineEl.setAttribute('stroke', stroke); lineEl.setAttribute('stroke-width', strokeWidth); + lineEl.setAttribute('stroke-dasharray', '5, 2'); + lineEl.setAttribute('stroke-linecap', strokeLineCap); + lineEl.setAttribute('stroke-linejoin', strokeLineJoin); + lineEl.setAttribute('stroke-miterlimit', strokeMiterLimit); var oLine = fabric.Line.fromElement(lineEl); ok(oLine instanceof fabric.Line); @@ -102,6 +113,10 @@ equal(oLine.get('y2'), y2); equal(oLine.get('stroke'), stroke); equal(oLine.get('strokeWidth'), strokeWidth); + deepEqual(oLine.get('strokeDashArray'), strokeDashArray); + equal(oLine.get('strokeLineCap'), strokeLineCap); + equal(oLine.get('strokeLineJoin'), strokeLineJoin); + equal(oLine.get('strokeMiterLimit'), strokeMiterLimit); var lineElWithMissingAttributes = fabric.document.createElement('line'); lineElWithMissingAttributes.setAttribute('x1', 10); diff --git a/test/unit/object.js b/test/unit/object.js index 48b2941f..d9c8456d 100644 --- a/test/unit/object.js +++ b/test/unit/object.js @@ -110,80 +110,94 @@ test('toJSON', function() { var emptyObjectJSON = '{"type":"object","originX":"center","originY":"center","left":0,"top":0,"width":0,"height":0,"fill":"rgb(0,0,0)",'+ - '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,'+ - '"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+ + '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeLineJoin":"miter","strokeMiterLimit":10,'+ + '"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+ '"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true}'; var augmentedJSON = '{"type":"object","originX":"center","originY":"center","left":0,"top":0,"width":122,"height":0,"fill":"rgb(0,0,0)",'+ - '"overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1.3,"scaleY":1,"angle":0,'+ - '"flipX":false,"flipY":true,"opacity":0.88,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+ + '"overlayFill":null,"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,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":true,'+ '"transparentCorners":true,"perPixelTargetFind":false,"shadow":null,"visible":true}'; var cObj = new fabric.Object(); ok(typeof cObj.toJSON == 'function'); equal(JSON.stringify(cObj.toJSON()), emptyObjectJSON); - cObj.set('opacity', 0.88).set('scaleX', 1.3).set('width', 122).set('flipY', true); + cObj.set('opacity', 0.88) + .set('scaleX', 1.3) + .set('width', 122) + .set('flipY', true) + .set('strokeDashArray', [5, 2]) + .set('strokeLineCap', 'round') + .set('strokeLineJoin', 'bevil') + .set('strokeMiterLimit', 5); + equal(JSON.stringify(cObj.toJSON()), augmentedJSON); }); test('toObject', function() { var emptyObjectRepr = { - 'type': "object", - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 0, - 'height': 0, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': "object", + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 0, + 'height': 0, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; var augmentedObjectRepr = { - 'type': "object", - 'originX': 'center', - 'originY': 'center', - 'left': 10, - 'top': 20, - 'width': 30, - 'height': 40, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': true, - 'flipY': false, - 'opacity': 0.13, - 'selectable': false, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': "object", + 'originX': 'center', + 'originY': 'center', + 'left': 10, + 'top': 20, + 'width': 30, + 'height': 40, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': [5, 2], + 'strokeLineCap': 'round', + 'strokeLineJoin': 'bevil', + 'strokeMiterLimit': 5, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': true, + 'flipY': false, + 'opacity': 0.13, + 'selectable': false, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; var cObj = new fabric.Object(); @@ -195,7 +209,11 @@ .set('height', 40) .set('flipX', true) .set('opacity', 0.13) - .set('selectable', false); + .set('selectable', false) + .set('strokeDashArray', [5, 2]) + .set('strokeLineCap', 'round') + .set('strokeLineJoin', 'bevil') + .set('strokeMiterLimit', 5); deepEqual(augmentedObjectRepr, cObj.toObject()); diff --git a/test/unit/path.js b/test/unit/path.js index 9e6b09a4..785c038d 100644 --- a/test/unit/path.js +++ b/test/unit/path.js @@ -1,33 +1,36 @@ (function() { var REFERENCE_PATH_OBJECT = { - 'type': 'path', - 'originX': 'center', - 'originY': 'center', - 'left': 200, - 'top': 200, - 'width': 200, - 'height': 200, - 'fill': 'red', - 'overlayFill': null, - 'stroke': 'blue', - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'path': [['M', 100, 100], ['L', 300, 100], ['L', 200, 300], ['z']], - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'path', + 'originX': 'center', + 'originY': 'center', + 'left': 200, + 'top': 200, + 'width': 200, + 'height': 200, + 'fill': 'red', + 'overlayFill': null, + 'stroke': 'blue', + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'path': [['M', 100, 100], ['L', 300, 100], ['L', 200, 300], ['z']], + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; function getPathElement(path) { @@ -35,7 +38,10 @@ el.setAttribute('d', path); el.setAttribute('fill', 'red'); el.setAttribute('stroke', 'blue'); - el.setAttribute('troke-width', 3); + el.setAttribute('stroke-width', 1); + el.setAttribute('stroke-linecap', 'butt'); + el.setAttribute('stroke-linejoin', 'miter'); + el.setAttribute('stroke-miterlimit', 10); return el; } @@ -114,16 +120,24 @@ elPath.setAttribute('fill-opacity', '1'); elPath.setAttribute('stroke', 'blue'); elPath.setAttribute('stroke-width', '1'); + elPath.setAttribute('stroke-dasharray', '5, 2'); + elPath.setAttribute('stroke-linecap', 'round'); + elPath.setAttribute('stroke-linejoin', 'bevil'); + elPath.setAttribute('stroke-miterlimit', '5'); // TODO (kangax): to support multiple transformation keywords, we need to do proper matrix multiplication - // elPath.setAttribute('transform', 'scale(2) translate(10, -20)'); + //elPath.setAttribute('transform', 'scale(2) translate(10, -20)'); elPath.setAttribute('transform', 'scale(2)'); var path = fabric.Path.fromElement(elPath); ok(path instanceof fabric.Path); deepEqual(fabric.util.object.extend(REFERENCE_PATH_OBJECT, { - transformMatrix: [2, 0, 0, 2, 0, 0] + strokeDashArray: [5, 2], + strokeLineCap: 'round', + strokeLineJoin: 'bevil', + strokeMiterLimit: 5, + transformMatrix: [2, 0, 0, 2, 0, 0] }), path.toObject()); var ANGLE = 90; diff --git a/test/unit/path_group.js b/test/unit/path_group.js index 266e1b1b..2ae0bf67 100644 --- a/test/unit/path_group.js +++ b/test/unit/path_group.js @@ -13,6 +13,9 @@ 'stroke': null, 'strokeWidth': 1, 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, 'scaleX': 1, 'scaleY': 1, 'angle': 0, diff --git a/test/unit/polygon.js b/test/unit/polygon.js index d2f8b9b6..286410b7 100644 --- a/test/unit/polygon.js +++ b/test/unit/polygon.js @@ -8,33 +8,36 @@ } var REFERENCE_OBJECT = { - 'type': 'polygon', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 10, - 'height': 10, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'points': getPoints(), - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'polygon', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 10, + 'height': 10, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'points': getPoints(), + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; QUnit.module('fabric.Polygon'); @@ -99,6 +102,10 @@ elPolygonWithAttrs.setAttribute('stroke-width', '3'); elPolygonWithAttrs.setAttribute('stroke', 'blue'); elPolygonWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2)'); + elPolygonWithAttrs.setAttribute('stroke-dasharray', '5, 2'); + elPolygonWithAttrs.setAttribute('stroke-linecap', 'round'); + elPolygonWithAttrs.setAttribute('stroke-linejoin', 'bevil'); + elPolygonWithAttrs.setAttribute('stroke-miterlimit', '5'); var polygonWithAttrs = fabric.Polygon.fromElement(elPolygonWithAttrs); var expectedPoints = [ @@ -114,6 +121,10 @@ '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()); diff --git a/test/unit/polyline.js b/test/unit/polyline.js index 36cf3665..c50e801f 100644 --- a/test/unit/polyline.js +++ b/test/unit/polyline.js @@ -8,33 +8,36 @@ } var REFERENCE_OBJECT = { - 'type': 'polyline', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 10, - 'height': 10, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'points': getPoints(), - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'polyline', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 10, + 'height': 10, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'points': getPoints(), + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true + 'shadow': null, + 'visible': true }; QUnit.module('fabric.Polyline'); @@ -92,6 +95,10 @@ elPolylineWithAttrs.setAttribute('stroke-width', '3'); elPolylineWithAttrs.setAttribute('stroke', 'blue'); elPolylineWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2)'); + elPolylineWithAttrs.setAttribute('stroke-dasharray', '5, 2'); + elPolylineWithAttrs.setAttribute('stroke-linecap', 'round'); + elPolylineWithAttrs.setAttribute('stroke-linejoin', 'bevil'); + elPolylineWithAttrs.setAttribute('stroke-miterlimit', '5'); var polylineWithAttrs = fabric.Polyline.fromElement(elPolylineWithAttrs); @@ -103,6 +110,10 @@ 'fill': 'rgb(255,255,255)', 'stroke': 'blue', 'strokeWidth': 3, + 'strokeDashArray': [5, 2], + 'strokeLineCap': 'round', + 'strokeLineJoin': 'bevil', + 'strokeMiterLimit': 5, 'opacity': 0.34, 'points': expectedPoints })); diff --git a/test/unit/rect.js b/test/unit/rect.js index 8a554bd5..71696cf1 100644 --- a/test/unit/rect.js +++ b/test/unit/rect.js @@ -1,34 +1,37 @@ (function() { var REFERENCE_RECT = { - 'type': 'rect', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 0, - 'height': 0, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': null, - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, + 'type': 'rect', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 0, + 'height': 0, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': null, + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, 'transparentCorners': true, 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true, - 'rx': 0, - 'ry': 0 + 'shadow': null, + 'visible': true, + 'rx': 0, + 'ry': 0 }; QUnit.module('fabric.Rect'); @@ -89,6 +92,10 @@ elRectWithAttrs.setAttribute('fill-opacity', 0.45); elRectWithAttrs.setAttribute('stroke', 'blue'); elRectWithAttrs.setAttribute('stroke-width', 3); + elRectWithAttrs.setAttribute('stroke-dasharray', '5, 2'); + elRectWithAttrs.setAttribute('stroke-linecap', 'round'); + elRectWithAttrs.setAttribute('stroke-linejoin', 'bevil'); + elRectWithAttrs.setAttribute('stroke-miterlimit', 5); //elRectWithAttrs.setAttribute('transform', 'translate(-10,-20) scale(2) rotate(45) translate(5,10)'); var rectWithAttrs = fabric.Rect.fromElement(elRectWithAttrs); @@ -103,6 +110,10 @@ opacity: 0.45, stroke: 'blue', strokeWidth: 3, + strokeDashArray: [5, 2], + strokeLineCap: 'round', + strokeLineJoin: 'bevil', + strokeMiterLimit: 5, rx: 11, ry: 12 }); @@ -125,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 5b487117..b28952d2 100644 --- a/test/unit/text.js +++ b/test/unit/text.js @@ -7,45 +7,48 @@ } var REFERENCE_TEXT_OBJECT = { - 'type': 'text', - 'originX': 'center', - 'originY': 'center', - 'left': 0, - 'top': 0, - 'width': 20, - 'height': 52, - 'fill': 'rgb(0,0,0)', - 'overlayFill': null, - 'stroke': '', - 'strokeWidth': 1, - 'strokeDashArray': null, - 'scaleX': 1, - 'scaleY': 1, - 'angle': 0, - 'flipX': false, - 'flipY': false, - 'opacity': 1, - 'selectable': true, - 'hasControls': true, - 'hasBorders': true, - 'hasRotatingPoint': true, - 'transparentCorners': true, - 'perPixelTargetFind': false, - 'shadow': null, - 'visible': true, - 'text': 'x', - 'fontSize': 40, - 'fontWeight': 'normal', - 'fontFamily': 'Times New Roman', - 'fontStyle': '', - 'lineHeight': 1.3, - 'textDecoration': '', - 'textShadow': '', - 'textAlign': 'left', - 'path': null, - 'backgroundColor': '', - 'textBackgroundColor': '', - 'useNative': true + 'type': 'text', + 'originX': 'center', + 'originY': 'center', + 'left': 0, + 'top': 0, + 'width': 20, + 'height': 52, + 'fill': 'rgb(0,0,0)', + 'overlayFill': null, + 'stroke': '', + 'strokeWidth': 1, + 'strokeDashArray': null, + 'strokeLineCap': 'butt', + 'strokeLineJoin': 'miter', + 'strokeMiterLimit': 10, + 'scaleX': 1, + 'scaleY': 1, + 'angle': 0, + 'flipX': false, + 'flipY': false, + 'opacity': 1, + 'selectable': true, + 'hasControls': true, + 'hasBorders': true, + 'hasRotatingPoint': true, + 'transparentCorners': true, + 'perPixelTargetFind': false, + 'shadow': null, + 'visible': true, + 'text': 'x', + 'fontSize': 40, + 'fontWeight': 'normal', + 'fontFamily': 'Times New Roman', + 'fontStyle': '', + 'lineHeight': 1.3, + 'textDecoration': '', + 'textShadow': '', + 'textAlign': 'left', + 'path': null, + 'backgroundColor': '', + 'textBackgroundColor': '', + 'useNative': true }; test('constructor', function() { @@ -165,6 +168,10 @@ elTextWithAttrs.setAttribute('fill-opacity', 0.45); elTextWithAttrs.setAttribute('stroke', 'blue'); elTextWithAttrs.setAttribute('stroke-width', 3); + elTextWithAttrs.setAttribute('stroke-dasharray', '5, 2'); + elTextWithAttrs.setAttribute('stroke-linecap', 'round'); + elTextWithAttrs.setAttribute('stroke-linejoin', 'bevil'); + elTextWithAttrs.setAttribute('stroke-miterlimit', 5); elTextWithAttrs.setAttribute('font-family', 'Monaco'); elTextWithAttrs.setAttribute('font-style', 'italic'); elTextWithAttrs.setAttribute('font-weight', 'bold'); @@ -179,19 +186,23 @@ var expectedObject = fabric.util.object.extend(fabric.util.object.clone(REFERENCE_TEXT_OBJECT), { /* left varies slightly due to node-canvas rendering */ - left: fabric.util.toFixed(textWithAttrs.left + '', 2), - top: -59.95, - width: 20, - height: 159.9, - fill: 'rgb(255,255,255)', - opacity: 0.45, - stroke: 'blue', - strokeWidth: 3, - fontFamily: 'Monaco', - fontStyle: 'italic', - fontWeight: 'bold', - fontSize: 123, - textDecoration: 'underline' + left: fabric.util.toFixed(textWithAttrs.left + '', 2), + top: -59.95, + width: 20, + height: 159.9, + fill: 'rgb(255,255,255)', + opacity: 0.45, + stroke: 'blue', + strokeWidth: 3, + strokeDashArray: [5, 2], + strokeLineCap: 'round', + strokeLineJoin: 'bevil', + strokeMiterLimit: 5, + fontFamily: 'Monaco', + fontStyle: 'italic', + fontWeight: 'bold', + fontSize: 123, + textDecoration: 'underline' }); deepEqual(textWithAttrs.toObject(), expectedObject);