1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * <iframe src='../../../examples/Text.html?noHeader' width = '320' height = '240' scrolling='no'></iframe>
  9  * <br/>
 10  * @class Text class provide basic text-display function, use DOMElement for complex text-display.
 11  * @augments View
 12  * @mixes CacheMixin
 13  * @borrows CacheMixin#cache as #cache
 14  * @borrows CacheMixin#updateCache as #updateCache
 15  * @borrows CacheMixin#setCacheDirty as #setCacheDirty
 16  * @param {Object} properties Properties parameters for the object. Includes all writable properties.
 17  * @module hilo/view/Text
 18  * @requires hilo/core/Class
 19  * @requires hilo/core/Hilo
 20  * @requires hilo/view/View
 21  * @requires hilo/view/CacheMixin
 22  * @property {String} text Text to display.
 23  * @property {String} color Color of the text.
 24  * @property {String} textAlign Horizontal alignment way of the text. May be one of the following value:'start', 'end', 'left', 'right', and 'center'. Note:Need to specify the width property of the text to take effect
 25  * @property {String} textVAlign Vertical alignment way of the text. May be one of the following value:'top', 'middle', 'bottom'. Note:Need to specify the height property of the text to take effect.
 26  * @property {Boolean} outline Draw the outline of the text or fill the text.
 27  * @property {Number} lineSpacing The spacing between lines. Measured in px, default value is 0.
 28  * @property {Number} maxWidth The max length of the text, default value is 200.
 29  * @property {String} font Text's CSS font style, readonly! Use setFont function to set text font.
 30  * @property {Number} textWidth Width of the text, readonly! Works only on canvas mode.
 31  * @property {Number} textHeight Height of the text, readonly! Works only on canvas mode.
 32  */
 33 var Text = Class.create(/** @lends Text.prototype */{
 34     Extends: View,
 35     Mixes:CacheMixin,
 36     constructor: function(properties){
 37         properties = properties || {};
 38         this.id = this.id || properties.id || Hilo.getUid('Text');
 39         Text.superclass.constructor.call(this, properties);
 40 
 41         // if(!properties.width) this.width = 200; //default width
 42         if(!properties.font) this.font = '12px arial'; //default font style
 43         this._fontHeight = Text.measureFontHeight(this.font);
 44     },
 45 
 46     text: '',
 47     color: '#000',
 48     textAlign: null,
 49     textVAlign: null,
 50     outline: false,
 51     lineSpacing: 0,
 52     maxWidth: 200,
 53     font: null, //ready-only
 54     textWidth: 0, //read-only
 55     textHeight: 0, //read-only
 56 
 57     /**
 58      * Set text CSS font style.
 59      * @param {String} font Text CSS font style to set.
 60      * @returns {Text} the Text object, chained call supported.
 61      */
 62     setFont: function(font){
 63         var me = this;
 64         if(me.font !== font){
 65             me.font = font;
 66             me._fontHeight = Text.measureFontHeight(font);
 67         }
 68 
 69         return me;
 70     },
 71 
 72     /**
 73      * Overwrite render function.
 74      * @private
 75      */
 76     render: function(renderer, delta){
 77         var me = this;
 78 
 79         if(renderer.renderType === 'canvas'){
 80             if(this.drawable){
 81                 renderer.draw(me);
 82             }
 83             else{
 84                 me._draw(renderer.context);
 85             }
 86         }
 87         else if(renderer.renderType === 'dom'){
 88             var drawable = me.drawable;
 89             var domElement = drawable.domElement;
 90             var style = domElement.style;
 91 
 92             style.font = me.font;
 93             style.textAlign = me.textAlign;
 94             style.color = me.color;
 95             style.width = me.width + 'px';
 96             style.height = me.height + 'px';
 97             style.lineHeight = (me._fontHeight + me.lineSpacing) + 'px';
 98 
 99             domElement.innerHTML = me.text;
100             renderer.draw(this);
101         }
102         else{
103             //TODO:自动更新cache  TODO:auto update cache
104             me.cache();
105             renderer.draw(me);
106         }
107     },
108 
109     /**
110      * Draw text under the assigned render context.
111      * @private
112      */
113     _draw: function(context){
114         var me = this, text = me.text.toString();
115         if(!text) return;
116 
117         //set drawing style
118         context.font = me.font;
119         context.textAlign = me.textAlign;
120         context.textBaseline = 'top';
121 
122         //find and draw all explicit lines
123         var lines = text.split(/\r\n|\r|\n|<br(?:[ \/])*>/);
124         var width = 0, height = 0;
125         var lineHeight = me._fontHeight + me.lineSpacing;
126         var i, line, w, len, wlen;
127         var drawLines = [];
128 
129         for(i = 0, len = lines.length; i < len; i++){
130             line = lines[i];
131             w = context.measureText(line).width;
132 
133             //check if the line need to split
134             if(w <= me.maxWidth){
135                 drawLines.push({text:line, y:height});
136                 // me._drawTextLine(context, line, height);
137                 if(width < w) width = w;
138                 height += lineHeight;
139                 continue;
140             }
141 
142             var str = '', oldWidth = 0, newWidth, j, word;
143 
144             for(j = 0, wlen = line.length; j < wlen; j++){
145                 word = line[j];
146                 newWidth = context.measureText(str + word).width;
147 
148                 if(newWidth > me.maxWidth){
149                     drawLines.push({text:str, y:height});
150                     // me._drawTextLine(context, str, height);
151                     if(width < oldWidth) width = oldWidth;
152                     height += lineHeight;
153                     str = word;
154                 }else{
155                     oldWidth = newWidth;
156                     str += word;
157                 }
158 
159                 if(j == wlen - 1){
160                     drawLines.push({text:str, y:height});
161                     // me._drawTextLine(context, str, height);
162                     if(str !== word && width < newWidth) width = newWidth;
163                     height += lineHeight;
164                 }
165             }
166         }
167 
168         me.textWidth = width;
169         me.textHeight = height;
170         if(!me.width) me.width = width;
171         if(!me.height) me.height = height;
172 
173         //vertical alignment
174         var startY = 0;
175         switch(me.textVAlign){
176             case 'middle':
177                 startY = me.height - me.textHeight >> 1;
178                 break;
179             case 'bottom':
180                 startY = me.height - me.textHeight;
181                 break;
182         }
183 
184         //draw background
185         var bg = me.background;
186         if(bg){
187             context.fillStyle = bg;
188             context.fillRect(0, 0, me.width, me.height);
189         }
190 
191         if(me.outline) context.strokeStyle = me.color;
192         else context.fillStyle = me.color;
193 
194         //draw text lines
195         for(i = 0; i < drawLines.length; i++){
196             line = drawLines[i];
197             me._drawTextLine(context, line.text, startY + line.y);
198         }
199     },
200 
201     /**
202      * Draw a line of text under the assigned render context.
203      * @private
204      */
205     _drawTextLine: function(context, text, y){
206         var me = this, x = 0, width = me.width;
207 
208         switch(me.textAlign){
209             case 'center':
210                 x = width >> 1;
211                 break;
212             case 'right':
213             case 'end':
214                 x = width;
215                 break;
216         }
217 
218         if(me.outline) context.strokeText(text, x, y);
219         else context.fillText(text, x, y);
220     },
221 
222     Statics: /** @lends Text */{
223         /**
224          * Measure the line height of the assigned text font style.
225          * @param {String} font Font style to measure.
226          * @return {Number} Return line height of the assigned font style.
227          */
228         measureFontHeight: function(font){
229             var docElement = document.documentElement, fontHeight;
230             var elem = Hilo.createElement('div', {style:{font:font, position:'absolute'}, innerHTML:'M'});
231 
232             docElement.appendChild(elem);
233             fontHeight = elem.offsetHeight;
234             docElement.removeChild(elem);
235             return fontHeight;
236         }
237     }
238 
239 });
240