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