/* * 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"; ctx.lineJoin = "miter"; ctx.miterLimit = 2.5; 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"); }());