Large refactoring of Style handling for notable speed improvements.

This commit is contained in:
Jürg Lehni 2013-04-19 19:31:29 -07:00
parent 98f7c020bd
commit e1807214f4
12 changed files with 104 additions and 87 deletions

View file

@ -25,7 +25,7 @@
*/ */
var Point = this.Point = Base.extend(/** @lends Point# */{ var Point = this.Point = Base.extend(/** @lends Point# */{
_class: 'Point', _class: 'Point',
// Tell Base.read that the Point constructor supporst reading with index // Tell Base.read that the Point constructor supports reading with index
_readIndex: true, _readIndex: true,
/** /**

View file

@ -19,7 +19,7 @@
*/ */
var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{ var Rectangle = this.Rectangle = Base.extend(/** @lends Rectangle# */{
_class: 'Rectangle', _class: 'Rectangle',
// Tell Base.read that the Rectangle constructor supporst reading with index // Tell Base.read that the Rectangle constructor supports reading with index
_readIndex: true, _readIndex: true,
/** /**

View file

@ -24,7 +24,7 @@
*/ */
var Size = this.Size = Base.extend(/** @lends Size# */{ var Size = this.Size = Base.extend(/** @lends Size# */{
_class: 'Size', _class: 'Size',
// Tell Base.read that the Point constructor supporst reading with index // Tell Base.read that the Point constructor supports reading with index
_readIndex: true, _readIndex: true,
// DOCS: improve Size class description // DOCS: improve Size class description

View file

@ -170,10 +170,7 @@ this.Base = Base.inject(/** @lends Base# */{
if (!length) if (!length)
length = list.length - index; length = list.length - index;
var obj = list[index]; var obj = list[index];
if (obj instanceof this if (obj instanceof this || readNull && obj == null && length <= 1) {
// If the class defines _readNull, return null when nothing
// was provided
|| (proto._readNull || readNull) && obj == null && length <= 1) {
if (readIndex) if (readIndex)
list._index = index + 1; list._index = index + 1;
return obj && clone ? obj.clone() : obj; return obj && clone ? obj.clone() : obj;

View file

@ -156,7 +156,7 @@ var Item = this.Item = Base.extend(Callback, {
// Do not serialize styles on Groups and Layers, since they just unify // Do not serialize styles on Groups and Layers, since they just unify
// their children's own styles. // their children's own styles.
if (!(this instanceof Group)) if (!(this instanceof Group))
serialize(this._style.getDefaults()); serialize(this._style._defaults);
// There is no compact form for Item serialization, we always keep the // There is no compact form for Item serialization, we always keep the
// type. // type.
return [ this._class, props ]; return [ this._class, props ];
@ -2155,8 +2155,8 @@ var Item = this.Item = Base.extend(Callback, {
// When the matrix could be applied, we also need to transform // When the matrix could be applied, we also need to transform
// color styles with matrices (only gradients so far): // color styles with matrices (only gradients so far):
var style = this._style, var style = this._style,
fillColor = style._fillColor, fillColor = style.getFillColor(),
strokeColor = style._strokeColor; strokeColor = style.getStrokeColor();
if (fillColor) if (fillColor)
fillColor.transform(this._matrix); fillColor.transform(this._matrix);
if (strokeColor) if (strokeColor)
@ -2743,14 +2743,14 @@ var Item = this.Item = Base.extend(Callback, {
// We can access internal properties since we're only using this on // We can access internal properties since we're only using this on
// items without children, where styles would be merged. // items without children, where styles would be merged.
var style = this._style, var style = this._style,
width = style._strokeWidth, width = style.getStrokeWidth(),
join = style._strokeJoin, join = style.getStrokeJoin(),
cap = style._strokeCap, cap = style.getStrokeCap(),
limit = style._miterLimit, limit = style.getMiterLimit(),
fillColor = style._fillColor, fillColor = style.getFillColor(),
strokeColor = style._strokeColor, strokeColor = style.getStrokeColor(),
dashArray = style._dashArray, dashArray = style.getDashArray(),
dashOffset = style._dashOffset; dashOffset = style.getDashOffset();
if (width != null) if (width != null)
ctx.lineWidth = width; ctx.lineWidth = width;
if (join) if (join)

View file

@ -31,8 +31,8 @@ var Shape = this.Shape = Item.extend(/** @lends Shape# */{
size = this._size, size = this._size,
width = size.width, width = size.width,
height = size.height, height = size.height,
fillColor = style._fillColor, fillColor = style.getFillColor(),
strokeColor = style._strokeColor; strokeColor = style.getStrokeColor();
if (fillColor || strokeColor || param.clip) { if (fillColor || strokeColor || param.clip) {
ctx.beginPath(); ctx.beginPath();
switch (this._type) { switch (this._type) {

View file

@ -186,7 +186,7 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
_hitTest: function(point, options) { _hitTest: function(point, options) {
var res = this.base(point, Base.merge(options, { fill: false })); var res = this.base(point, Base.merge(options, { fill: false }));
if (!res && options.fill && this._style._fillColor) { if (!res && options.fill && this._style.getFillColor()) {
res = this._contains(point); res = this._contains(point);
res = res ? new HitResult('fill', res[0]) : null; res = res ? new HitResult('fill', res[0]) : null;
} }
@ -206,9 +206,9 @@ var CompoundPath = this.CompoundPath = PathItem.extend(/** @lends CompoundPath#
param.compound = false; param.compound = false;
if (!param.clip) { if (!param.clip) {
this._setStyles(ctx); this._setStyles(ctx);
if (style._fillColor) if (style.getFillColor())
ctx.fill(); ctx.fill();
if (style._strokeColor) if (style.getStrokeColor())
ctx.stroke(); ctx.stroke();
} }
} }

View file

@ -240,7 +240,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
// We only need to draw the connecting curve if it is not a line, and if // We only need to draw the connecting curve if it is not a line, and if
// the path is closed and has a stroke color, or if it is filled. // the path is closed and has a stroke color, or if it is filled.
// TODO: Verify this, sound dodgy // TODO: Verify this, sound dodgy
if (this._closed && style._strokeColor || style._fillColor) if (this._closed && style.getStrokeColor() || style.getFillColor())
addCurve(segments[segments.length - 1], segments[0], true); addCurve(segments[segments.length - 1], segments[0], true);
if (this._closed) if (this._closed)
parts.push('z'); parts.push('z');
@ -1599,8 +1599,9 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
hasFill: function() { hasFill: function() {
// If this path is part of a CompoundPath, we need to check that // If this path is part of a CompoundPath, we need to check that
// for fillColor too... // for fillColor too...
return this._style._fillColor || this._parent instanceof CompoundPath return this._style.getFillColor()
&& this._parent._style._fillColor; || this._parent._type === 'compound-path'
&& this._parent._style.getFillColor();
}, },
contains: function(point) { contains: function(point) {
@ -1637,8 +1638,8 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
// directly here: // directly here:
var style = this._style, var style = this._style,
tolerance = options.tolerance || 0, tolerance = options.tolerance || 0,
radius = (options.stroke && style._strokeColor radius = (options.stroke && style.getStrokeColor()
? style._strokeWidth / 2 : 0) + tolerance, ? style.getStrokeWidth() / 2 : 0) + tolerance,
loc, loc,
res; res;
// If we're asked to query for segments, ends or handles, do all that // If we're asked to query for segments, ends or handles, do all that
@ -1823,9 +1824,9 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
// since Path items do not have children, thus do not need style // since Path items do not have children, thus do not need style
// accessors for merged styles. // accessors for merged styles.
var style = this._style, var style = this._style,
fillColor = style._fillColor, fillColor = style.getFillColor(),
strokeColor = style._strokeColor, strokeColor = style.getStrokeColor(),
dashArray = style._dashArray, dashArray = style.getDashArray(),
drawDash = !paper.support.nativeDash && strokeColor drawDash = !paper.support.nativeDash && strokeColor
&& dashArray && dashArray.length; && dashArray && dashArray.length;
@ -1850,7 +1851,7 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
// Use CurveFlatteners to draw dashed paths: // Use CurveFlatteners to draw dashed paths:
ctx.beginPath(); ctx.beginPath();
var flattener = new PathFlattener(this), var flattener = new PathFlattener(this),
from = style._dashOffset, to, from = style.getDashOffset(), to,
i = 0; i = 0;
while (from < flattener.length) { while (from < flattener.length) {
to = from + dashArray[(i++) % dashArray.length]; to = from + dashArray[(i++) % dashArray.length];
@ -2298,16 +2299,16 @@ statics: {
} }
// TODO: Find a way to reuse 'bounds' cache instead? // TODO: Find a way to reuse 'bounds' cache instead?
if (!style._strokeColor || !style._strokeWidth) if (!style.getStrokeColor() || !style.getStrokeWidth())
return Path.getBounds(segments, closed, style, matrix); return Path.getBounds(segments, closed, style, matrix);
var radius = style._strokeWidth / 2, var radius = style.getStrokeWidth() / 2,
padding = getPenPadding(radius, matrix), padding = getPenPadding(radius, matrix),
bounds = Path.getBounds(segments, closed, style, matrix, padding), bounds = Path.getBounds(segments, closed, style, matrix, padding),
join = style._strokeJoin, join = style.getStrokeJoin(),
cap = style._strokeCap, cap = style.getStrokeCap(),
// miter is relative to stroke width. Divide it by 2 since we're // miter is relative to stroke width. Divide it by 2 since we're
// measuring half the distance below // measuring half the distance below
miter = style._miterLimit * style._strokeWidth / 2; miter = style.getMiterLimit() * style.getStrokeWidth() / 2;
// Create a rectangle of padding size, used for union with bounds // Create a rectangle of padding size, used for union with bounds
// further down // further down
var joinBounds = new Rectangle(new Size(padding).multiply(2)); var joinBounds = new Rectangle(new Size(padding).multiply(2));
@ -2429,11 +2430,11 @@ statics: {
// Delegate to handleBounds, but pass on radius values for stroke and // Delegate to handleBounds, but pass on radius values for stroke and
// joins. Hanlde miter joins specially, by passing the largets radius // joins. Hanlde miter joins specially, by passing the largets radius
// possible. // possible.
var strokeWidth = style._strokeColor ? style._strokeWidth : 0; var strokeWidth = style.getStrokeColor() ? style.getStrokeWidth() : 0;
return Path.getHandleBounds(segments, closed, style, matrix, return Path.getHandleBounds(segments, closed, style, matrix,
strokeWidth, strokeWidth,
style._strokeJoin == 'miter' style.getStrokeJoin() == 'miter'
? strokeWidth * style._miterLimit ? strokeWidth * style.getMiterLimit()
: strokeWidth); : strokeWidth);
} }
}}); }});

View file

@ -277,9 +277,7 @@ var Color = this.Color = Base.extend(new function() {
}, this); }, this);
}, /** @lends Color# */{ }, /** @lends Color# */{
_class: 'Color', _class: 'Color',
// Tell Base.read that we do not want null to be converted to a color. // Tell Base.read that the Point constructor supports reading with index
_readNull: true,
// Tell Base.read that the Point constructor supporst reading with index
_readIndex: true, _readIndex: true,
/** /**

View file

@ -68,11 +68,6 @@ var Style = this.Style = Base.extend(new function() {
justification: 'left' justification: 'left'
}; };
// Override default fillColor for text items
var textDefaults = Base.merge(defaults, {
fillColor: 'black'
});
var flags = { var flags = {
strokeWidth: /*#=*/ Change.STROKE, strokeWidth: /*#=*/ Change.STROKE,
strokeCap: /*#=*/ Change.STROKE, strokeCap: /*#=*/ Change.STROKE,
@ -86,14 +81,17 @@ var Style = this.Style = Base.extend(new function() {
var item = {}, var item = {},
fields = { fields = {
getDefaults: function() { _defaults: defaults,
return this._item instanceof TextItem ? textDefaults : defaults; // Override default fillColor for text items
} _textDefaults: Base.merge(defaults, {
fillColor: 'black'
})
}; };
Base.each(defaults, function(value, key) { Base.each(defaults, function(value, key) {
var isColor = /Color$/.test(key), var isColor = /Color$/.test(key),
part = Base.capitalize(key), part = Base.capitalize(key),
flag = flags[key],
set = 'set' + part, set = 'set' + part,
get = 'get' + part; get = 'get' + part;
@ -101,50 +99,61 @@ var Style = this.Style = Base.extend(new function() {
fields[set] = function(value) { fields[set] = function(value) {
var children = this._item && this._item._children; var children = this._item && this._item._children;
// Clone color objects since they reference their owner // Clone color objects since they reference their owner
value = isColor ? Color.read(arguments, 0, 0, true) : value; // value = isColor ? Color.read(arguments, 0, 0, true) : value;
// Only unify styles on children of Groups, excluding CompoundPaths. // Only unify styles on children of Groups, excluding CompoundPaths.
if (children && children.length > 0 if (children && children.length > 0
&& this._item._type !== 'compound-path') { && this._item._type !== 'compound-path') {
for (var i = 0, l = children.length; i < l; i++) for (var i = 0, l = children.length; i < l; i++)
children[i]._style[set](value); children[i]._style[set](value);
} else { } else {
var old = this['_' + key]; var old = this._values[key];
if (!Base.equals(old, value)) { if (old === undefined || !Base.equals(old, value)) {
if (isColor) { if (isColor) {
if (old) if (old)
delete old._owner; delete old._owner;
if (value) { if (value && value.constructor === Color)
value._owner = this._item; value._owner = this._item;
}
} }
this['_' + key] = value; this._values[key] = value;
// Notify the item of the style change STYLE is always set, // Notify the item of the style change STYLE is always set,
// additional flags come from _flags, as used for STROKE: // additional flags come from flags, as used for STROKE:
if (this._item) if (this._item)
this._item._changed(flags[key] || /*#=*/ Change.STYLE); this._item._changed(flag || /*#=*/ Change.STYLE);
} }
} }
}; };
fields[get] = function() { fields[get] = function() {
var style, var value,
children = this._item && this._item._children; children = this._item && this._item._children;
// If this item has children, walk through all of them and see if // If this item has children, walk through all of them and see if
// they all have the same style. // they all have the same style.
if (!children || children.length === 0 if (!children || children.length === 0
|| this._item._type === 'compound-path') || this._item._type === 'compound-path') {
return this['_' + key]; var value = this._values[key];
if (value === undefined) {
value = this._defaults[key];
if (value && value.clone)
value = value.clone();
this._values[key] = value;
} else if (isColor && !(value instanceof Color)) {
this._values[key] = value = Color.read([value], 0, 0, true, true);
if (value)
value._owner = this._item;
}
return value;
}
for (var i = 0, l = children.length; i < l; i++) { for (var i = 0, l = children.length; i < l; i++) {
var childStyle = children[i]._style[get](); var childValue = children[i]._style[get]();
if (!style) { if (!value) {
style = childStyle; value = childValue;
} else if (!Base.equals(style, childStyle)) { } else if (!Base.equals(value, childValue)) {
// If there is another item with a different // If there is another item with a different
// style, the style is not defined: // style, the style is not defined:
return undefined; return undefined;
} }
} }
return style; return value;
}; };
// Inject style getters and setters into the Item class, which redirect // Inject style getters and setters into the Item class, which redirect
@ -162,27 +171,37 @@ var Style = this.Style = Base.extend(new function() {
return fields; return fields;
}, /** @lends Style# */{ }, /** @lends Style# */{
initialize: function(style) { initialize: function(style) {
// If the passed style object is also a Style, clone its clonable fields // Keep values in a separate object that we can iterate over.
// rather than simply copying them. this._values = {};
var clone = style instanceof Style; if (this._item instanceof TextItem)
// Note: This relies on bean getters and setters that get implicetly this._defaults = this._textDefaults;
// called when getting from style[key] and setting on this[key]. if (style) {
return Base.each(this.getDefaults(), function(value, key) { // If the passed style object is also a Style, clone its clonable
value = style && style[key] || value; // fields rather than simply copying them.
this[key] = value && clone && value.clone var isStyle = style instanceof Style,
? value.clone() : value; // Use the other stlyle's _values object for iteration
}, this); values = isStyle ? style._values : style;
for (var key in values) {
if (key in this._defaults) {
var value = values[key];
// Delegate to setter, so Group styles work too.
this[key] = value && isStyle && value.clone
? value.clone() : value;
}
}
}
}, },
getLeading: function() { getLeading: function() {
// Override leading to return fontSize * 1.2 by default. // Override leading to return fontSize * 1.2 by default.
var leading = this.base(); var leading = this.base();
return leading != null ? leading : this._fontSize * 1.2; return leading != null ? leading : this.getFontSize() * 1.2;
}, },
getFontStyle: function() { getFontStyle: function() {
var size = this._fontSize; var size = this.getFontSize();
return (/[a-z]/i.test(size) ? size + ' ' : size + 'px ') + this._font; return (/[a-z]/i.test(size) ? size + ' ' : size + 'px ')
+ this.getFont();
}, },
statics: { statics: {

View file

@ -193,11 +193,13 @@ new function() {
function exportText(item) { function exportText(item) {
var attrs = getTransform(item, true), var attrs = getTransform(item, true),
style = item._style; style = item._style,
if (style._font != null) font = style.getFont(),
attrs['font-family'] = style._font; fontSize = style.getFontSize();
if (style._fontSize != null) if (font)
attrs['font-size'] = style._fontSize; attrs['font-family'] = font;
if (fontSize)
attrs['font-size'] = fontSize;
var node = createElement('text', attrs); var node = createElement('text', attrs);
node.textContent = item._content; node.textContent = item._content;
return node; return node;

View file

@ -68,9 +68,9 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{
ctx.textAlign = style.getJustification(); ctx.textAlign = style.getJustification();
for (var i = 0, l = lines.length; i < l; i++) { for (var i = 0, l = lines.length; i < l; i++) {
var line = lines[i]; var line = lines[i];
if (style._fillColor) if (style.getFillColor())
ctx.fillText(line, 0, 0); ctx.fillText(line, 0, 0);
if (style._strokeColor) if (style.getStrokeColor())
ctx.strokeText(line, 0, 0); ctx.strokeText(line, 0, 0);
ctx.translate(0, leading); ctx.translate(0, leading);
} }