1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  * using image file to print text label on the screen, might be a bit slower than cc.Label, similar to cc.LabelBMFont
 29  * @class
 30  * @extends cc.AtlasNode
 31  *
 32  * @property {String}   string  - Content string of label
 33  */
 34 cc.LabelAtlas = cc.AtlasNode.extend(/** @lends cc.LabelAtlas# */{
 35     // string to render
 36     _string: null,
 37     // the first char in the charmap
 38     _mapStartChar: null,
 39 
 40     _textureLoaded: false,
 41     _loadedEventListeners: null,
 42     _className: "LabelAtlas",
 43 
 44     /**
 45      * <p>
 46      *  Create a label atlas.
 47      *  It accepts two groups of parameters:                                                            <br/>
 48      * a) string, fntFile                                                                               <br/>
 49      * b) label, textureFilename, width, height, startChar                                              <br/>
 50      * </p>
 51      * @param {String} strText
 52      * @param {String} charMapFile  charMapFile or fntFile
 53      * @param {Number} [itemWidth=0]
 54      * @param {Number} [itemHeight=0]
 55      * @param {Number} [startCharMap=""]
 56      * @example
 57      * //creates the cc.LabelAtlas with a string, a char map file(the atlas), the width and height of each element and the starting char of the atlas
 58      * var myLabel = new cc.LabelAtlas('Text to display', 'CharMapfile.png', 12, 20, ' ')
 59      *
 60      * //creates the cc.LabelAtlas with a string, a fnt file
 61      * var myLabel = new cc.LabelAtlas('Text to display', 'CharMapFile.plist‘);
 62      */
 63     ctor: function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
 64         cc.AtlasNode.prototype.ctor.call(this);
 65 
 66         charMapFile && cc.LabelAtlas.prototype.initWithString.call(this, strText, charMapFile, itemWidth, itemHeight, startCharMap);
 67     },
 68 
 69     /**
 70      * return  texture is loaded
 71      * @returns {boolean}
 72      */
 73     textureLoaded: function () {
 74         return this._textureLoaded;
 75     },
 76 
 77     /**
 78      * add texture loaded event listener
 79      * @param {Function} callback
 80      * @param {Object} target
 81      */
 82     addLoadedEventListener: function (callback, target) {
 83         if (!this._loadedEventListeners)
 84             this._loadedEventListeners = [];
 85         this._loadedEventListeners.push({eventCallback: callback, eventTarget: target});
 86     },
 87 
 88     _callLoadedEventCallbacks: function () {
 89         if (!this._loadedEventListeners)
 90             return;
 91         this._textureLoaded = true;
 92         var locListeners = this._loadedEventListeners;
 93         for (var i = 0, len = locListeners.length; i < len; i++) {
 94             var selCallback = locListeners[i];
 95             selCallback.eventCallback.call(selCallback.eventTarget, this);
 96         }
 97         locListeners.length = 0;
 98     },
 99     /**
100      * <p>
101      * initializes the cc.LabelAtlas with a string, a char map file(the atlas),                     <br/>
102      * the width and height of each element and the starting char of the atlas                      <br/>
103      *  It accepts two groups of parameters:                                                        <br/>
104      * a) string, fntFile                                                                           <br/>
105      * b) label, textureFilename, width, height, startChar                                          <br/>
106      * </p>
107      * @param {String} strText
108      * @param {String|cc.Texture2D} charMapFile  charMapFile or fntFile or texture file
109      * @param {Number} [itemWidth=0]
110      * @param {Number} [itemHeight=0]
111      * @param {Number} [startCharMap=""]
112      * @return {Boolean} returns true on success
113      */
114     initWithString: function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
115         var label = strText + "", textureFilename, width, height, startChar;
116         if (itemWidth === undefined) {
117             var dict = cc.loader.getRes(charMapFile);
118             if (parseInt(dict["version"], 10) !== 1) {
119                 cc.log("cc.LabelAtlas.initWithString(): Unsupported version. Upgrade cocos2d version");
120                 return false;
121             }
122 
123             textureFilename = cc.path.changeBasename(charMapFile, dict["textureFilename"]);
124             var locScaleFactor = cc.contentScaleFactor();
125             width = parseInt(dict["itemWidth"], 10) / locScaleFactor;
126             height = parseInt(dict["itemHeight"], 10) / locScaleFactor;
127             startChar = String.fromCharCode(parseInt(dict["firstChar"], 10));
128         } else {
129             textureFilename = charMapFile;
130             width = itemWidth || 0;
131             height = itemHeight || 0;
132             startChar = startCharMap || " ";
133         }
134 
135         var texture = null;
136         if (textureFilename instanceof cc.Texture2D)
137             texture = textureFilename;
138         else
139             texture = cc.textureCache.addImage(textureFilename);
140         var locLoaded = texture.isLoaded();
141         this._textureLoaded = locLoaded;
142         if (!locLoaded) {
143             texture.addLoadedEventListener(function (sender) {
144                 this.initWithTexture(texture, width, height, label.length);
145                 this.string = label;
146                 this._callLoadedEventCallbacks();
147             }, this);
148         }
149         if (this.initWithTexture(texture, width, height, label.length)) {
150             this._mapStartChar = startChar;
151             this.string = label;
152             return true;
153         }
154         return false;
155     },
156 
157     /**
158      * @param {cc.Color} color3
159      */
160     setColor: function (color3) {
161         cc.AtlasNode.prototype.setColor.call(this, color3);
162         this.updateAtlasValues();
163     },
164     /**
165      * return the text of this label
166      * @return {String}
167      */
168     getString: function () {
169         return this._string;
170     },
171 
172     /**
173      * draw the label
174      */
175     draw: function (ctx) {
176         cc.AtlasNode.prototype.draw.call(this, ctx);
177         if (cc.LABELATLAS_DEBUG_DRAW) {
178             var s = this.size;
179             var vertices = [cc.p(0, 0), cc.p(s.width, 0),
180                 cc.p(s.width, s.height), cc.p(0, s.height)];
181             cc._drawingUtil.drawPoly(vertices, 4, true);
182         }
183     },
184 
185     /**
186      * @function
187      * Atlas generation
188      */
189     updateAtlasValues: null,
190 
191     _updateAtlasValuesForCanvas: function () {
192         var locString = this._string;
193         var n = locString.length;
194         var texture = this.texture;
195         var locItemWidth = this._itemWidth , locItemHeight = this._itemHeight;     //needn't multiply cc.contentScaleFactor(), because sprite's draw will do this
196 
197         for (var i = 0; i < n; i++) {
198             var a = locString.charCodeAt(i) - this._mapStartChar.charCodeAt(0);
199             var row = parseInt(a % this._itemsPerRow, 10);
200             var col = parseInt(a / this._itemsPerRow, 10);
201 
202             var rect = cc.rect(row * locItemWidth, col * locItemHeight, locItemWidth, locItemHeight);
203             var c = locString.charCodeAt(i);
204             var fontChar = this.getChildByTag(i);
205             if (!fontChar) {
206                 fontChar = new cc.Sprite();
207                 if (c == 32) {
208                     fontChar.init();
209                     fontChar.setTextureRect(cc.rect(0, 0, 10, 10), false, cc.size(0, 0));
210                 } else
211                     fontChar.initWithTexture(texture, rect);
212 
213                 this.addChild(fontChar, 0, i);
214             } else {
215                 if (c == 32) {
216                     fontChar.init();
217                     fontChar.setTextureRect(cc.rect(0, 0, 10, 10), false, cc.size(0, 0));
218                 } else {
219                     // reusing fonts
220                     fontChar.initWithTexture(texture, rect);
221                     // restore to default in case they were modified
222                     fontChar.visible = true;
223                     fontChar.opacity = this._displayedOpacity;
224                 }
225             }
226             fontChar.setPosition(i * locItemWidth + locItemWidth / 2, locItemHeight / 2);
227         }
228     },
229 
230     _updateAtlasValuesForWebGL: function () {
231         var locString = this._string;
232         var n = locString.length;
233         var locTextureAtlas = this.textureAtlas;
234 
235         var texture = locTextureAtlas.texture;
236         var textureWide = texture.pixelsWidth;
237         var textureHigh = texture.pixelsHeight;
238         var itemWidthInPixels = this._itemWidth;
239         var itemHeightInPixels = this._itemHeight;
240         if (!this._ignoreContentScaleFactor) {
241             itemWidthInPixels = this._itemWidth * cc.contentScaleFactor();
242             itemHeightInPixels = this._itemHeight * cc.contentScaleFactor();
243         }
244         if (n > locTextureAtlas.getCapacity())
245             cc.log("cc.LabelAtlas._updateAtlasValues(): Invalid String length");
246         var quads = locTextureAtlas.quads;
247         var locDisplayedColor = this._displayedColor;
248         var curColor = {r: locDisplayedColor.r, g: locDisplayedColor.g, b: locDisplayedColor.b, a: this._displayedOpacity};
249         var locItemWidth = this._itemWidth;
250         for (var i = 0; i < n; i++) {
251             var a = locString.charCodeAt(i) - this._mapStartChar.charCodeAt(0);
252             var row = a % this._itemsPerRow;
253             var col = 0 | (a / this._itemsPerRow);
254 
255             var left, right, top, bottom;
256             if (cc.FIX_ARTIFACTS_BY_STRECHING_TEXEL) {
257                 // Issue #938. Don't use texStepX & texStepY
258                 left = (2 * row * itemWidthInPixels + 1) / (2 * textureWide);
259                 right = left + (itemWidthInPixels * 2 - 2) / (2 * textureWide);
260                 top = (2 * col * itemHeightInPixels + 1) / (2 * textureHigh);
261                 bottom = top + (itemHeightInPixels * 2 - 2) / (2 * textureHigh);
262             } else {
263                 left = row * itemWidthInPixels / textureWide;
264                 right = left + itemWidthInPixels / textureWide;
265                 top = col * itemHeightInPixels / textureHigh;
266                 bottom = top + itemHeightInPixels / textureHigh;
267             }
268             var quad = quads[i];
269             var locQuadTL = quad.tl, locQuadTR = quad.tr, locQuadBL = quad.bl, locQuadBR = quad.br;
270             locQuadTL.texCoords.u = left;
271             locQuadTL.texCoords.v = top;
272             locQuadTR.texCoords.u = right;
273             locQuadTR.texCoords.v = top;
274             locQuadBL.texCoords.u = left;
275             locQuadBL.texCoords.v = bottom;
276             locQuadBR.texCoords.u = right;
277             locQuadBR.texCoords.v = bottom;
278 
279             locQuadBL.vertices.x = (i * locItemWidth);
280             locQuadBL.vertices.y = 0;
281             locQuadBL.vertices.z = 0.0;
282             locQuadBR.vertices.x = (i * locItemWidth + locItemWidth);
283             locQuadBR.vertices.y = 0;
284             locQuadBR.vertices.z = 0.0;
285             locQuadTL.vertices.x = i * locItemWidth;
286             locQuadTL.vertices.y = this._itemHeight;
287             locQuadTL.vertices.z = 0.0;
288             locQuadTR.vertices.x = i * locItemWidth + locItemWidth;
289             locQuadTR.vertices.y = this._itemHeight;
290             locQuadTR.vertices.z = 0.0;
291             locQuadTL.colors = curColor;
292             locQuadTR.colors = curColor;
293             locQuadBL.colors = curColor;
294             locQuadBR.colors = curColor;
295         }
296         if (n > 0) {
297             locTextureAtlas.dirty = true;
298             var totalQuads = locTextureAtlas.totalQuads;
299             if (n > totalQuads)
300                 locTextureAtlas.increaseTotalQuadsWith(n - totalQuads);
301         }
302     },
303 
304     /**
305      * set the display string
306      * @function
307      * @param {String} label
308      */
309     setString: null,
310 
311     _setStringForCanvas: function (label) {
312         label = String(label);
313         var len = label.length;
314         this._string = label;
315         this.width = len * this._itemWidth;
316         this.height = this._itemHeight;
317         if (this._children) {
318             var locChildren = this._children;
319             len = locChildren.length;
320             for (var i = 0; i < len; i++) {
321                 var node = locChildren[i];
322                 if (node)
323                     node.visible = false;
324             }
325         }
326 
327         this.updateAtlasValues();
328         this.quadsToDraw = len;
329     },
330 
331     _setStringForWebGL: function (label) {
332         label = String(label);
333         var len = label.length;
334         if (len > this.textureAtlas.totalQuads)
335             this.textureAtlas.resizeCapacity(len);
336 
337         this._string = label;
338         this.width = len * this._itemWidth;
339         this.height = this._itemHeight;
340 
341         this.updateAtlasValues();
342         this.quadsToDraw = len;
343     },
344 
345     setOpacity: null,
346 
347     _setOpacityForCanvas: function (opacity) {
348         if (this._displayedOpacity !== opacity) {
349             cc.AtlasNode.prototype.setOpacity.call(this, opacity);
350             var locChildren = this._children;
351             for (var i = 0, len = locChildren.length; i < len; i++) {
352                 if (locChildren[i])
353                     locChildren[i].opacity = opacity;
354             }
355         }
356     },
357 
358     _setOpacityForWebGL: function (opacity) {
359         if (this._opacity !== opacity)
360             cc.AtlasNode.prototype.setOpacity.call(this, opacity);
361     }
362 });
363 
364 var _p = cc.LabelAtlas.prototype;
365 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
366     _p.updateAtlasValues = _p._updateAtlasValuesForWebGL;
367     _p.setString = _p._setStringForWebGL;
368     _p.setOpacity = _p._setOpacityForWebGL;
369 } else {
370     _p.updateAtlasValues = _p._updateAtlasValuesForCanvas;
371     _p.setString = _p._setStringForCanvas;
372     _p.setOpacity = _p._setOpacityForCanvas;
373 }
374 
375 // Override properties
376 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
377 
378 // Extended properties
379 /** @expose */
380 _p.string;
381 cc.defineGetterSetter(_p, "string", _p.getString, _p.setString);
382 
383 /**
384  * <p>
385  *  Create a label atlas.
386  *  It accepts two groups of parameters:                                                            <br/>
387  * a) string, fntFile                                                                               <br/>
388  * b) label, textureFilename, width, height, startChar                                              <br/>
389  * </p>
390  * @param {String} strText
391  * @param {String} charMapFile  charMapFile or fntFile
392  * @param {Number} [itemWidth=0]
393  * @param {Number} [itemHeight=0]
394  * @param {Number} [startCharMap=""]
395  * @return {cc.LabelAtlas|Null} returns the LabelAtlas object on success
396  * @example
397  * //Example
398  * //creates the cc.LabelAtlas with a string, a char map file(the atlas), the width and height of each element and the starting char of the atlas
399  * var myLabel = cc.LabelAtlas.create('Text to display', 'CharMapfile.png', 12, 20, ' ')
400  *
401  * //creates the cc.LabelAtlas with a string, a fnt file
402  * var myLabel = cc.LabelAtlas.create('Text to display', 'CharMapFile.plist‘);
403  */
404 cc.LabelAtlas.create = function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
405     return new cc.LabelAtlas(strText, charMapFile, itemWidth, itemHeight, startCharMap);
406 };
407 
408