Better strokeDashArray support + Fixes

- fabric.Text has now strokeDashArray support (only native support)
- fabric.Text.fill = null should now work
- Fix save/restore context in render methods => setLineDash affected drawBorder/drawControls
- Add strokeLineCap (default "butt"), strokeLineJoin (default "miter") and strokeMiterLimit (default 10)
- Add support for fabric.Object#fromElement for strokeDashArray (and other stroke properties)
- Add @default tag to properties (JSDoc 3)
- strokeDashArray now only works if stroke property is defined
- Add trokeLineCap (default "round"), strokeLineJoin (default "round") to fabric.BaseBrush
- Updated unit tests
This commit is contained in:
Kienz 2013-05-18 13:01:34 +02:00
parent 4491b24a75
commit d80fec5df1
30 changed files with 781 additions and 462 deletions

View file

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

View file

@ -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

View file

@ -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

View file

@ -35,6 +35,7 @@
/**
* Type of an object
* @type String
* @default
*/
type: 'group',

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -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 <path> element

View file

@ -24,12 +24,14 @@
/**
* Type of an object
* @type String
* @default
*/
type: 'path-group',
/**
* Fill value
* @type String
* @default
*/
fill: '',

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -20,6 +20,7 @@
/**
* Type of an object
* @type String
* @default
*/
type: 'triangle',

View file

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

View file

@ -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

View file

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

View file

@ -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() {

View file

@ -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 = '<g transform="translate(80 117.5)"><rect x="-5" y="-20" rx="0" ry="0" width="10" height="40" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(-30 2.5)"/><rect x="-15" y="-5" rx="0" ry="0" width="30" height="10" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(20 -17.5)"/></g>';
var expectedSVG = '<g transform="translate(80 117.5)"><rect x="-5" y="-20" rx="0" ry="0" width="10" height="40" style="stroke: none; stroke-width: 1; stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); opacity: 1;" transform="translate(-30 2.5)"/><rect x="-15" y="-5" rx="0" ry="0" width="30" height="10" style="stroke: none; stroke-width: 1; stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); opacity: 1;" transform="translate(20 -17.5)"/></g>';
equal(group.toSVG(), expectedSVG);
});

View file

@ -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() {

View file

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

View file

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

View file

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

View file

@ -13,6 +13,9 @@
'stroke': null,
'strokeWidth': 1,
'strokeDashArray': null,
'strokeLineCap': 'butt',
'strokeLineJoin': 'miter',
'strokeMiterLimit': 10,
'scaleX': 1,
'scaleY': 1,
'angle': 0,

View file

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

View file

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

View file

@ -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('<rect x="-50" y="-50" rx="20" ry="30" width="100" height="100" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: rgb(0,0,0); opacity: 1;" transform="translate(0 0)"/>', svg);
equal('<rect x="-50" y="-50" rx="20" ry="30" width="100" height="100" style="stroke: none; stroke-width: 1; stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); opacity: 1;" transform="translate(0 0)"/>', svg);
});
})();

View file

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