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  * @param {String} strText
 35  * @param {String} charMapFile  charMapFile or fntFile
 36  * @param {Number} [itemWidth=0]
 37  * @param {Number} [itemHeight=0]
 38  * @param {Number} [startCharMap=""]
 39  * @example
 40  * //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
 41  * var myLabel = new cc.LabelAtlas('Text to display', 'CharMapfile.png', 12, 20, ' ')
 42  *
 43  * //creates the cc.LabelAtlas with a string, a fnt file
 44  * var myLabel = new cc.LabelAtlas('Text to display', 'CharMapFile.plist‘);
 45  */
 46 cc.LabelAtlas = cc.AtlasNode.extend(/** @lends cc.LabelAtlas# */{
 47 
 48     //property String is Getter and Setter
 49 
 50     // string to render
 51     _string: null,
 52     // the first char in the charmap
 53     _mapStartChar: null,
 54 
 55     _textureLoaded: false,
 56     _loadedEventListeners: null,
 57     _className: "LabelAtlas",
 58 
 59     /**
 60      * <p>
 61      *  Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. <br />
 62      *  Create a label atlas. <br />
 63      *  It accepts two groups of parameters: <br/>
 64      * a) string, fntFile <br/>
 65      * b) label, textureFilename, width, height, startChar <br/>
 66      * </p>
 67      * @param {String} strText
 68      * @param {String} charMapFile  charMapFile or fntFile
 69      * @param {Number} [itemWidth=0]
 70      * @param {Number} [itemHeight=0]
 71      * @param {Number} [startCharMap=""]
 72      */
 73     ctor: function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
 74         cc.AtlasNode.prototype.ctor.call(this);
 75 
 76         charMapFile && cc.LabelAtlas.prototype.initWithString.call(this, strText, charMapFile, itemWidth, itemHeight, startCharMap);
 77     },
 78 
 79     /**
 80      * Return  texture is loaded.
 81      * @returns {boolean}
 82      */
 83     textureLoaded: function () {
 84         return this._textureLoaded;
 85     },
 86 
 87     /**
 88      * Add texture loaded event listener.
 89      * @param {Function} callback
 90      * @param {cc.Node} target
 91      */
 92     addLoadedEventListener: function (callback, target) {
 93         if (!this._loadedEventListeners)
 94             this._loadedEventListeners = [];
 95         this._loadedEventListeners.push({eventCallback: callback, eventTarget: target});
 96     },
 97 
 98     _callLoadedEventCallbacks: function () {
 99         if (!this._loadedEventListeners)
100             return;
101         this._textureLoaded = true;
102         var locListeners = this._loadedEventListeners;
103         for (var i = 0, len = locListeners.length; i < len; i++) {
104             var selCallback = locListeners[i];
105             selCallback.eventCallback.call(selCallback.eventTarget, this);
106         }
107         locListeners.length = 0;
108     },
109 
110     /**
111      * <p>
112      *  initializes the cc.LabelAtlas with a string, a char map file(the atlas), <br/>
113      *  the width and height of each element and the starting char of the atlas <br/>
114      *  It accepts two groups of parameters: <br/>
115      * a) string, fntFile <br/>
116      * b) label, textureFilename, width, height, startChar <br/>
117      * </p>
118      * @param {String} strText
119      * @param {String|cc.Texture2D} charMapFile  charMapFile or fntFile or texture file
120      * @param {Number} [itemWidth=0]
121      * @param {Number} [itemHeight=0]
122      * @param {Number} [startCharMap=""]
123      * @return {Boolean} returns true on success
124      */
125     initWithString: function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
126         var label = strText + "", textureFilename, width, height, startChar;
127         if (itemWidth === undefined) {
128             var dict = cc.loader.getRes(charMapFile);
129             if (parseInt(dict["version"], 10) !== 1) {
130                 cc.log("cc.LabelAtlas.initWithString(): Unsupported version. Upgrade cocos2d version");
131                 return false;
132             }
133 
134             textureFilename = cc.path.changeBasename(charMapFile, dict["textureFilename"]);
135             var locScaleFactor = cc.contentScaleFactor();
136             width = parseInt(dict["itemWidth"], 10) / locScaleFactor;
137             height = parseInt(dict["itemHeight"], 10) / locScaleFactor;
138             startChar = String.fromCharCode(parseInt(dict["firstChar"], 10));
139         } else {
140             textureFilename = charMapFile;
141             width = itemWidth || 0;
142             height = itemHeight || 0;
143             startChar = startCharMap || " ";
144         }
145 
146         var texture = null;
147         if (textureFilename instanceof cc.Texture2D)
148             texture = textureFilename;
149         else
150             texture = cc.textureCache.addImage(textureFilename);
151         var locLoaded = texture.isLoaded();
152         this._textureLoaded = locLoaded;
153         if (!locLoaded) {
154             texture.addLoadedEventListener(function (sender) {
155                 this.initWithTexture(texture, width, height, label.length);
156                 this.string = label;
157                 this._callLoadedEventCallbacks();
158             }, this);
159         }
160         if (this.initWithTexture(texture, width, height, label.length)) {
161             this._mapStartChar = startChar;
162             this.string = label;
163             return true;
164         }
165         return false;
166     },
167 
168     /**
169      * Set the color.
170      * @param {cc.Color} color3
171      */
172     setColor: function (color3) {
173         cc.AtlasNode.prototype.setColor.call(this, color3);
174         this.updateAtlasValues();
175     },
176 
177     /**
178      * return the text of this label
179      * @return {String}
180      */
181     getString: function () {
182         return this._string;
183     },
184 
185     /**
186      * draw the label
187      */
188     draw: function (ctx) {
189         cc.AtlasNode.prototype.draw.call(this, ctx);
190         if (cc.LABELATLAS_DEBUG_DRAW) {
191             var s = this.size;
192             var vertices = [cc.p(0, 0), cc.p(s.width, 0),
193                 cc.p(s.width, s.height), cc.p(0, s.height)];
194             cc._drawingUtil.drawPoly(vertices, 4, true);
195         }
196     },
197 
198     _addChildForCanvas: function(child, zOrder, tag){
199         child._lateChild = true;
200         cc.Node.prototype.addChild.call(this, child, zOrder, tag);
201     },
202 
203     /**
204      * Atlas generation
205      * @function
206      */
207     updateAtlasValues: null,
208 
209     _updateAtlasValuesForCanvas: function () {
210         var locString = this._string || "";
211         var n = locString.length;
212         var texture = this.texture;
213         var locItemWidth = this._itemWidth , locItemHeight = this._itemHeight;     //needn't multiply cc.contentScaleFactor(), because sprite's draw will do this
214 
215         for (var i = 0; i < n; i++) {
216             var a = locString.charCodeAt(i) - this._mapStartChar.charCodeAt(0);
217             var row = parseInt(a % this._itemsPerRow, 10);
218             var col = parseInt(a / this._itemsPerRow, 10);
219 
220             var rect = cc.rect(row * locItemWidth, col * locItemHeight, locItemWidth, locItemHeight);
221             var c = locString.charCodeAt(i);
222             var fontChar = this.getChildByTag(i);
223             if (!fontChar) {
224                 fontChar = new cc.Sprite();
225                 if (c == 32) {
226                     fontChar.init();
227                     fontChar.setTextureRect(cc.rect(0, 0, 10, 10), false, cc.size(0, 0));
228                 } else
229                     fontChar.initWithTexture(texture, rect);
230 
231                 cc.Node.prototype.addChild.call(this, fontChar, 0, i);
232             } else {
233                 if (c == 32) {
234                     fontChar.init();
235                     fontChar.setTextureRect(cc.rect(0, 0, 10, 10), false, cc.size(0, 0));
236                 } else {
237                     // reusing fonts
238                     fontChar.initWithTexture(texture, rect);
239                     // restore to default in case they were modified
240                     fontChar.visible = true;
241                     fontChar.opacity = this._displayedOpacity;
242                 }
243             }
244             fontChar.setPosition(i * locItemWidth + locItemWidth / 2, locItemHeight / 2);
245         }
246     },
247 
248     _updateAtlasValuesForWebGL: function () {
249         var locString = this._string;
250         var n = locString.length;
251         var locTextureAtlas = this.textureAtlas;
252 
253         var texture = locTextureAtlas.texture;
254         var textureWide = texture.pixelsWidth;
255         var textureHigh = texture.pixelsHeight;
256         var itemWidthInPixels = this._itemWidth;
257         var itemHeightInPixels = this._itemHeight;
258         if (!this._ignoreContentScaleFactor) {
259             itemWidthInPixels = this._itemWidth * cc.contentScaleFactor();
260             itemHeightInPixels = this._itemHeight * cc.contentScaleFactor();
261         }
262         if (n > locTextureAtlas.getCapacity())
263             cc.log("cc.LabelAtlas._updateAtlasValues(): Invalid String length");
264         var quads = locTextureAtlas.quads;
265         var locDisplayedColor = this._displayedColor;
266         var curColor = {r: locDisplayedColor.r, g: locDisplayedColor.g, b: locDisplayedColor.b, a: this._displayedOpacity};
267         var locItemWidth = this._itemWidth;
268         for (var i = 0; i < n; i++) {
269             var a = locString.charCodeAt(i) - this._mapStartChar.charCodeAt(0);
270             var row = a % this._itemsPerRow;
271             var col = 0 | (a / this._itemsPerRow);
272 
273             var left, right, top, bottom;
274             if (cc.FIX_ARTIFACTS_BY_STRECHING_TEXEL) {
275                 // Issue #938. Don't use texStepX & texStepY
276                 left = (2 * row * itemWidthInPixels + 1) / (2 * textureWide);
277                 right = left + (itemWidthInPixels * 2 - 2) / (2 * textureWide);
278                 top = (2 * col * itemHeightInPixels + 1) / (2 * textureHigh);
279                 bottom = top + (itemHeightInPixels * 2 - 2) / (2 * textureHigh);
280             } else {
281                 left = row * itemWidthInPixels / textureWide;
282                 right = left + itemWidthInPixels / textureWide;
283                 top = col * itemHeightInPixels / textureHigh;
284                 bottom = top + itemHeightInPixels / textureHigh;
285             }
286             var quad = quads[i];
287             var locQuadTL = quad.tl, locQuadTR = quad.tr, locQuadBL = quad.bl, locQuadBR = quad.br;
288             locQuadTL.texCoords.u = left;
289             locQuadTL.texCoords.v = top;
290             locQuadTR.texCoords.u = right;
291             locQuadTR.texCoords.v = top;
292             locQuadBL.texCoords.u = left;
293             locQuadBL.texCoords.v = bottom;
294             locQuadBR.texCoords.u = right;
295             locQuadBR.texCoords.v = bottom;
296 
297             locQuadBL.vertices.x = (i * locItemWidth);
298             locQuadBL.vertices.y = 0;
299             locQuadBL.vertices.z = 0.0;
300             locQuadBR.vertices.x = (i * locItemWidth + locItemWidth);
301             locQuadBR.vertices.y = 0;
302             locQuadBR.vertices.z = 0.0;
303             locQuadTL.vertices.x = i * locItemWidth;
304             locQuadTL.vertices.y = this._itemHeight;
305             locQuadTL.vertices.z = 0.0;
306             locQuadTR.vertices.x = i * locItemWidth + locItemWidth;
307             locQuadTR.vertices.y = this._itemHeight;
308             locQuadTR.vertices.z = 0.0;
309             locQuadTL.colors = curColor;
310             locQuadTR.colors = curColor;
311             locQuadBL.colors = curColor;
312             locQuadBR.colors = curColor;
313         }
314         if (n > 0) {
315             locTextureAtlas.dirty = true;
316             var totalQuads = locTextureAtlas.totalQuads;
317             if (n > totalQuads)
318                 locTextureAtlas.increaseTotalQuadsWith(n - totalQuads);
319         }
320     },
321 
322     /**
323      * set the display string
324      * @function
325      * @param {String} label
326      */
327     setString: null,
328 
329     _setStringForCanvas: function (label) {
330         label = String(label);
331         var len = label.length;
332         this._string = label;
333         this.width = len * this._itemWidth;
334         this.height = this._itemHeight;
335         if (this._children) {
336             var locChildren = this._children;
337             len = locChildren.length;
338             for (var i = 0; i < len; i++) {
339                 var node = locChildren[i];
340                 if (node && !node._lateChild)
341                     node.visible = false;
342             }
343         }
344 
345         this.updateAtlasValues();
346         this.quadsToDraw = len;
347     },
348 
349     _setStringForWebGL: function (label) {
350         label = String(label);
351         var len = label.length;
352         if (len > this.textureAtlas.totalQuads)
353             this.textureAtlas.resizeCapacity(len);
354 
355         this._string = label;
356         this.width = len * this._itemWidth;
357         this.height = this._itemHeight;
358 
359         this.updateAtlasValues();
360         this.quadsToDraw = len;
361     },
362 
363     /**
364      * set the opacity
365      * @function
366      * @param {Number} opacity
367      */
368     setOpacity: null,
369 
370     _setOpacityForCanvas: function (opacity) {
371         if (this._displayedOpacity !== opacity) {
372             cc.AtlasNode.prototype.setOpacity.call(this, opacity);
373             var locChildren = this._children;
374             for (var i = 0, len = locChildren.length; i < len; i++) {
375                 if (locChildren[i])
376                     locChildren[i].opacity = opacity;
377             }
378         }
379     },
380 
381     _setOpacityForWebGL: function (opacity) {
382         if (this._opacity !== opacity)
383             cc.AtlasNode.prototype.setOpacity.call(this, opacity);
384     }
385 });
386 
387 var _p = cc.LabelAtlas.prototype;
388 if (cc._renderType === cc._RENDER_TYPE_WEBGL) {
389     _p.updateAtlasValues = _p._updateAtlasValuesForWebGL;
390     _p.setString = _p._setStringForWebGL;
391     _p.setOpacity = _p._setOpacityForWebGL;
392 } else {
393     _p.updateAtlasValues = _p._updateAtlasValuesForCanvas;
394     _p.setString = _p._setStringForCanvas;
395     _p.setOpacity = _p._setOpacityForCanvas;
396     _p.addChild = _p._addChildForCanvas;
397 }
398 
399 // Override properties
400 cc.defineGetterSetter(_p, "opacity", _p.getOpacity, _p.setOpacity);
401 
402 // Extended properties
403 /** @expose */
404 _p.string;
405 cc.defineGetterSetter(_p, "string", _p.getString, _p.setString);
406 
407 /**
408  * <p>
409  *     Please use new cc.LabelAtlas instead. <br />
410  *     Create a label atlas. <br />
411  *     It accepts two groups of parameters:                                                            <br/>
412  *         a) string, fntFile                                                                               <br/>
413  *         b) label, textureFilename, width, height, startChar                                              <br/>
414  * </p>
415  * @deprecated since v3.0 please use new cc.LabelAtlas
416  * @param {String} strText
417  * @param {String} charMapFile  charMapFile or fntFile
418  * @param {Number} [itemWidth=0]
419  * @param {Number} [itemHeight=0]
420  * @param {Number} [startCharMap=""]
421  * @return {cc.LabelAtlas} returns the LabelAtlas object on success
422  * @example
423  * //Example
424  * //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
425  * var myLabel = cc.LabelAtlas.create('Text to display', 'CharMapfile.png', 12, 20, ' ')
426  *
427  * //creates the cc.LabelAtlas with a string, a fnt file
428  * var myLabel = cc.LabelAtlas.create('Text to display', 'CharMapFile.plist‘);
429  */
430 cc.LabelAtlas.create = function (strText, charMapFile, itemWidth, itemHeight, startCharMap) {
431     return new cc.LabelAtlas(strText, charMapFile, itemWidth, itemHeight, startCharMap);
432 };
433 
434