#

API Documentation for: NEXT
Show: Inherited Protected Private Deprecated

# File:easeljs\display\Text.js

    /*
    * Text
    * Visit http://createjs.com/ for documentation, updates and examples.
    *
    * Copyright (c) 2010 gskinner.com, inc.
    *
    * Permission is hereby granted, free of charge, to any person
    * obtaining a copy of this software and associated documentation
    * files (the "Software"), to deal in the Software without
    * restriction, including without limitation the rights to use,
    * copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the
    * Software is furnished to do so, subject to the following
    * conditions:
    *
    * The above copyright notice and this permission notice shall be
    * included in all copies or substantial portions of the Software.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    * OTHER DEALINGS IN THE SOFTWARE.
    */

    /**
     * @module EaselJS
     */

    // namespace:
    this.createjs = this.createjs||{};

    (function() {
    	"use strict";

    // constructor:
    	/**
    	 * Display one or more lines of dynamic text (not user editable) in the display list. Line wrapping support (using the
    	 * lineWidth) is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text, you can position HTML
    	 * text above or below the canvas relative to items in the display list using the {{#crossLink "DisplayObject/localToGlobal"}}{{/crossLink}}
    	 * method, or using {{#crossLink "DOMElement"}}{{/crossLink}}.
    	 *
    	 * <b>Please note that Text does not support HTML text, and can only display one font style at a time.</b> To use
    	 * multiple font styles, you will need to create multiple text instances, and position them manually.
    	 *
    	 * <h4>Example</h4>
    	 *
    	 *      var text = new createjs.Text("Hello World", "20px Arial", "#ff7700");
    	 *      text.x = 100;
    	 *      text.textBaseline = "alphabetic";
    	 *
    	 * CreateJS Text supports web fonts (the same rules as Canvas). The font must be loaded and supported by the browser
    	 * before it can be displayed.
    	 *
    	 * <strong>Note:</strong> Text can be expensive to generate, so cache instances where possible. Be aware that not all
    	 * browsers will render Text exactly the same.
    	 * @class Text
    	 * @extends DisplayObject
    	 * @constructor
    	 * @param {String} [text] The text to display.
    	 * @param {String} [font] The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold
    	 * 36px Arial").
    	 * @param {String} [color] The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex.
    	 * "#F00", "red", or "#FF0000").
    	 **/
    	function Text(text, font, color) {
    		this.DisplayObject_constructor();

    	// public properties:
    		/**
    		 * The text to display.
    		 * @property text
    		 * @type String
    		 **/
    		this.text = text;

    		/**
    		 * The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial").
    		 * @property font
    		 * @type String
    		 **/
    		this.font = font;

    		/**
    		 * The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). Default is "#000".
    		 * It will also accept valid canvas fillStyle values.
    		 * @property color
    		 * @type String
    		 **/
    		this.color = color;

    		/**
    		 * The horizontal text alignment. Any of "start", "end", "left", "right", and "center". For detailed
    		 * information view the
    		 * <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles">
    		 * whatwg spec</a>. Default is "left".
    		 * @property textAlign
    		 * @type String
    		 **/
    		this.textAlign = "left";

    		/**
    		 * The vertical alignment point on the font. Any of "top", "hanging", "middle", "alphabetic", "ideographic", or
    		 * "bottom". For detailed information view the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles">
    		 * whatwg spec</a>. Default is "top".
    		 * @property textBaseline
    		 * @type String
    		*/
    		this.textBaseline = "top";

    		/**
    		 * The maximum width to draw the text. If maxWidth is specified (not null), the text will be condensed or
    		 * shrunk to make it fit in this width. For detailed information view the
    		 * <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles">
    		 * whatwg spec</a>.
    		 * @property maxWidth
    		 * @type Number
    		*/
    		this.maxWidth = null;

    		/**
    		 * If greater than 0, the text will be drawn as a stroke (outline) of the specified width.
    		 * @property outline
    		 * @type Number
    		 **/
    		this.outline = 0;

    		/**
    		 * Indicates the line height (vertical distance between baselines) for multi-line text. If null or 0,
    		 * the value of getMeasuredLineHeight is used.
    		 * @property lineHeight
    		 * @type Number
    		 **/
    		this.lineHeight = 0;

    		/**
    		 * Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null,
    		 * the text will not be wrapped.
    		 * @property lineWidth
    		 * @type Number
    		 **/
    		this.lineWidth = null;
    	}
    	var p = createjs.extend(Text, createjs.DisplayObject);

    	// TODO: deprecated
    	// p.initialize = function() {}; // searchable for devs wondering where it is. REMOVED. See docs for details.

    // static properties:
    	/**
    	 * @property _workingContext
    	 * @type CanvasRenderingContext2D
    	 * @private
    	 **/
    	var canvas = (createjs.createCanvas?createjs.createCanvas():document.createElement("canvas"));
    	if (canvas.getContext) { Text._workingContext = canvas.getContext("2d"); canvas.width = canvas.height = 1; }

    // constants:
    	/**
    	 * Lookup table for the ratio to offset bounds x calculations based on the textAlign property.
    	 * @property H_OFFSETS
    	 * @type Object
    	 * @protected
    	 * @static
    	 **/
    	Text.H_OFFSETS = {start: 0, left: 0, center: -0.5, end: -1, right: -1};

    	/**
    	 * Lookup table for the ratio to offset bounds y calculations based on the textBaseline property.
    	 * @property H_OFFSETS
    	 * @type Object
    	 * @protected
    	 * @static
    	 **/
    	Text.V_OFFSETS = {top: 0, hanging: -0.01, middle: -0.4, alphabetic: -0.8, ideographic: -0.85, bottom: -1};

    // public methods:
    	/**
    	 * Returns true or false indicating whether the display object would be visible if drawn to a canvas.
    	 * This does not account for whether it would be visible within the boundaries of the stage.
    	 * NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
    	 * @method isVisible
    	 * @return {Boolean} Whether the display object would be visible if drawn to a canvas
    	 **/
    	p.isVisible = function() {
    		var hasContent = this.cacheCanvas || (this.text != null && this.text !== "");
    		return !!(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && hasContent);
    	};

    	/**
    	 * Draws the Text into the specified context ignoring its visible, alpha, shadow, and transform.
    	 * Returns true if the draw was handled (useful for overriding functionality).
    	 * NOTE: This method is mainly for internal use, though it may be useful for advanced uses.
    	 * @method draw
    	 * @param {CanvasRenderingContext2D} ctx The canvas 2D context object to draw into.
    	 * @param {Boolean} ignoreCache Indicates whether the draw operation should ignore any current cache.
    	 * For example, used for drawing the cache (to prevent it from simply drawing an existing cache back
    	 * into itself).
    	 **/
    	p.draw = function(ctx, ignoreCache) {
    		if (this.DisplayObject_draw(ctx, ignoreCache)) { return true; }

    		var col = this.color || "#000";
    		if (this.outline) { ctx.strokeStyle = col; ctx.lineWidth = this.outline*1; }
    		else { ctx.fillStyle = col; }

    		this._drawText(this._prepContext(ctx));
    		return true;
    	};

    	/**
    	 * Returns the measured, untransformed width of the text without wrapping. Use getBounds for a more robust value.
    	 * @method getMeasuredWidth
    	 * @return {Number} The measured, untransformed width of the text.
    	 **/
    	p.getMeasuredWidth = function() {
    		return this._getMeasuredWidth(this.text);
    	};

    	/**
    	 * Returns an approximate line height of the text, ignoring the lineHeight property. This is based on the measured
    	 * width of a "M" character multiplied by 1.2, which provides an approximate line height for most fonts.
    	 * @method getMeasuredLineHeight
    	 * @return {Number} an approximate line height of the text, ignoring the lineHeight property. This is
    	 * based on the measured width of a "M" character multiplied by 1.2, which approximates em for most fonts.
    	 **/
    	p.getMeasuredLineHeight = function() {
    		return this._getMeasuredWidth("M")*1.2;
    	};

    	/**
    	 * Returns the approximate height of multi-line text by multiplying the number of lines against either the
    	 * <code>lineHeight</code> (if specified) or {{#crossLink "Text/getMeasuredLineHeight"}}{{/crossLink}}. Note that
    	 * this operation requires the text flowing logic to run, which has an associated CPU cost.
    	 * @method getMeasuredHeight
    	 * @return {Number} The approximate height of the untransformed multi-line text.
    	 **/
    	p.getMeasuredHeight = function() {
    		return this._drawText(null,{}).height;
    	};

    	/**
    	 * Docced in superclass.
    	 */
    	p.getBounds = function() {
    		var rect = this.DisplayObject_getBounds();
    		if (rect) { return rect; }
    		if (this.text == null || this.text === "") { return null; }
    		var o = this._drawText(null, {});
    		var w = (this.maxWidth && this.maxWidth < o.width) ? this.maxWidth : o.width;
    		var x = w * Text.H_OFFSETS[this.textAlign||"left"];
    		var lineHeight = this.lineHeight||this.getMeasuredLineHeight();
    		var y = lineHeight * Text.V_OFFSETS[this.textBaseline||"top"];
    		return this._rectangle.setValues(x, y, w, o.height);
    	};

    	/**
    	 * Returns an object with width, height, and lines properties. The width and height are the visual width and height
    	 * of the drawn text. The lines property contains an array of strings, one for
    	 * each line of text that will be drawn, accounting for line breaks and wrapping. These strings have trailing
    	 * whitespace removed.
    	 * @method getMetrics
    	 * @return {Object} An object with width, height, and lines properties.
    	 **/
    	p.getMetrics = function() {
    		var o = {lines:[]};
    		o.lineHeight = this.lineHeight || this.getMeasuredLineHeight();
    		o.vOffset = o.lineHeight * Text.V_OFFSETS[this.textBaseline||"top"];
    		return this._drawText(null, o, o.lines);
    	};

    	/**
    	 * Returns a clone of the Text instance.
    	 * @method clone
    	 * @return {Text} a clone of the Text instance.
    	 **/
    	p.clone = function() {
    		return this._cloneProps(new Text(this.text, this.font, this.color));
    	};

    	/**
    	 * Returns a string representation of this object.
    	 * @method toString
    	 * @return {String} a string representation of the instance.
    	 **/
    	p.toString = function() {
    		return "[Text (text="+  (this.text.length > 20 ? this.text.substr(0, 17)+"..." : this.text) +")]";
    	};

    // private methods:
    	/**
    	 * @method _cloneProps
    	 * @param {Text} o
    	 * @protected
    	 * @return {Text} o
    	 **/
    	p._cloneProps = function(o) {
    		this.DisplayObject__cloneProps(o);
    		o.textAlign = this.textAlign;
    		o.textBaseline = this.textBaseline;
    		o.maxWidth = this.maxWidth;
    		o.outline = this.outline;
    		o.lineHeight = this.lineHeight;
    		o.lineWidth = this.lineWidth;
    		return o;
    	};

    	/**
    	 * @method _getWorkingContext
    	 * @param {CanvasRenderingContext2D} ctx
    	 * @return {CanvasRenderingContext2D}
    	 * @protected
    	 **/
    	p._prepContext = function(ctx) {
    		ctx.font = this.font||"10px sans-serif";
    		ctx.textAlign = this.textAlign||"left";
    		ctx.textBaseline = this.textBaseline||"top";
    		return ctx;
    	};

    	/**
    	 * Draws multiline text.
    	 * @method _drawText
    	 * @param {CanvasRenderingContext2D} ctx
    	 * @param {Object} o
    	 * @param {Array} lines
    	 * @return {Object}
    	 * @protected
    	 **/
    	p._drawText = function(ctx, o, lines) {
    		var paint = !!ctx;
    		if (!paint) {
    			ctx = Text._workingContext;
    			ctx.save();
    			this._prepContext(ctx);
    		}
    		var lineHeight = this.lineHeight||this.getMeasuredLineHeight();

    		var maxW = 0, count = 0;
    		var hardLines = String(this.text).split(/(?:\r\n|\r|\n)/);
    		for (var i=0, l=hardLines.length; i<l; i++) {
    			var str = hardLines[i];
    			var w = null;

    			if (this.lineWidth != null && (w = ctx.measureText(str).width) > this.lineWidth) {
    				// text wrapping:
    				var words = str.split(/(\s)/);
    				str = words[0];
    				w = ctx.measureText(str).width;

    				for (var j=1, jl=words.length; j<jl; j+=2) {
    					// Line needs to wrap:
    					var wordW = ctx.measureText(words[j] + words[j+1]).width;
    					if (w + wordW > this.lineWidth) {
    						if (paint) { this._drawTextLine(ctx, str, count*lineHeight); }
    						if (lines) { lines.push(str); }
    						if (w > maxW) { maxW = w; }
    						str = words[j+1];
    						w = ctx.measureText(str).width;
    						count++;
    					} else {
    						str += words[j] + words[j+1];
    						w += wordW;
    					}
    				}
    			}

    			if (paint) { this._drawTextLine(ctx, str, count*lineHeight); }
    			if (lines) { lines.push(str); }
    			if (o && w == null) { w = ctx.measureText(str).width; }
    			if (w > maxW) { maxW = w; }
    			count++;
    		}

    		if (o) {
    			o.width = maxW;
    			o.height = count*lineHeight;
    		}
    		if (!paint) { ctx.restore(); }
    		return o;
    	};

    	/**
    	 * @method _drawTextLine
    	 * @param {CanvasRenderingContext2D} ctx
    	 * @param {String} text
    	 * @param {Number} y
    	 * @protected
    	 **/
    	p._drawTextLine = function(ctx, text, y) {
    		// Chrome 17 will fail to draw the text if the last param is included but null, so we feed it a large value instead:
    		if (this.outline) { ctx.strokeText(text, 0, y, this.maxWidth||0xFFFF); }
    		else { ctx.fillText(text, 0, y, this.maxWidth||0xFFFF); }
    	};

    	/**
    	 * @method _getMeasuredWidth
    	 * @param {String} text
    	 * @protected
    	 **/
    	p._getMeasuredWidth = function(text) {
    		var ctx = Text._workingContext;
    		ctx.save();
    		var w = this._prepContext(ctx).measureText(text).width;
    		ctx.restore();
    		return w;
    	};

    	createjs.Text = createjs.promote(Text, "DisplayObject");
    }());