diff --git a/src/item/Item.js b/src/item/Item.js index 25a8d9de..64cb918d 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -59,9 +59,7 @@ var Item = this.Item = Base.extend(Callback, { // hierarchy. Used by Layer, where it's added to project.layers instead if (!this._project) paper.project.activeLayer.addChild(this); - // TextItem defines its own _style, based on CharacterStyle - if (!this._style) - this._style = PathStyle.create(this); + this._style = Style.create(this); this.setStyle(this._project.getCurrentStyle()); this._matrix = new Matrix(); if (point) @@ -158,7 +156,7 @@ var Item = this.Item = Base.extend(Callback, { // Do not serialize styles on Groups and Layers, since they just unify // their children's own styles. if (!(this instanceof Group)) - serialize(this._style._defaults); + serialize(this._style.getDefaults()); // There is no compact form for Item serialization, we always keep the // type. return [ this._class, props ]; @@ -166,7 +164,7 @@ var Item = this.Item = Base.extend(Callback, { /** * Private notifier that is called whenever a change occurs in this item or - * its sub-elements, such as Segments, Curves, PathStyles, etc. + * its sub-elements, such as Segments, Curves, Styles, etc. * * @param {ChangeFlag} flags describes what exactly has changed. */ @@ -314,7 +312,7 @@ var Item = this.Item = Base.extend(Callback, { * The path style of the item. * * @name Item#getStyle - * @type PathStyle + * @type Style * @bean * * @example {@paperscript} diff --git a/src/paper.js b/src/paper.js index fc500e0d..9e5e959f 100644 --- a/src/paper.js +++ b/src/paper.js @@ -82,9 +82,6 @@ var paper = new function() { /*#*/ include('text/PointText.js'); /*#*/ include('style/Style.js'); -/*#*/ include('style/PathStyle.js'); -/*#*/ include('style/ParagraphStyle.js'); -/*#*/ include('style/CharacterStyle.js'); /*#*/ include('color/Color.js'); /*#*/ include('color/Gradient.js'); diff --git a/src/project/Project.js b/src/project/Project.js index 24e9cda1..0fa7712c 100644 --- a/src/project/Project.js +++ b/src/project/Project.js @@ -53,7 +53,7 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{ this.activeLayer = new Layer(); if (view) this.view = view instanceof View ? view : View.create(view); - this._currentStyle = new PathStyle(); + this._currentStyle = new Style(); this._selectedItems = {}; this._selectedItemCount = 0; // See Item.draw() for an explanation of _drawCount @@ -107,7 +107,7 @@ var Project = this.Project = PaperScopeItem.extend(/** @lends Project# */{ * The currently active path style. All selected items and newly * created items will be styled with this style. * - * @type PathStyle + * @type Style * @bean * * @example {@paperscript} diff --git a/src/style/CharacterStyle.js b/src/style/CharacterStyle.js deleted file mode 100644 index d0d92e6b..00000000 --- a/src/style/CharacterStyle.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. - * http://paperjs.org/ - * - * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey - * http://lehni.org/ & http://jonathanpuckey.com/ - * - * Distributed under the MIT license. See LICENSE file for details. - * - * All rights reserved. - */ - -/** - * @name CharacterStyle - * - * @class The CharacterStyle object represents the character style of a text - * item ({@link TextItem#characterStyle}) - * - * @extends PathStyle - * - * @classexample - * var text = new PointText(new Point(50, 50)); - * text.content = 'Hello world.'; - * text.characterStyle = { - * fontSize: 50, - * fillColor: 'black', - * }; - */ -// Note that CharacterStyle extends PathStyle and thus injects the same -// accessors into its _owner TextItem, overriding those previously defined by -// PathStyle for Item. It is also returned from TextItem#getStyle instead of -// PathStyle. TextItem#characterStyle is now simply a pointer to #style. -var CharacterStyle = this.CharacterStyle = PathStyle.extend(/** @lends CharacterStyle# */{ - _owner: TextItem, - _style: 'style', - _defaults: Base.merge(PathStyle.prototype._defaults, { - // Override default fillColor of CharacterStyle - fillColor: 'black', - fontSize: 12, - leading: null, - font: 'sans-serif' - }), - _flags: { - fontSize: /*#=*/ Change.GEOMETRY, - leading: /*#=*/ Change.GEOMETRY, - font: /*#=*/ Change.GEOMETRY - } - - /** - * CharacterStyle objects don't need to be created directly. Just pass an - * object to {@link TextItem#characterStyle}, it will be converted to a - * CharacterStyle object internally. - * - * @name CharacterStyle#initialize - * @param {object} style - */ - - /** - * The font of the character style. - * - * @name CharacterStyle#font - * @default 'sans-serif' - * @type String - */ - - /** - * The font size of the character style, as {@Number} in pixels, or as - * {@String} with optional units {@code 'px'}, {@code 'pt'}, {@code 'em'}. - * - * @name CharacterStyle#fontSize - * @default 10 - * @type Number|String - */ -}, { - getLeading: function() { - // Override leading to return fontSize * 1.2 by default, when undefined - var leading = this.base(); - return leading != null ? leading : this.getFontSize() * 1.2; - }, - - getFontStyle: function() { - var size = this._fontSize; - return (/[a-z]/i.test(size) ? size + ' ' : size + 'px ') + this._font; - } -}); diff --git a/src/style/ParagraphStyle.js b/src/style/ParagraphStyle.js deleted file mode 100644 index 8825bbbe..00000000 --- a/src/style/ParagraphStyle.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. - * http://paperjs.org/ - * - * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey - * http://lehni.org/ & http://jonathanpuckey.com/ - * - * Distributed under the MIT license. See LICENSE file for details. - * - * All rights reserved. - */ - -/** - * @name ParagraphStyle - * - * @class The ParagraphStyle object represents the paragraph style of a text - * item ({@link TextItem#paragraphStyle}). - * - * Currently, the ParagraphStyle object may seem a bit empty, with just the - * {@link #justification} property. Yet, we have lots in store for Paper.js - * when it comes to typography. Please stay tuned. - * - * @classexample - * var text = new PointText(new Point(0,0)); - * text.fillColor = 'black'; - * text.content = 'Hello world.'; - * text.paragraphStyle.justification = 'center'; - */ -var ParagraphStyle = this.ParagraphStyle = Style.extend(/** @lends ParagraphStyle# */{ - _owner: TextItem, - _style: 'paragraphStyle', - _defaults: { - justification: 'left' - }, - _flags: { - justification: /*#=*/ Change.GEOMETRY - } - - /** - * ParagraphStyle objects don't need to be created directly. Just pass an - * object to {@link TextItem#paragraphStyle}, it will be converted to a - * ParagraphStyle object internally. - * - * @name ParagraphStyle#initialize - * @param {object} style - */ - - /** - * The justification of the paragraph. - * - * @name ParagraphStyle#justification - * @default 'left' - * @type String('left', 'right', 'center') - */ -}); diff --git a/src/style/PathStyle.js b/src/style/PathStyle.js deleted file mode 100644 index bcc162a3..00000000 --- a/src/style/PathStyle.js +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. - * http://paperjs.org/ - * - * Copyright (c) 2011 - 2013, Juerg Lehni & Jonathan Puckey - * http://lehni.org/ & http://jonathanpuckey.com/ - * - * Distributed under the MIT license. See LICENSE file for details. - * - * All rights reserved. - */ - -/** - * @name PathStyle - * - * @class PathStyle is used for changing the visual styles of items - * contained within a Paper.js project and is returned by - * {@link Item#style} and {@link Project#currentStyle}. - * - * All properties of PathStyle are also reflected directly in {@link Item}, - * i.e.: {@link Item#fillColor}. - * - * To set multiple style properties in one go, you can pass an object to - * {@link Item#style}. This is a convenient way to define a style once and - * apply it to a series of items: - * - * @classexample {@paperscript} - * var circleStyle = { - * fillColor: new Color(1, 0, 0), - * strokeColor: 'black', - * strokeWidth: 5 - * }; - * - * var path = new Path.Circle(new Point(80, 50), 30); - * path.style = circleStyle; - */ -var PathStyle = this.PathStyle = Style.extend(/** @lends PathStyle# */{ - _owner: Item, - _style: 'style', - // windingRule / resolution / fillOverprint / strokeOverprint are currently - // not supported. - _defaults: { - fillColor: undefined, - strokeColor: undefined, - strokeWidth: 1, - strokeCap: 'butt', - strokeJoin: 'miter', - miterLimit: 10, - dashOffset: 0, - dashArray: [] - }, - _flags: { - strokeWidth: /*#=*/ Change.STROKE, - strokeCap: /*#=*/ Change.STROKE, - strokeJoin: /*#=*/ Change.STROKE, - miterLimit: /*#=*/ Change.STROKE - } - - // DOCS: why isn't the example code showing up? - /** - * PathStyle objects don't need to be created directly. Just pass an - * object to {@link Item#style} or {@link Project#currentStyle}, it will - * be converted to a PathStyle object internally. - * - * @name PathStyle#initialize - * @param {object} style - */ - - /** - * {@grouptitle Stroke Style} - * - * The color of the stroke. - * - * @name PathStyle#strokeColor - * @property - * @type Color - * - * @example {@paperscript} - * // Setting the stroke color of a path: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle(new Point(80, 50), 35); - * - * // Set its stroke color to RGB red: - * circle.strokeColor = new Color(1, 0, 0); - */ - - /** - * The width of the stroke. - * - * @name PathStyle#strokeWidth - * @property - * @default 1 - * @type Number - * - * @example {@paperscript} - * // Setting an item's stroke width: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle(new Point(80, 50), 35); - * - * // Set its stroke color to black: - * circle.strokeColor = 'black'; - * - * // Set its stroke width to 10: - * circle.strokeWidth = 10; - */ - - /** - * The shape to be used at the end of open {@link Path} items, when they - * have a stroke. - * - * @name PathStyle#strokeCap - * @property - * @default 'butt' - * @type String('round', 'square', 'butt') - * - * @example {@paperscript height=200} - * // A look at the different stroke caps: - * - * var line = new Path(new Point(80, 50), new Point(420, 50)); - * line.strokeColor = 'black'; - * line.strokeWidth = 20; - * - * // Select the path, so we can see where the stroke is formed: - * line.selected = true; - * - * // Set the stroke cap of the line to be round: - * line.strokeCap = 'round'; - * - * // Copy the path and set its stroke cap to be square: - * var line2 = line.clone(); - * line2.position.y += 50; - * line2.strokeCap = 'square'; - * - * // Make another copy and set its stroke cap to be butt: - * var line2 = line.clone(); - * line2.position.y += 100; - * line2.strokeCap = 'butt'; - */ - - /** - * The shape to be used at the corners of paths when they have a stroke. - * - * @name PathStyle#strokeJoin - * @property - * @default 'miter' - * @type String('miter', 'round', 'bevel') - * - * @example {@paperscript height=120} - * // A look at the different stroke joins: - * var path = new Path(); - * path.add(new Point(80, 100)); - * path.add(new Point(120, 40)); - * path.add(new Point(160, 100)); - * path.strokeColor = 'black'; - * path.strokeWidth = 20; - * - * // Select the path, so we can see where the stroke is formed: - * path.selected = true; - * - * var path2 = path.clone(); - * path2.position.x += path2.bounds.width * 1.5; - * path2.strokeJoin = 'round'; - * - * var path3 = path2.clone(); - * path3.position.x += path3.bounds.width * 1.5; - * path3.strokeJoin = 'bevel'; - */ - - /** - * The dash offset of the stroke. - * - * @name PathStyle#dashOffset - * @property - * @default 0 - * @type Number - */ - - /** - * Specifies an array containing the dash and gap lengths of the stroke. - * - * @example {@paperscript} - * var path = new Path.Circle(new Point(80, 50), 40); - * path.strokeWidth = 2; - * path.strokeColor = 'black'; - * - * // Set the dashed stroke to [10pt dash, 4pt gap]: - * path.dashArray = [10, 4]; - * - * @name PathStyle#dashArray - * @property - * @default [] - * @type Array - */ - - /** - * The miter limit of the stroke. - * When two line segments meet at a sharp angle and miter joins have been - * specified for {@link #strokeJoin}, it is possible for the miter to extend - * far beyond the {@link #strokeWidth} of the path. The miterLimit imposes a - * limit on the ratio of the miter length to the {@link #strokeWidth}. - * - * @name PathStyle#miterLimit - * @property - * @default 10 - * @type Number - */ - - /** - * {@grouptitle Fill Style} - * - * The fill color. - * - * @name PathStyle#fillColor - * @property - * @type Color - * - * @example {@paperscript} - * // Setting the fill color of a path to red: - * - * // Create a circle shaped path at { x: 80, y: 50 } - * // with a radius of 35: - * var circle = new Path.Circle(new Point(80, 50), 35); - * - * // Set the fill color of the circle to RGB red: - * circle.fillColor = new Color(1, 0, 0); - */ -}); diff --git a/src/style/Style.js b/src/style/Style.js index c0a58621..65f70b0a 100644 --- a/src/style/Style.js +++ b/src/style/Style.js @@ -13,117 +13,402 @@ /** * @name Style * - * @class Internal base-class for all style objects, e.g. PathStyle, - * CharacterStyle, PargraphStyle. + * @class Style is used for changing the visual styles of items + * contained within a Paper.js project and is returned by + * {@link Item#style} and {@link Project#currentStyle}. * - * @private + * All properties of Style are also reflected directly in {@link Item}, + * i.e.: {@link Item#fillColor}. + * + * To set multiple style properties in one go, you can pass an object to + * {@link Item#style}. This is a convenient way to define a style once and + * apply it to a series of items: + * + * @classexample {@paperscript} + * + * var path = new Path.Circle(new Point(80, 50), 30); + * path.style = { + * fillColor: new Color(1, 0, 0), + * strokeColor: 'black', + * strokeWidth: 5 + * }; + * + * @classexample + * var text = new PointText(new Point(50, 50)); + * text.content = 'Hello world.'; + * text.style = { + * fontSize: 50, + * fillColor: 'black', + * }; + * + * @classexample + * var text = new PointText(new Point(0,0)); + * text.fillColor = 'black'; + * text.content = 'Hello world.'; + * text.justification = 'center'; */ -var Style = Base.extend({ +var Style = this.Style = Base.extend(new function() { + // windingRule / resolution / fillOverprint / strokeOverprint are currently + // not supported. + var defaults = { + // path styles + fillColor: undefined, + strokeColor: undefined, + strokeWidth: 1, + strokeCap: 'butt', + strokeJoin: 'miter', + miterLimit: 10, + dashOffset: 0, + dashArray: [], + // character styles + font: 'sans-serif', + fontSize: 12, + leading: null, + // paragraph styles + justification: 'left' + }; + + // Override default fillColor for text items + var textDefaults = Base.merge(defaults, { + fillColor: 'black' + }); + + var flags = { + strokeWidth: /*#=*/ Change.STROKE, + strokeCap: /*#=*/ Change.STROKE, + strokeJoin: /*#=*/ Change.STROKE, + miterLimit: /*#=*/ Change.STROKE, + font: /*#=*/ Change.GEOMETRY, + fontSize: /*#=*/ Change.GEOMETRY, + leading: /*#=*/ Change.GEOMETRY, + justification: /*#=*/ Change.GEOMETRY + }; + + var item = {}, + fields = { + getDefaults: function() { + return this._item instanceof TextItem ? textDefaults : defaults; + } + }; + + Base.each(defaults, function(value, key) { + var isColor = /Color$/.test(key), + part = Base.capitalize(key), + set = 'set' + part, + get = 'get' + part; + + // Define getters and setters to be injected into this class + fields[set] = function(value) { + var children = this._item && this._item._children; + // Clone color objects since they reference their owner + value = isColor ? Color.read(arguments, 0, 0, true) : value; + // Only unify styles on children of Groups, excluding CompoundPaths. + if (children && children.length > 0 + && this._item._type !== 'compound-path') { + for (var i = 0, l = children.length; i < l; i++) + children[i]._style[set](value); + } else { + var old = this['_' + key]; + if (!Base.equals(old, value)) { + if (isColor) { + if (old) + delete old._owner; + if (value) { + value._owner = this._item; + } + } + this['_' + key] = value; + // Notify the item of the style change STYLE is always set, + // additional flags come from _flags, as used for STROKE: + if (this._item) + this._item._changed(flags[key] || /*#=*/ Change.STYLE); + } + } + }; + + fields[get] = function() { + var style, + children = this._item && this._item._children; + // If this item has children, walk through all of them and see if + // they all have the same style. + if (!children || children.length === 0 + || this._item._type === 'compound-path') + return this['_' + key]; + for (var i = 0, l = children.length; i < l; i++) { + var childStyle = children[i]._style[get](); + if (!style) { + style = childStyle; + } else if (!Base.equals(style, childStyle)) { + // If there is another item with a different + // style, the style is not defined: + return undefined; + } + } + return style; + }; + + // Inject style getters and setters into the Item class, which redirect + // calls to the linked style object. + item[get] = function() { + return this._style[get](); + }; + + item[set] = function(value) { + this._style[set](value); + }; + }); + + // Define accessor on Item class for this style. + item.getStyle = function() { + return this._style; + }; + + item.setStyle = function(style) { + this._style.initialize(style); + }; + + Item.inject(item); + return fields; +}, /** @lends Style# */{ initialize: function(style) { - // If the passed style object is also a Style, clone its clonable - // fields rather than simply copying them. + // If the passed style object is also a Style, clone its clonable fields + // rather than simply copying them. var clone = style instanceof Style; // Note: This relies on bean getters and setters that get implicetly // called when getting from style[key] and setting on this[key]. - return Base.each(this._defaults, function(value, key) { + return Base.each(this.getDefaults(), function(value, key) { value = style && style[key] || value; this[key] = value && clone && value.clone ? value.clone() : value; }, this); }, + getLeading: function() { + // Override leading to return fontSize * 1.2 by default. + var leading = this.base(); + return leading != null ? leading : this._fontSize * 1.2; + }, + + getFontStyle: function() { + var size = this._fontSize; + return (/[a-z]/i.test(size) ? size + ' ' : size + 'px ') + this._font; + }, + statics: { create: function(item) { var style = Base.create(this); style._item = item; return style; - }, - - extend: function(src) { - // Inject style getters and setters into the 'owning' class, which - // redirect calls to the linked style objects through their internal - // property on the instances of that class, as defined by _style. - var styleKey = '_' + src._style, - stylePart = Base.capitalize(src._style), - flags = src._flags || {}, - owner = {}; - - // Define accessor on owner class for this style: - owner['get' + stylePart] = function() { - return this[styleKey]; - }; - - owner['set' + stylePart] = function(style) { - this[styleKey].initialize(style); - }; - - Base.each(src._defaults, function(value, key) { - var isColor = /Color$/.test(key), - part = Base.capitalize(key), - set = 'set' + part, - get = 'get' + part; - // Simply extend src with these getters and setters, to be - // injected into this class using this.base() further down. - src[set] = function(value) { - var children = this._item && this._item._children; - // Clone color objects since they reference their owner - value = isColor ? Color.read(arguments, 0, 0, true) : value; - // Only unify styles on children of Groups, excluding - // CompoundPaths. - if (children && children.length > 0 - && this._item._type !== 'compound-path') { - for (var i = 0, l = children.length; i < l; i++) - children[i][styleKey][set](value); - } else { - var old = this['_' + key]; - if (!Base.equals(old, value)) { - if (isColor) { - if (old) - delete old._owner; - if (value) { - value._owner = this._item; - } - } - this['_' + key] = value; - // Notify the item of the style change STYLE is - // always set, additional flags come from _flags, - // as used for STROKE: - if (this._item) - this._item._changed(flags[key] || /*#=*/ Change.STYLE); - } - } - }; - src[get] = function() { - var style, - children = this._item && this._item._children; - // If this item has children, walk through all of them and - // see if they all have the same style. - if (!children || children.length === 0 - || this._item._type === 'compound-path') - return this['_' + key]; - for (var i = 0, l = children.length; i < l; i++) { - var childStyle = children[i][styleKey][get](); - if (!style) { - style = childStyle; - } else if (!Base.equals(style, childStyle)) { - // If there is another item with a different - // style, the style is not defined: - return undefined; - } - } - return style; - }; - // Style-getters and setters for owner class: - owner[get] = function() { - return this[styleKey][get](); - }; - owner[set] = function(value) { - this[styleKey][set](value); - }; - }); - src._owner.inject(owner); - // Pass on to base() - return this.base.apply(this, arguments); } } + + // DOCS: why isn't the example code showing up? + /** + * Style objects don't need to be created directly. Just pass an object to + * {@link Item#style} or {@link Project#currentStyle}, it will be converted + * to a Style object internally. + * + * @name Style#initialize + * @param {object} style + */ + + /** + * {@grouptitle Stroke Style} + * + * The color of the stroke. + * + * @name Style#strokeColor + * @property + * @type Color + * + * @example {@paperscript} + * // Setting the stroke color of a path: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle(new Point(80, 50), 35); + * + * // Set its stroke color to RGB red: + * circle.strokeColor = new Color(1, 0, 0); + */ + + /** + * The width of the stroke. + * + * @name Style#strokeWidth + * @property + * @default 1 + * @type Number + * + * @example {@paperscript} + * // Setting an item's stroke width: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle(new Point(80, 50), 35); + * + * // Set its stroke color to black: + * circle.strokeColor = 'black'; + * + * // Set its stroke width to 10: + * circle.strokeWidth = 10; + */ + + /** + * The shape to be used at the end of open {@link Path} items, when they + * have a stroke. + * + * @name Style#strokeCap + * @property + * @default 'butt' + * @type String('round', 'square', 'butt') + * + * @example {@paperscript height=200} + * // A look at the different stroke caps: + * + * var line = new Path(new Point(80, 50), new Point(420, 50)); + * line.strokeColor = 'black'; + * line.strokeWidth = 20; + * + * // Select the path, so we can see where the stroke is formed: + * line.selected = true; + * + * // Set the stroke cap of the line to be round: + * line.strokeCap = 'round'; + * + * // Copy the path and set its stroke cap to be square: + * var line2 = line.clone(); + * line2.position.y += 50; + * line2.strokeCap = 'square'; + * + * // Make another copy and set its stroke cap to be butt: + * var line2 = line.clone(); + * line2.position.y += 100; + * line2.strokeCap = 'butt'; + */ + + /** + * The shape to be used at the corners of paths when they have a stroke. + * + * @name Style#strokeJoin + * @property + * @default 'miter' + * @type String('miter', 'round', 'bevel') + * + * @example {@paperscript height=120} + * // A look at the different stroke joins: + * var path = new Path(); + * path.add(new Point(80, 100)); + * path.add(new Point(120, 40)); + * path.add(new Point(160, 100)); + * path.strokeColor = 'black'; + * path.strokeWidth = 20; + * + * // Select the path, so we can see where the stroke is formed: + * path.selected = true; + * + * var path2 = path.clone(); + * path2.position.x += path2.bounds.width * 1.5; + * path2.strokeJoin = 'round'; + * + * var path3 = path2.clone(); + * path3.position.x += path3.bounds.width * 1.5; + * path3.strokeJoin = 'bevel'; + */ + + /** + * The dash offset of the stroke. + * + * @name Style#dashOffset + * @property + * @default 0 + * @type Number + */ + + /** + * Specifies an array containing the dash and gap lengths of the stroke. + * + * @example {@paperscript} + * var path = new Path.Circle(new Point(80, 50), 40); + * path.strokeWidth = 2; + * path.strokeColor = 'black'; + * + * // Set the dashed stroke to [10pt dash, 4pt gap]: + * path.dashArray = [10, 4]; + * + * @name Style#dashArray + * @property + * @default [] + * @type Array + */ + + /** + * The miter limit of the stroke. When two line segments meet at a sharp + * angle and miter joins have been specified for {@link #strokeJoin}, it is + * possible for the miter to extend far beyond the {@link #strokeWidth} of + * the path. The miterLimit imposes a limit on the ratio of the miter length + * to the {@link #strokeWidth}. + * + * @name Style#miterLimit + * @property + * @default 10 + * @type Number + */ + + /** + * {@grouptitle Fill Style} + * + * The fill color. + * + * @name Style#fillColor + * @property + * @type Color + * + * @example {@paperscript} + * // Setting the fill color of a path to red: + * + * // Create a circle shaped path at { x: 80, y: 50 } + * // with a radius of 35: + * var circle = new Path.Circle(new Point(80, 50), 35); + * + * // Set the fill color of the circle to RGB red: + * circle.fillColor = new Color(1, 0, 0); + */ + + /** + * {@grouptitle Character Style} + * + * The font to be used in text content. + * + * @name Style#font + * @default 'sans-serif' + * @type String + */ + + /** + * The font size of text content, as {@Number} in pixels, or as {@String} + * with optional units {@code 'px'}, {@code 'pt'} and {@code 'em'}. + * + * @name Style#fontSize + * @default 10 + * @type Number|String + */ + + /** + * The text leading of text content. + * + * @name Style#leading + * @default fontSize * 1.2 + * @type Number|String + */ + + /** + * {@grouptitle Character Style} + * + * The justification of text paragraphs. + * + * @name Style#justification + * @default 'left' + * @type String('left', 'right', 'center') + */ }); diff --git a/src/svg/SvgImport.js b/src/svg/SvgImport.js index 51ae0ce8..9b735245 100644 --- a/src/svg/SvgImport.js +++ b/src/svg/SvgImport.js @@ -292,18 +292,6 @@ new function() { color.setAlpha(parseFloat(value)); } - function applyTextAttribute(item, apply) { - if (item instanceof TextItem) { - apply(item); - } else if (item instanceof Group) { - // Text styles need to be recursively passed down to children that - // might be TextItems explicitely. - var children = item._children; - for (var i = 0, l = children.length; i < l; i++) - apply(children[i]); - } - } - // Create apply-functions for attributes, and merge in those for SvgStlyes: var attributes = Base.each(SvgStyles, function(entry) { this[entry.attribute] = function(item, value, name, node) { @@ -338,26 +326,20 @@ new function() { 'stroke-opacity': applyOpacity, 'font-family': function(item, value) { - applyTextAttribute(item, function(item) { - item.setFont(value.split(',')[0].replace(/^\s+|\s+$/g, '')); - }); + item.setFont(value.split(',')[0].replace(/^\s+|\s+$/g, '')); }, 'font-size': function(item, value) { - applyTextAttribute(item, function(item) { - item.setFontSize(parseFloat(value)); - }); + item.setFontSize(parseFloat(value)); }, 'text-anchor': function(item, value) { - applyTextAttribute(item, function(item) { - // http://www.w3.org/TR/SVG/text.html#TextAnchorProperty - item.setJustification({ - start: 'left', - middle: 'center', - end: 'right' - }[value]); - }); + // http://www.w3.org/TR/SVG/text.html#TextAnchorProperty + item.setJustification({ + start: 'left', + middle: 'center', + end: 'right' + }[value]); }, visibility: function(item, value) { diff --git a/src/text/PointText.js b/src/text/PointText.js index 7a6d791d..aa5a384d 100644 --- a/src/text/PointText.js +++ b/src/text/PointText.js @@ -62,10 +62,10 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{ return; this._setStyles(ctx); var style = this._style, - leading = this.getLeading(), - lines = this._lines; + lines = this._lines, + leading = style.getLeading(); ctx.font = style.getFontStyle(); - ctx.textAlign = this.getJustification(); + ctx.textAlign = style.getJustification(); for (var i = 0, l = lines.length; i < l; i++) { var line = lines[i]; if (style._fillColor) @@ -83,23 +83,24 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{ // Create an in-memory canvas on which to do the measuring if (!measureCtx) measureCtx = CanvasProvider.getContext(1, 1); - var justification = this.getJustification(), + var style = this._style, + lines = this._lines, + count = lines.length, + justification = style.getJustification(), + leading = style.getLeading(), x = 0; // Measure the real width of the text. Unfortunately, there is no // sane way to measure text height with canvas - measureCtx.font = this._style.getFontStyle(); + measureCtx.font = style.getFontStyle(); var width = 0; - for (var i = 0, l = this._lines.length; i < l; i++) - width = Math.max(width, measureCtx.measureText( - this._lines[i]).width); + for (var i = 0; i < count; i++) + width = Math.max(width, measureCtx.measureText(lines[i]).width); // Adjust for different justifications if (justification !== 'left') x -= width / (justification === 'center' ? 2: 1); - var leading = this.getLeading(), - count = this._lines.length, - // Until we don't have baseline measuring, assume leading / 4 as - // a rough guess: - bounds = Rectangle.create(x, + // Until we don't have baseline measuring, assume leading / 4 as a + // rough guess: + var bounds = Rectangle.create(x, count ? leading / 4 + (count - 1) * leading : 0, width, -count * leading); return matrix ? matrix._transformBounds(bounds, bounds) : bounds; diff --git a/src/text/TextItem.js b/src/text/TextItem.js index fe756541..b8e940cb 100644 --- a/src/text/TextItem.js +++ b/src/text/TextItem.js @@ -34,19 +34,11 @@ var TextItem = this.TextItem = Item.extend(/** @lends TextItem# */{ // Support two forms of item initialization: Passing one object literal // describing all the different properties to be set, or a point where // it should be placed (arg). - // Note that internally #characterStyle is the same as #style, but - // defined as an instance of CharacterStyle. We need to define it before - // calling this.base(), to override the default PathStyle instance. - this._style = CharacterStyle.create(this); - this._paragraphStyle = ParagraphStyle.create(this); // See if a point is passed, and if so, pass it on to base(). If not, it // might be a properties object literal for #setPropeties() at the end. var hasProperties = arg && Base.isPlainObject(arg) && arg.x === undefined && arg.y === undefined; this.base(hasProperties ? null : Point.read(arguments)); - // No need to call setStyle(), since base() handles this already. - // Call with no parameter to initalize defaults now. - this.setParagraphStyle(); this._content = ''; this._lines = []; if (hasProperties) @@ -87,7 +79,6 @@ var TextItem = this.TextItem = Item.extend(/** @lends TextItem# */{ _clone: function(copy) { copy.setContent(this._content); - copy.setParagraphStyle(this._paragraphStyle); return this.base(copy); }, @@ -106,28 +97,26 @@ var TextItem = this.TextItem = Item.extend(/** @lends TextItem# */{ }, /** - * {@grouptitle Style Properties} - * - * The character style of the text item. - * - * @type CharacterStyle - * @bean + * @private + * @deprecated use {@link #getStyle()} instead. */ - - // As explained in CharacterStyle, this is internally the same as #style. getCharacterStyle: function() { return this.getStyle(); }, setCharacterStyle: function(style) { this.setStyle(style); - } + }, /** - * The paragraph style of the text item. - * - * @name TextItem#getParagraphStyle - * @type ParagraphStyle - * @bean + * @private + * @deprecated use {@link #getStyle()} instead. */ + getParagraphStyle: function() { + return this.getStyle(); + }, + + setParagraphStyle: function(style) { + this.setStyle(style); + } }); diff --git a/test/lib/helpers.js b/test/lib/helpers.js index bc0a1c33..cf8c983f 100644 --- a/test/lib/helpers.js +++ b/test/lib/helpers.js @@ -93,7 +93,7 @@ function compareColors(color1, color2, message, precision) { (message || '') + ' components', precision); } -function comparePathStyles(style, style2, checkIdentity) { +function compareStyles(style, style2, checkIdentity) { if (checkIdentity) { equals(function() { return style !== style2; @@ -114,28 +114,19 @@ function comparePathStyles(style, style2, checkIdentity) { }, true, 'The ' + key + '.gradient should point to the same object:'); } compareColors(style[key], style2[key], - 'Compare PathStyle#' + key); + 'Compare Style#' + key); } else { equals(style[key] && style[key].toString(), style2[key] && style2[key].toString(), - 'Compare PathStyle#' + key); + 'Compare Style#' + key); } } }); - Base.each(['strokeCap', 'strokeJoin', 'dashOffset', 'miterLimit', - 'strokeOverprint', 'fillOverprint'], function(key) { - if (style[key]) { - equals(function() { - return style[key] == style2[key]; - }, true, 'Compare PathStyle#' + key); - } - }); - - if (style.dashArray) { - compareArrays(style.dashArray, style2.dashArray, - 'Compare CharacterStyle#dashArray'); - } + compareObjects('Style', ['strokeCap', 'strokeJoin', 'dashArray', + 'dashOffset', 'miterLimit', 'strokeOverprint', 'fillOverprint', + 'fontSize', 'font', 'leading', 'justification'], + style, style2, checkIdentity); } function compareObjects(name, keys, obj, obj2, checkIdentity) { @@ -145,20 +136,18 @@ function compareObjects(name, keys, obj, obj2, checkIdentity) { }, true); } Base.each(keys, function(key) { - equals(obj[key], obj2[key], 'Compare ' + name + '#' + key); + var val = obj[key], val2 = obj2[key], + message = 'Compare ' + name + '#' + key; + if (typeof val === 'number') { + compareNumbers(val, val2, message); + } else if (Array.isArray(val)) { + compareArrays(val, val2, message); + } else { + equals(val, val2, message); + } }); } -function compareCharacterStyles(characterStyle, characterStyle2, checkIdentity) { - compareObjects('CharacterStyle', ['fontSize', 'font'], - characterStyle, characterStyle2, checkIdentity); -} - -function compareParagraphStyles(paragraphStyle, paragraphStyle2, checkIdentity) { - compareObjects('ParagraphStyle', ['justification'], - paragraphStyle, paragraphStyle2, checkIdentity); -} - function compareSegmentPoints(segmentPoint, segmentPoint2, checkIdentity) { compareObjects('SegmentPoint', ['x', 'y', 'selected'], segmentPoint, segmentPoint2, checkIdentity); @@ -318,8 +307,6 @@ function compareItems(item, item2, cloned, checkIdentity, dontShareProject) { // TextItem specific: if (item instanceof TextItem) { equals(item.content, item2.content, 'Compare Item#content'); - compareCharacterStyles(item.characterStyle, item2.characterStyle, - checkIdentity); } // PointText specific: @@ -334,8 +321,8 @@ function compareItems(item, item2, cloned, checkIdentity, dontShareProject) { } if (item.style) { - // Path Style - comparePathStyles(item.style, item2.style, checkIdentity); + // Style + compareStyles(item.style, item2.style, checkIdentity); } // Check length of children and recursively compare them: diff --git a/test/tests/Item_Bounds.js b/test/tests/Item_Bounds.js index 8e0cafff..8978dbf8 100644 --- a/test/tests/Item_Bounds.js +++ b/test/tests/Item_Bounds.js @@ -54,4 +54,11 @@ test('path.bounds when contained in a transformed group', function() { compareRectangles(path.bounds, { x: 10, y: 10, width: 50, height: 50 }, 'path.bounds before group translation'); group.translate(100, 100); compareRectangles(path.bounds, { x: 10, y: 10, width: 50, height: 50 }, 'path.bounds after group translation'); -}); \ No newline at end of file +}); + +test('text.bounds', function() { + var text = new PointText(new Point(50, 100)); + text.fillColor = 'black'; + text.content = 'This is a test'; + compareRectangles(text.bounds, { x: 50, y: 89.2, width: 67, height: 14.4 } , 'text.bounds'); +}); diff --git a/test/tests/Item_Cloning.js b/test/tests/Item_Cloning.js index 09eabe32..28ad71f9 100644 --- a/test/tests/Item_Cloning.js +++ b/test/tests/Item_Cloning.js @@ -105,7 +105,7 @@ test('PointText#clone()', function() { var pointText = new PointText(new Point(50, 50)); pointText.content = 'test'; pointText.position = pointText.position.add(100); - pointText.characterStyle = { + pointText.style = { font: 'serif', fontSize: 20 }; diff --git a/test/tests/JSON.js b/test/tests/JSON.js index 9f01a04d..85f8d6d0 100644 --- a/test/tests/JSON.js +++ b/test/tests/JSON.js @@ -143,8 +143,8 @@ test('PointText testing', function() { text.content = 'This is also a test'; text.rotate(45); - text.shear(.85, .15); - text.scale(.85, 2); + text.shear(0.85, 0.15); + text.scale(0.85, 2); testExportImportJson(paper.project); }); diff --git a/test/tests/PathStyle.js b/test/tests/Style.js similarity index 99% rename from test/tests/PathStyle.js rename to test/tests/Style.js index 0b642fb5..e0d830fc 100644 --- a/test/tests/PathStyle.js +++ b/test/tests/Style.js @@ -10,7 +10,7 @@ * All rights reserved. */ -module('Path Style'); +module('Style'); test('style defaults', function() { var path = new Path(); diff --git a/test/tests/load.js b/test/tests/load.js index cf0a0a40..3c305d22 100644 --- a/test/tests/load.js +++ b/test/tests/load.js @@ -29,7 +29,7 @@ /*#*/ include('Segment.js'); /*#*/ include('Path.js'); -/*#*/ include('PathStyle.js'); +/*#*/ include('Style.js'); /*#*/ include('Path_Shapes.js'); /*#*/ include('Path_Drawing_Commands.js'); /*#*/ include('Path_Curves.js');