1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies 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     _addChildForCanvas: function(child, zOrder, tag){
186         child._lateChild = true;
187         cc.NodeRGBA.prototype.addChild.call(this, child, zOrder, tag);
188     },
189 
190     /**
191      * @function
192      * Atlas generation
193      */
194     updateAtlasValues: null,
195 
196     _updateAtlasValuesForCanvas: function () {
197         var locString = this._string || "";
198         var n = locString.length;
199         var texture = this.texture;
200         var locItemWidth = this._itemWidth , locItemHeight = this._itemHeight;     //needn't multiply cc.contentScaleFactor(), because sprite's draw will do this
201 
202         for (var i = 0; i < n; i++) {
203             var a = locString.charCodeAt(i) - this._mapStartChar.charCodeAt(0);
204             var row = parseInt(a % this._itemsPerRow, 10);
205             var col = parseInt(a / this._itemsPerRow, 10);
206 
207             var rect = cc.rect(row * locItemWidth, col * locItemHeight, locItemWidth, locItemHeight);
208             var c = locString.charCodeAt(i);
209             var fontChar = this.getChildByTag(i);
210             if (!fontChar) {
211                 fontChar = new cc.Sprite();
212                 if (c == 32) {
213                     fontChar.init();
214                     fontChar.setTextureRect(cc.rect(0, 0, 10, 10), false, cc.size(0, 0));
215                 } else
216                     fontChar.initWithTexture(texture, rect);
217 
218                 cc.NodeRGBA.prototype.addChild.call(this, fontChar, 0, i);
219             } else {
220                 if (c == 32) {
221                     fontChar.init();
222                     fontChar.setTextureRect(cc.rect(0, 0, 10, 10), false, cc.size(0, 0));
223                 } else {
224                     // reusing fonts
225                     fontChar.initWithTexture(texture, rect);
226                     // restore to default in case they were modified
227                     fontChar.visible = true;
228                     fontChar.opacity = this._displayedOpacity;
229                 }
230             }
231             fontChar.setPosition(i * locItemWidth + locItemWidth / 2, locItemHeight / 2);
232         }
233     },
234 
235     _updateAtlasValuesForWebGL: function () {
236         var locString = this._string;
237         var n = locString.length;
238         var locTextureAtlas = this.textureAtlas;
239 
240         var texture = locTextureAtlas.texture;
241         var textureWide = texture.pixelsWidth;
242         var textureHigh = texture.pixelsHeight;
243         var itemWidthInPixels = this._itemWidth;
244         var itemHeightInPixels = this._itemHeight;
245         if (!this._ignoreContentScaleFactor) {
246             itemWidthInPixels = this._itemWidth * cc.contentScaleFactor();
247             itemHeightInPixels = this._itemHeight * cc.contentScaleFactor();
248         }
249         if (n > locTextureAtlas.getCapacity())
250             cc.log("cc.LabelAtlas._updateAtlasValues(): Invalid String length");
251         var quads = locTextureAtlas.quads;
252         var locDisplayedColor = this._displayedColor;
253         var curColor = {r: locDisplayedColor.r, g: locDisplayedColor.g, b: locDisplayedColor.b, a: this._displayedOpacity};
254         var locItemWidth = this._itemWidth;
255         for (var i = 0; i < n; i++) {
256             var a = locString.charCodeAt(i) - this._mapStartChar.charCodeAt(0);
257             var row = a % this._itemsPerRow;
258             var col = 0 | (a / this._itemsPerRow);
259 
260             var left, right, top, bottom;
261             if (cc.FIX_ARTIFACTS_BY_STRECHING_TEXEL) {
262                 // Issue #938. Don't use texStepX & texStepY
263                 left = (2 * row * itemWidthInPixels + 1) / (2 * textureWide);
264                 right = left + (itemWidthInPixels * 2 - 2) / (2 * textureWide);
265                 top = (2 * col * itemHeightInPixels + 1) / (2 * textureHigh);
266                 bottom = top + (itemHeightInPixels * 2 - 2) / (2 * textureHigh);
267             } else {
268                 left = row * itemWidthInPixels / textureWide;
269                 right = left + itemWidthInPixels / textureWide;
270                 top = col * itemHeightInPixels / textureHigh;
271                 bottom = top + itemHeightInPixels / textureHigh;
272             }
273             var quad = quads[i];
274             var locQuadTL = quad.tl, locQuadTR = quad.tr, locQuadBL = quad.bl, locQuadBR = quad.br;
275             locQuadTL.texCoords.u = left;
276             locQuadTL.texCoords.v = top;
277             locQuadTR.texCoords.u = right;
278             locQuadTR.texCoords.v = top;
279             locQuadBL.texCoords.u = left;
280             locQuadBL.texCoords.v = bottom;
281             locQuadBR.texCoords.u = right;
282             locQuadBR.texCoords.v = bottom;
283 
284             locQuadBL.vertices.x = (i * locItemWidth);
285             locQuadBL.vertices.y = 0;
286             locQuadBL.vertices.z = 0.0;
287             locQuadBR.vertices.x = (i * locItemWidth + locItemWidth);
288             locQuadBR.vertices.y = 0;
289             locQuadBR.vertices.z = 0.0;
290             locQuadTL.vertices.x = i * locItemWidth;
291             locQuadTL.vertices.y = this._itemHeight;
292             locQuadTL.vertices.z = 0.0;
293             locQuadTR.vertices.x = i * locItemWidth + locItemWidth;
294             locQuadTR.vertices.y = this._itemHeight;
295             locQuadTR.vertices.z = 0.0;
296             locQuadTL.colors = curColor;
297             locQuadTR.colors = curColor;
298             locQuadBL.colors = curColor;
299             locQuadBR.colors = curColor;
300         }
301         if (n > 0) {
302             locTextureAtlas.dirty = true;
303             var totalQuads = locTextureAtlas.totalQuads;
304             if (n > totalQuads)
305                 locTextureAtlas.increaseTotalQuadsWith(n - totalQuads);
306         }
307     },
308 
309     /**
310      * set the display string
311      * @function
312      * @param {String} label
313      */
314     setString: null,
315 
316     _setStringForCanvas: function (label) {
317         label = String(label);
318         var len = label.length;
319         this._string = label;
320         this.width = len * this._itemWidth;
321         this.height = this._itemHeight;
322         if (this._children) {
323             var locChildren = this._children;
324             len = locChildren.length;
325             for (var i = 0; i < len; i++) {
326                 var node = locChildren[i];
327                 if (node && !node._lateChild)
328                     node.visible = false;
329             }
330         }
331 
332         this.updateAtlasValues();
333         this.quadsToDraw = len;
334     },
335 
336     _setStringForWebGL: function (label) {
337         label = String(label);
338         var len = label.length;
339         if (len > this.textureAtlas.totalQuads)
340             this.textureAtlas.resizeCapacity(len);
341 
342         this._string = label;
343         this.width = len * this._itemWidth;
344         this.height = this._itemHeight;
345 
346         this.updateAtlasValues();
347         this.quadsToDraw = len;
348     },
349 
350     setOpacity: null,
351 
352     _setOpacityForCanvas: function (opacity) {
353         if (this._displayedOpacity !== opacity) {
354             cc.AtlasNode.prototype.setOpacity.call(this, opacity);
355             var locChildren = this._children;
356             for (var i = 0, len = locChildren.length; i < len; i++) {
357                 if (locChildren[i])
358                     locChildren[i].opacity = opacity;
359             }
360         }
361     },
362 
363     _setOpacityForWebGL: function (opacity) {
364         if (this._opacity !== opacity)
365             cc.AtlasNode.prototype.setOpacity.call(this, opacity);
366     }
367 });
368 
369 var _p = cc.LabelAtlas.prototype;
370 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
371     _p.updateAtlasValues = _p._updateAtlasValuesForWebGL;
372     _p.setString = _p._setStringForWebGL;
373     _p.setOpacity = _p._setOpacityForWebGL;
374 } else {
375     _p.updateAtlasValues = _p._updateAtlasValuesForCanvas;
376     _p.setString = _p._setStringForCanvas;
377     _p.setOpacity = _p._setOpacityForCanvas;
378     _p.addChild = _p._addChildForCanvas;
379 }
380 
381 // Override properties
382 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
383 
384 // Extended properties
385 /** @expose */
386 _p.string;
387 cc.defineGetterSetter(_p, "string", _p.getString, _p.setString);
388 
389 /**
390  * <p>
391  *  Create a label atlas.
392  *  It accepts two groups of parameters:                                                            <br/>
393  * a) string, fntFile                                                                               <br/>
394  * b) label, textureFilename, width, height, startChar                                              <br/>
395  * </p>
396  * @param {String} strText
397  * @param {String} charMapFile  charMapFile or fntFile
398  * @param {Number} [itemWidth=0]
399  * @param {Number} [itemHeight=0]
400  * @param {Number} [startCharMap=""]
401  * @return {cc.LabelAtlas|Null} returns the LabelAtlas object on success
402  * @example
403  * //Example
404  * //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
405  * var myLabel = cc.LabelAtlas.create('Text to display', 'CharMapfile.png', 12, 20, ' ')
406  *
407  * //creates the cc.LabelAtlas with a string, a fnt file
408  * var myLabel = cc.LabelAtlas.create('Text to display', 'CharMapFile.plist‘);
409  */
410 cc.LabelAtlas.create = function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
411     return new cc.LabelAtlas(strText, charMapFile, itemWidth, itemHeight, startCharMap);
412 };
413 
414