1 /**
  2 * Text by Grant Skinner. Dec 5, 2010
  3 * Visit http://easeljs.com/ for documentation, updates and examples.
  4 *
  5 *
  6 * Copyright (c) 2010 Grant Skinner
  7 * 
  8 * Permission is hereby granted, free of charge, to any person
  9 * obtaining a copy of this software and associated documentation
 10 * files (the "Software"), to deal in the Software without
 11 * restriction, including without limitation the rights to use,
 12 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 13 * copies of the Software, and to permit persons to whom the
 14 * Software is furnished to do so, subject to the following
 15 * conditions:
 16 * 
 17 * The above copyright notice and this permission notice shall be
 18 * included in all copies or substantial portions of the Software.
 19 * 
 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 27 * OTHER DEALINGS IN THE SOFTWARE.
 28 */
 29 
 30 
 31 
 32 /**
 33 * Constructs a new Text instance.
 34 * @param text Optional. The text to display.
 35 * @param font Optional. The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "36px bold Arial").
 36 * @param color Optional. The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00").
 37 * @class Allows you to display one or more lines of dynamic text (not user editable) in the display list. Line wrapping
 38  * support (using the lineWidth is very basic, wrapping on spaces and tabs only. Note that as an alternative to Text,
 39  * you can position HTML text above or below the canvas relative to items in the display list using the localToGlobal() method.
 40 * @augments DisplayObject
 41 */
 42 function Text(text, font, color) {
 43   this.initialize(text, font, color);
 44 this.prototype = new DisplayObject();
 45 
 46 /** @private */
 47 var canvas = document.createElement("canvas");
 48 Text._workingContext = canvas.getContext("2d");
 49 
 50 // public properties:
 51 	/** The text to display. */
 52 	this.text = "";
 53 	/** The font style to use. Any valid value for the CSS font attribute is acceptable (ex. "bold 36px Arial"). */
 54 	this.font = null;
 55 	/** The color to draw the text in. Any valid value for the CSS color attribute is acceptable (ex. "#F00"). */
 56 	this.color = null;
 57 	/** 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-0">whatwg spec</a>. */
 58 	this.textAlign = null;
 59 	/** 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-0">whatwg spec</a>. */
 60 	this.textBaseline = null;
 61 	/** 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-0">whatwg spec</a>. */
 62 	this.maxWidth = null;
 63 	/** If true, the text will be drawn as a stroke (outline). If false, the text will be drawn as a fill. */
 64 	this.outline = false;
 65 	/** Indicates the line height (vertical distance between baselines) for multi-line text. If null, the value of getMeasuredLineHeight is used. */
 66 	this.lineHeight = null;
 67 	/** Indicates the maximum width for a line of text before it is wrapped to multiple lines. If null, the text will not be wrapped. */
 68 	this.lineWidth = null;
 69 	
 70 // constructor:
 71 	/** @private */
 72 	this.DisplayObject_initialize = this.initialize;
 73 	/** @private */
 74 	this.initialize = function(text, font, color) {
 75 		this.DisplayObject_initialize();
 76 		this.text = text;
 77 		this.font = font;
 78 		this.color = color ? color : "#000";
 79 	}
 80 	
 81 // public methods:
 82 	this.isVisible = function() {
 83 		return Boolean(this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0 && this.text != null && this.text != "");
 84 	}
 85 
 86 	this.DisplayObject_draw = this.draw;
 87 	this.draw = function(ctx,ignoreCache) {
 88 		if (this.DisplayObject_draw(ctx,ignoreCache)) { return true; }
 89 		
 90 		if (this.outline) { ctx.strokeStyle = this.color; }
 91 		else { ctx.fillStyle = this.color; }
 92 		ctx.font = this.font;
 93 		ctx.textAlign = this.textAlign ? this.textAlign : "start";
 94 		ctx.textBaseline = this.textBaseline ? this.textBaseline : "alphabetic";
 95 
 96 		var lines = String(this.text).split(/(?:\r\n|\r|\n)/);
 97 		var lineHeight = (this.lineHeight == null) ? this.getMeasuredLineHeight() : this.lineHeight;
 98 		var y = 0;
 99 		for (var i=0, l=lines.length; i<l; i++) {
100 			var w = ctx.measureText(lines[i]).width;
101 			if (this.lineWidth == null || w < this.lineWidth) {
102 				this._drawTextLine(ctx, lines[i], y);
103 				y += lineHeight;
104 				continue;
105 			}
106 
107 			// split up the line
108 			var words = lines[i].split(/(\s)/);
109 			var str = words[0];
110 			for (var j=1, jl=words.length; j<jl; j+=2) {
111 				// Line needs to wrap:
112 				if (ctx.measureText(str + words[j] + words[j+1]).width > this.lineWidth) {
113 					this._drawTextLine(ctx, str, y);
114 					y += lineHeight;
115 					str = words[j+1];
116 				} else {
117 					str += words[j] + words[j+1];
118 				}
119 			}
120 			this._drawTextLine(ctx, str, y); // Draw remaining text
121 			y += lineHeight;
122 		}
123 		return true;
124 	}
125 	
126 	/**
127 	* Returns the measured, untransformed width of the text.
128 	*/
129 	this.getMeasuredWidth = function() {
130 		return this._getWorkingContext().measureText(this.text).width;
131 	}
132 
133 	/**
134 	 * Returns an approximate line height of the text, ignoring the lineHeight property. This is based on the measured width of
135 	 * a "M" character multiplied by 1.2, which approximates em for most fonts.
136 	 */
137 	this.getMeasuredLineHeight = function() {
138 		return this._getWorkingContext().measureText("M").width*1.2;
139 	}
140 	
141 	this.clone = function() {
142 		var o = new Text(this.text, this.font, this.color);
143 		this.cloneProps(o);
144 		return o;
145 	}
146 		
147 	this.toString = function() {
148 		return "[Text (text="+  (this.text.length > 20 ? this.text.substr(0,17)+"..." : this.text) +")]";
149 	}
150 	
151 // private methods:
152 	
153 	/** @private */
154 	this.DisplayObject_cloneProps = this.cloneProps;
155 	/** @private */
156 	this.cloneProps = function(o) {
157 		this.DisplayObject_cloneProps(o);
158 		o.textAlign = this.textAlign;
159 		o.textBaseline = this.textBaseline;
160 		o.maxWidth = this.maxWidth;
161 		o.outline = this.outline;
162 		o.lineHeight = this.lineHeight;
163 		o.lineWidth = this.lineWidth;
164 	}
165 
166 	this._getWorkingContext = function() {
167 		var ctx = Text._workingContext;
168 		ctx.font = this.font;
169 		ctx.textAlign = this.textAlign ? this.textAlign : "start";
170 		ctx.textBaseline = this.textBaseline ? this.textBaseline : "alphabetic";
171 		return ctx;
172 	}
173 	
174 	this._drawTextLine = function(ctx, text, y) {
175 		if (this.outline) { ctx.strokeText(text, 0, y, this.maxWidth); }
176 		else { ctx.fillText(text, 0, y, this.maxWidth); }
177 	}
178 }