Forráskód Böngészése

Canvas2D: minor evol/fix on AtlasPicture and BMFont support

AtlasPicture:

 - a plugin class must now be decorated with @AtlasLoaderPlugin to register automatically (so you don't have to call AtlasPictureInfoFactory.addLoader anymore)
 - fix on plugin loading fail to switch to the next available

BMFont support:

 - New abstract class BaseFontTexture is the base class of both FontTexture and the new BitmapFontTexture
 - BitmapFontTexture class introduced to load BMFont configuration file along with its corresponding texture
 - IBitmapFontLoader interface to make the configuration file loading extensible with a plugin architecture (same as AtlasPicture)
 - BMFontLoaderTxt loader to load the Text version of the BMFont ".fnt" file.

Text2D: now supports rendering with a BitmapFontTexture using the bitmapFontTexture setting in the constructor
nockawa 8 éve
szülő
commit
98ae77a07c

+ 71 - 62
canvas2D/src/Engine/babylon.atlasPicture.ts

@@ -28,45 +28,6 @@
         smartupdate: string;
     }
 
-    // Loader class for the TexturePacker's JSON Array data format
-    class JSONArrayLoader implements IAtlasLoader {
-
-        loadFile(content): { api: AtlasPictureInfo, errorMsg: string, errorCode: number } {
-            let errorMsg: string = null;
-            let errorCode: number = 0;
-            let root = null;
-            let api: AtlasPictureInfo = null;
-            try {
-                try {
-                    root = JSON.parse(content);
-                    let frames = <Array<IFrame>>root.frames;
-                    let meta = <IMeta>root.meta;
-
-                    api = new AtlasPictureInfo();
-                    api.atlasSize = new Size(meta.size.w, meta.size.h);
-                    api.subPictures = new StringDictionary<AtlasSubPictureInfo>();
-
-                    for (let f of frames) {
-                        let aspi = new AtlasSubPictureInfo();
-                        aspi.name = f.filename;
-                        aspi.location = new Vector2(f.frame.x, api.atlasSize.height - (f.frame.y + f.frame.h));
-                        aspi.size = new Size(f.frame.w, f.frame.h);
-
-                        api.subPictures.add(aspi.name, aspi);
-                    }
-
-                } catch (ex1) {
-                    errorMsg = "Invalid JSON file";
-                    errorCode = -1;
-                }                 
-            } catch (ex2) {
-                errorMsg = "Unknown Exception: " + ex2;
-                errorCode = -2;
-            } 
-            return { api: api, errorMsg: errorMsg, errorCode: errorCode };
-        }
-    }
-
     /**
      * This class will contains information about a sub picture present in an Atlas Picture.
      */
@@ -235,8 +196,6 @@
          * @param plugin the instance of the loader
          */
         public static addLoader(fileExtension: string, plugin: IAtlasLoader) {
-            AtlasPictureInfoFactory._initialize();
-
             let a = AtlasPictureInfoFactory.plugins.getOrAddWithFactory(fileExtension.toLocaleLowerCase(), () => new Array<IAtlasLoader>());
             a.push(plugin);
         }
@@ -245,12 +204,10 @@
          * Load an Atlas Picture Info object from a data file at a given url and with a given texture
          * @param texture the texture containing the atlas bitmap
          * @param url the URL of the Atlas Info data file
-         * @param loaded a callback that will be called when the AtlasPictureInfo object will be loaded and ready
-         * @param error a callback that will be called in case of error
+         * @param onLoad a callback that will be called when the AtlasPictureInfo object will be loaded and ready
+         * @param onError a callback that will be called in case of error
          */
-        public static loadFromUrl(texture: Texture, url: string, loaded: (api: AtlasPictureInfo) => void, error: (msg: string, code: number) => void = null) {
-            AtlasPictureInfoFactory._initialize();
-
+        public static loadFromUrl(texture: Texture, url: string, onLoad: (api: AtlasPictureInfo) => void, onError: (msg: string, code: number) => void = null) {
             var xhr = new XMLHttpRequest();
             xhr.onreadystatechange = () => {
                 if (xhr.readyState === XMLHttpRequest.DONE) {
@@ -258,26 +215,33 @@
                         let ext = url.split('.').pop().split(/\#|\?/)[0];
                         let plugins = AtlasPictureInfoFactory.plugins.get(ext.toLocaleLowerCase());
                         if (!plugins) {
-                            if (error) {
-                                error("couldn't find a plugin for this file extension", -1);
+                            if (onError) {
+                                onError("couldn't find a plugin for this file extension", -1);
                             }
                             return;
                         }
                         for (let p of plugins) {
                             let ret = p.loadFile(xhr.response);
                             if (ret) {
-                                if (ret.api && loaded) {
+                                if (ret.api) {
                                     ret.api.texture = texture;
-                                    loaded(ret.api);
-                                } else if (error) {
-                                    error(ret.errorMsg, ret.errorCode);
+                                    if (onLoad) {
+                                        onLoad(ret.api);
+                                    }
+                                } else if (onError) {
+                                    onError(ret.errorMsg, ret.errorCode);
                                 }
-                                break;
+                                return;
                             }
                         }
+
+                        if (onError) {
+                            onError("No plugin to load this Atlas Data file format", -1);
+                        }
+
                     } else {
-                        if (error) {
-                            error("Couldn't load file through HTTP Request, HTTP Status " + xhr.status, xhr.status);
+                        if (onError) {
+                            onError("Couldn't load file through HTTP Request, HTTP Status " + xhr.status, xhr.status);
                         }
                     }
                 }
@@ -287,16 +251,61 @@
             return null;
         }
 
-        private static _initialize() {
-            if (AtlasPictureInfoFactory.plugins !== null) {
-                return;
-            }
+        private static plugins: StringDictionary<Array<IAtlasLoader>> = new StringDictionary<Array<IAtlasLoader>>();
+    }
 
-            AtlasPictureInfoFactory.plugins = new StringDictionary<Array<IAtlasLoader>>();
-            AtlasPictureInfoFactory.addLoader("json", new JSONArrayLoader());
+     // Loader class for the TexturePacker's JSON Array data format
+    @AtlasLoaderPlugin("json", new JSONArrayLoader())
+    class JSONArrayLoader implements IAtlasLoader {
 
+        loadFile(content): { api: AtlasPictureInfo, errorMsg: string, errorCode: number } {
+            let errorMsg: string = null;
+            let errorCode: number = 0;
+            let root = null;
+            let api: AtlasPictureInfo = null;
+            try {
+                let frames: Array<IFrame>;
+                let meta: IMeta;
+                try {
+                    root = JSON.parse(content);
+                    frames = <Array<IFrame>>root.frames;
+                    meta = <IMeta>root.meta;
+                    if (!frames || !meta) {
+                        throw Error("Not a JSON Array file format");
+                    }
+                } catch (ex1) {
+                    return null;
+                }                 
+
+                api = new AtlasPictureInfo();
+                api.atlasSize = new Size(meta.size.w, meta.size.h);
+                api.subPictures = new StringDictionary<AtlasSubPictureInfo>();
+
+                for (let f of frames) {
+                    let aspi = new AtlasSubPictureInfo();
+                    aspi.name = f.filename;
+                    aspi.location = new Vector2(f.frame.x, api.atlasSize.height - (f.frame.y + f.frame.h));
+                    aspi.size = new Size(f.frame.w, f.frame.h);
+
+                    api.subPictures.add(aspi.name, aspi);
+                }
+            } catch (ex2) {
+                errorMsg = "Unknown Exception: " + ex2;
+                errorCode = -2;
+            } 
+
+            return { api: api, errorMsg: errorMsg, errorCode: errorCode };
         }
+    }
 
-        private static plugins: StringDictionary<Array<IAtlasLoader>> = null;
+    /**
+     * Use this decorator when you declare an Atlas Loader Class for the loader to register itself automatically.
+     * @param fileExtension the extension of the file that the plugin is loading (there can be many plugin for the same extension)
+     * @param plugin an instance of the plugin class to add to the AtlasPictureInfoFactory
+     */
+    export function AtlasLoaderPlugin(fileExtension: string, plugin: IAtlasLoader): (target: Object) => void {
+        return () => {
+            AtlasPictureInfoFactory.addLoader(fileExtension, plugin);
+        }
     }
 }

+ 325 - 84
canvas2D/src/Engine/babylon.fontTexture.ts

@@ -17,17 +17,17 @@
         charWidth: number;
     }
 
-    interface ICharInfoMap {
-        [char: string]: CharInfo;
-    }
-
     /**
      * This is an abstract base class to hold a Texture that will contain a FontMap
      */
     export abstract class BaseFontTexture extends Texture {
 
-        BaseFontTexture() {
-            this._cachedFontId = null;           
+        constructor(url: string, scene: Scene, noMipmap: boolean = false, invertY: boolean = true, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+
+            super(url, scene, noMipmap, invertY, samplingMode);
+
+            this._cachedFontId = null;
+            this._charInfos = new StringDictionary<CharInfo>();
         }
 
         /**
@@ -89,7 +89,49 @@
          * @param text the text to measure
          * @param tabulationSize the size (in space character) of the tabulation character, default value must be 4
          */
-        abstract measureText(text: string, tabulationSize?: number): Size;
+        measureText(text: string, tabulationSize?: number): Size {
+            let maxWidth: number = 0;
+            let curWidth: number = 0;
+            let lineCount = 1;
+            let charxpos: number = 0;
+
+            // Parse each char of the string
+            for (var char of text) {
+
+                // Next line feed?
+                if (char === "\n") {
+                    maxWidth = Math.max(maxWidth, curWidth);
+                    charxpos = 0;
+                    curWidth = 0;
+                    ++lineCount;
+                    continue;
+                }
+
+                // Tabulation ?
+                if (char === "\t") {
+                    let nextPos = charxpos + tabulationSize;
+                    nextPos = nextPos - (nextPos % tabulationSize);
+
+                    curWidth += (nextPos - charxpos) * this.spaceWidth;
+                    charxpos = nextPos;
+                    continue;
+                }
+
+                if (char < " ") {
+                    continue;
+                }
+
+                let ci = this.getChar(char);
+                if (!ci) {
+                    throw new Error(`Character ${char} is not supported by FontTexture ${this.name}`);
+                }
+                curWidth += ci.charWidth;
+                ++charxpos;
+            }
+            maxWidth = Math.max(maxWidth, curWidth);
+
+            return new Size(maxWidth, lineCount * this.lineHeight);
+        }
 
         /**
          * Retrieve the CharInfo object for a given character
@@ -97,17 +139,104 @@
          */
         abstract getChar(char: string): CharInfo;
 
+        protected _charInfos: StringDictionary<CharInfo>;
         protected _lineHeight: number;
         protected _spaceWidth;
         protected _superSample: boolean;
         protected _signedDistanceField: boolean;
-        protected _usedCounter = 1;
         protected _cachedFontId: string;
     }
 
+    export class BitmapFontInfo {
+        kerningDic = new StringDictionary<number>();
+        charDic = new StringDictionary<CharInfo>();
+
+        textureSize : Size;
+        atlasName   : string;
+        padding     : Vector4;       // Left, Top, Right, Bottom
+        lineHeight: number;
+        textureUrl  : string;
+        textureFile : string;
+    }
+
+    export interface IBitmapFontLoader {
+        loadFont(fontDataContent: any, scene: Scene, invertY: boolean): { bfi: BitmapFontInfo, errorMsg: string, errorCode: number };
+    }
+
     export class BitmapFontTexture extends BaseFontTexture {
+        public constructor( scene: Scene,
+                            bmFontUrl: string,
+                            textureUrl: string = null,
+                            noMipmap: boolean = false,
+                            invertY: boolean = true,
+                            samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, 
+                            onLoad: () => void = null,
+                            onError: (msg: string, code: number) => void = null)
+        {
+            super(null, scene, noMipmap, invertY, samplingMode);
+
+            var xhr = new XMLHttpRequest();
+            xhr.onreadystatechange = () => {
+                if (xhr.readyState === XMLHttpRequest.DONE) {
+                    if (xhr.status === 200) {
+                        let ext = bmFontUrl.split('.').pop().split(/\#|\?/)[0];
+                        let plugins = BitmapFontTexture.plugins.get(ext.toLocaleLowerCase());
+                        if (!plugins) {
+                            if (onError) {
+                                onError("couldn't find a plugin for this file extension", -1);
+                            }
+                            return;
+                        }
+
+                        for (let p of plugins) {
+                            let ret = p.loadFont(xhr.response, scene, invertY);
+                            if (ret) {
+                                let bfi = ret.bfi;
+
+                                if (textureUrl != null) {
+                                    bfi.textureUrl = textureUrl;
+                                } else {
+                                    let baseUrl = bmFontUrl.substr(0, bmFontUrl.lastIndexOf("/") + 1);
+                                    bfi.textureUrl = baseUrl + bfi.textureFile;
+                                }
+
+                                this._texture = scene.getEngine().createTexture(bfi.textureUrl, noMipmap, invertY, scene, samplingMode, () => {
+                                    if (ret.bfi && onLoad) {
+                                        onLoad();
+                                    }
+                                });
+
+                                this._lineHeight = bfi.lineHeight;
+                                this._charInfos.copyFrom(bfi.charDic);
+                                let ci = this.getChar(" ");
+                                if (ci) {
+                                    this._spaceWidth = ci.charWidth;
+                                } else {
+                                    this._charInfos.first((k, v) => this._spaceWidth = v.charWidth);
+                                }
+
+                                if (!ret.bfi && onError) {
+                                    onError(ret.errorMsg, ret.errorCode);
+                                }
+                                return;
+                            }
+                        }
+
+                        if (onError) {
+                            onError("No plugin to load this BMFont file format", -1);
+                        }
+                    } else {
+                        if (onError) {
+                            onError("Couldn't load file through HTTP Request, HTTP Status " + xhr.status, xhr.status);
+                        }
+                    }
+                }
+            }
+            xhr.open("GET", bmFontUrl, true);
+            xhr.send();
+        }
 
-        public static GetCachedFontTexture(scene: Scene, fontTexture: Texture): BitmapFontTexture {
+        public static GetCachedFontTexture(scene: Scene, fontTexture: BitmapFontTexture): BitmapFontTexture {
             let dic = scene.getOrAddExternalDataWithFactory("BitmapFontTextureCache", () => new StringDictionary<BitmapFontTexture>());
 
             let ft = dic.get(fontTexture.uid);
@@ -121,38 +250,20 @@
             return ft;
         }
 
-        public static ReleaseCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false, signedDistanceField: boolean = false) {
-            let dic = scene.getExternalData<StringDictionary<FontTexture>>("BitmapFontTextureCache");
+        public static ReleaseCachedFontTexture(scene: Scene, fontTexture: BitmapFontTexture) {
+            let dic = scene.getExternalData<StringDictionary<BitmapFontTexture>>("BitmapFontTextureCache");
             if (!dic) {
                 return;
             }
 
-            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS") + (signedDistanceField ? "_+SDF" : "_-SDF");
-            var font = dic.get(lfn);
+            var font = dic.get(fontTexture.uid);
             if (--font._usedCounter === 0) {
-                dic.remove(lfn);
+                dic.remove(fontTexture.uid);
                 font.dispose();
             }
         }
 
         /**
-         * When the FontTexture is retrieved through the FontCache, there's a reference counter that is incremented for each use.
-         * You also have the possibility to extend the lifetime of the FontTexture when passing it to another object by calling this method
-         * Don't forget to call the corresponding decCachedFontTextureCounter method when you no longer have use of the FontTexture.
-         * Each call to incCachedFontTextureCounter must have a corresponding call to decCachedFontTextureCounter.
-         */
-        incCachedFontTextureCounter() {
-            
-        }
-
-        /**
-         * Decrement the reference counter, if it reaches 0 the FontTexture is disposed
-         */
-        decCachedFontTextureCounter() {
-            
-        }
-
-        /**
          * Is the font dynamically updated, if true is returned then you have to call the update() before using the font in rendering if new character were adding using getChar()
          */
         get isDynamicFontTexture(): boolean {
@@ -166,23 +277,42 @@
         }
 
         /**
-         * Measure the width/height that will take a given text
-         * @param text the text to measure
-         * @param tabulationSize the size (in space character) of the tabulation character, default value must be 4
+         * Retrieve the CharInfo object for a given character
+         * @param char the character to retrieve the CharInfo object from (e.g.: "A", "a", etc.)
          */
-        measureText(text: string, tabulationSize?: number): Size {
-            return null;
+        getChar(char: string): CharInfo {
+            return this._charInfos.get(char);
+        }
 
+        /**
+         * For FontTexture retrieved using GetCachedFontTexture, use this method when you transfer this object's lifetime to another party in order to share this resource.
+         * When the other party is done with this object, decCachedFontTextureCounter must be called.
+         */
+        public incCachedFontTextureCounter() {
+            ++this._usedCounter;
         }
 
         /**
-         * Retrieve the CharInfo object for a given character
-         * @param char the character to retrieve the CharInfo object from (e.g.: "A", "a", etc.)
+         * Use this method only in conjunction with incCachedFontTextureCounter, call it when you no longer need to use this shared resource.
          */
-        getChar(char: string): CharInfo {
-            return null;
+        public decCachedFontTextureCounter() {
+            let dic = this.getScene().getExternalData<StringDictionary<BitmapFontTexture>>("BitmapFontTextureCache");
+            if (!dic) {
+                return;
+            }
+            if (--this._usedCounter === 0) {
+                dic.remove(this._cachedFontId);
+                this.dispose();
+            }
+        }
+        private _usedCounter = 1;
+
+        static addLoader(fileExtension: string, plugin: IBitmapFontLoader) {
+            let a = BitmapFontTexture.plugins.getOrAddWithFactory(fileExtension.toLocaleLowerCase(), () => new Array<IBitmapFontLoader>());
+            a.push(plugin);
         }
 
+        static plugins: StringDictionary<IBitmapFontLoader[]> = new StringDictionary<Array<IBitmapFontLoader>>();
     }
 
     /**
@@ -202,13 +332,13 @@
         private _yMargin: number;
         private _offset: number;
         private _currentFreePosition: Vector2;
-        private _charInfos: ICharInfoMap = {};
         private _curCharCount = 0;
         private _lastUpdateCharCount = -1;
         private _spaceWidthSuper;
         private _sdfCanvas: HTMLCanvasElement;
         private _sdfContext: CanvasRenderingContext2D;
         private _sdfScale: number;
+        private _usedCounter = 1;
 
         get isDynamicFontTexture(): boolean {
             return true;
@@ -254,7 +384,8 @@
          * @param samplingMode the texture sampling mode
          * @param superSample if true the FontTexture will be created with a font of a size twice bigger than the given one but all properties (lineHeight, charWidth, etc.) will be according to the original size. This is made to improve the text quality.
          */
-        constructor(name: string, font: string, scene: Scene, maxCharCount=200, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, superSample: boolean = false, signedDistanceField: boolean = false) {
+        constructor(name: string, font: string, scene: Scene, maxCharCount = 200, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, superSample: boolean = false, signedDistanceField: boolean = false) {
+
             super(null, scene, true, false, samplingMode);
 
             this.name = name;
@@ -355,7 +486,7 @@
                 return null;
             }
 
-            var info = this._charInfos[char];
+            var info = this._charInfos.get(char);
             if (info) {
                 return info;
             }
@@ -403,7 +534,7 @@
             info.charWidth = this._superSample ? (width/2) : width;
 
             // Add the info structure
-            this._charInfos[char] = info;
+            this._charInfos.add(char, info);
             this._curCharCount++;
 
             // Set the next position
@@ -559,46 +690,6 @@
             return res;
         }
 
-        public measureText(text: string, tabulationSize: number = 4): Size {
-            let maxWidth: number = 0;
-            let curWidth: number = 0;
-            let lineCount = 1;
-            let charxpos: number = 0;
-
-            // Parse each char of the string
-            for (var char of text) {
-
-                // Next line feed?
-                if (char === "\n") {
-                    maxWidth = Math.max(maxWidth, curWidth);
-                    charxpos = 0;
-                    curWidth = 0;
-                    ++lineCount;
-                    continue;
-                }
-
-                // Tabulation ?
-                if (char === "\t") {
-                    let nextPos = charxpos + tabulationSize;
-                    nextPos = nextPos - (nextPos % tabulationSize);
-
-                    curWidth += (nextPos - charxpos) * this.spaceWidth;
-                    charxpos = nextPos;
-                    continue;
-                }
-
-                if (char < " ") {
-                    continue;
-                }
-
-                curWidth += this.getChar(char).charWidth;
-                ++charxpos;
-            }
-            maxWidth = Math.max(maxWidth, curWidth);
-
-            return new Size(maxWidth, lineCount * this.lineHeight);
-        }
-
         private getSuperSampleFont(font: string): string {
             // Eternal thank to http://stackoverflow.com/a/10136041/802124
             let regex = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-,\"\sa-z]+?)\s*$/;
@@ -700,4 +791,154 @@
         }
 
     }
+
+    /**
+     * Orginial code from cocos2d-js, converted to TypeScript by Nockawa
+     * Load the Text version of the BMFont format, no XML or binary supported, just plain old text
+     */
+    @BitmapFontLoaderPlugin("fnt", new BMFontLoaderTxt())
+    class BMFontLoaderTxt implements IBitmapFontLoader {
+        private static INFO_EXP    = /info [^\r\n]*(\r\n|$)/gi;
+        private static COMMON_EXP  = /common [^\n]*(\n|$)/gi;
+        private static PAGE_EXP    = /page [^\n]*(\n|$)/gi;
+        private static CHAR_EXP    = /char [^\n]*(\n|$)/gi;
+        private static KERNING_EXP = /kerning [^\n]*(\n|$)/gi;
+        private static ITEM_EXP    = /\w+=[^ \r\n]+/gi;
+        private static INT_EXP     = /^[\-]?\d+$/;
+
+        private _parseStrToObj(str) {
+            var arr = str.match(BMFontLoaderTxt.ITEM_EXP);
+            if (!arr) {
+                return null;
+            }
+
+            var obj = {};
+            for (var i = 0, li = arr.length; i < li; i++) {
+                var tempStr = arr[i];
+                var index = tempStr.indexOf("=");
+                var key = tempStr.substring(0, index);
+                var value = tempStr.substring(index + 1);
+                if (value.match(BMFontLoaderTxt.INT_EXP)) value = parseInt(value);
+                else if (value[0] === '"') value = value.substring(1, value.length - 1);
+                obj[key] = value;
+            }
+            return obj;
+        }
+
+        private _buildCharInfo(initialLine: string, obj: any, textureSize: Size, invertY: boolean, chars: StringDictionary<CharInfo>) {
+            let char: string = null;
+            let x: number = null;
+            let y: number = null;
+            let xadv: number = null;
+            let width: number = null;
+            let height: number = null;
+            let ci = new CharInfo();
+            for (let key in obj) {
+                let value = obj[key];
+                switch (key) {
+                    case "id":
+                        char = String.fromCharCode(value);
+                        break;
+                    case "x":
+                        x = value;
+                        break;
+                    case "y":
+                        y = value;
+                        break;
+                    case "width":
+                        width = value;
+                        break;
+                    case "height":
+                        height = value;
+                        break;
+                    case "xadvance":
+                        xadv = value;
+                        break;
+                }
+            }
+
+            if (x != null && y != null && width != null && height != null && char != null) {
+                if (xadv) {
+                    width = xadv;
+                }
+                if (invertY) {
+                    ci.topLeftUV = new Vector2(1 - (x / textureSize.width), 1 - (y / textureSize.height));
+                    ci.bottomRightUV = new Vector2(1 - ((x + width) / textureSize.width), 1 - ((y + height) / textureSize.height));
+                } else {
+                    ci.topLeftUV = new Vector2(x / textureSize.width, y / textureSize.height);
+                    ci.bottomRightUV = new Vector2((x + width) / textureSize.width, (y + height) / textureSize.height);
+                }
+                ci.charWidth = width;
+                chars.add(char, ci);
+            } else {
+                console.log("Error while parsing line " + initialLine);
+            }
+        }
+
+        public loadFont(fontContent: any, scene: Scene, invertY: boolean): { bfi: BitmapFontInfo, errorMsg: string, errorCode: number } {
+            let fontStr = <string>fontContent;
+            let bfi = new BitmapFontInfo();
+            let errorCode = 0;
+            let errorMsg = "OK";
+
+            //padding
+            let info = fontStr.match(BMFontLoaderTxt.INFO_EXP);
+            let infoObj = this._parseStrToObj(info[0]);
+            if (!infoObj) {
+                return null;
+            }
+            let paddingArr = infoObj["padding"].split(",");
+            bfi.padding = new Vector4(parseInt(paddingArr[0]), parseInt(paddingArr[1]), parseInt(paddingArr[2]), parseInt(paddingArr[3]));
+
+            //common
+            var commonObj = this._parseStrToObj(fontStr.match(BMFontLoaderTxt.COMMON_EXP)[0]);
+            bfi.lineHeight = commonObj["lineHeight"];
+            bfi.textureSize = new Size(commonObj["scaleW"], commonObj["scaleH"]);
+
+            var maxTextureSize = scene.getEngine()._gl.getParameter(0xd33);
+            if (commonObj["scaleW"] > maxTextureSize.width || commonObj["scaleH"] > maxTextureSize.height) {
+                errorMsg = "FontMap texture's size is bigger than what WebGL supports";
+                errorCode = -1;
+            } else {
+                if (commonObj["pages"] !== 1) {
+                    errorMsg = "FontMap must contain one page only.";
+                    errorCode = -1;
+                } else {
+                    //page
+                    let pageObj = this._parseStrToObj(fontStr.match(BMFontLoaderTxt.PAGE_EXP)[0]);
+                    if (pageObj["id"] !== 0) {
+                        errorMsg = "Only one page of ID 0 is supported";
+                        errorCode = -1;
+                    } else {
+                        bfi.textureFile = pageObj["file"];
+
+                        //char
+                        let charLines = fontStr.match(BMFontLoaderTxt.CHAR_EXP);
+                        for (let i = 0, li = charLines.length; i < li; i++) {
+                            let charObj = this._parseStrToObj(charLines[i]);
+                            this._buildCharInfo(charLines[i], charObj, bfi.textureSize, invertY, bfi.charDic);
+                        }
+
+                        //kerning
+                        var kerningLines = fontStr.match(BMFontLoaderTxt.KERNING_EXP);
+                        if (kerningLines) {
+                            for (let i = 0, li = kerningLines.length; i < li; i++) {
+                                let kerningObj = this._parseStrToObj(kerningLines[i]);
+                                bfi.kerningDic.add(((kerningObj["first"] << 16) | (kerningObj["second"] & 0xffff)).toString(), kerningObj["amount"]);
+                            }
+                        }
+                    }
+                }
+
+            }
+            return { bfi: bfi, errorCode: errorCode, errorMsg: errorMsg };
+        }
+    };
+
+    export function BitmapFontLoaderPlugin(fileExtension: string, plugin: IBitmapFontLoader): (target: Object) => void {
+        return () => {
+            BitmapFontTexture.addLoader(fileExtension, plugin);
+        }
+    }
+
 } 

+ 14 - 6
canvas2D/src/Engine/babylon.text2d.ts

@@ -4,7 +4,7 @@
         vb: WebGLBuffer                                 = null;
         ib: WebGLBuffer                                 = null;
         instancingAttributes: InstancingAttributeInfo[] = null;
-        fontTexture: FontTexture                        = null;
+        fontTexture: BaseFontTexture                    = null;
         effect: Effect                                  = null;
         effectInstanced: Effect                         = null;
 
@@ -266,7 +266,7 @@
             return this._textSize;
         }
 
-        protected get fontTexture(): FontTexture {
+        protected get fontTexture(): BaseFontTexture {
             if (this._fontTexture) {
                 return this._fontTexture;
             }
@@ -356,6 +356,7 @@
             fontName                ?: string,
             fontSuperSample         ?: boolean,
             fontSignedDistanceField ?: boolean,
+            bitmapFontTexture       ?: BitmapFontTexture,
             defaultFontColor        ?: Color4,
             size                    ?: Size,
             tabulationSize          ?: number,
@@ -384,9 +385,16 @@
 
             super(settings);
 
-            this.fontName            = (settings.fontName==null) ? "12pt Arial" : settings.fontName;
-            this._fontSuperSample    = (settings.fontSuperSample!=null && settings.fontSuperSample);
-            this._fontSDF            = (settings.fontSignedDistanceField!=null && settings.fontSignedDistanceField);
+            if (settings.bitmapFontTexture != null) {
+                this._fontTexture     = settings.bitmapFontTexture;
+                this._fontName        = null;
+                this._fontSuperSample = false;
+                this._fontSDF         = false;
+            } else {
+                this._fontName       = (settings.fontName==null) ? "12pt Arial" : settings.fontName;
+                this._fontSuperSample= (settings.fontSuperSample!=null && settings.fontSuperSample);
+                this._fontSDF        = (settings.fontSignedDistanceField!=null && settings.fontSignedDistanceField);
+            }
             this.defaultFontColor    = (settings.defaultFontColor==null) ? new Color4(1,1,1,1) : settings.defaultFontColor;
             this._tabulationSize     = (settings.tabulationSize == null) ? 4 : settings.tabulationSize;
             this._textSize           = null;
@@ -547,7 +555,7 @@
             return !this._fontSDF;
         }
 
-        private _fontTexture: FontTexture;
+        private _fontTexture: BaseFontTexture;
         private _tabulationSize: number;
         private _charCount: number;
         private _fontName: string;