Преглед изворни кода

FontTexture & Canvas2D

Adding the superSample setting to FontTexture to double the stored font size.

Adding the fontSuperSample setting to Text2D to render a text using a superSample FontTexture
nockawa пре 9 година
родитељ
комит
97cd5cf768
3 измењених фајлова са 68 додато и 18 уклоњено
  1. 14 3
      src/Canvas2d/babylon.text2d.ts
  2. 52 14
      src/Materials/Textures/babylon.fontTexture.ts
  3. 2 1
      src/Shaders/text2d.vertex.fx

+ 14 - 3
src/Canvas2d/babylon.text2d.ts

@@ -111,6 +111,11 @@
         get color(): Color4 {
         get color(): Color4 {
             return null;
             return null;
         }
         }
+
+        @instanceData()
+        get superSampleFactor(): number {
+            return null;
+        }
     }
     }
 
 
     @className("Text2D")
     @className("Text2D")
@@ -223,7 +228,7 @@
                 return this._fontTexture;
                 return this._fontTexture;
             }
             }
 
 
-            this._fontTexture = FontTexture.GetCachedFontTexture(this.owner.scene, this.fontName);
+            this._fontTexture = FontTexture.GetCachedFontTexture(this.owner.scene, this.fontName, this._fontSuperSample);
             return this._fontTexture;
             return this._fontTexture;
         }
         }
 
 
@@ -236,7 +241,7 @@
             }
             }
 
 
             if (this._fontTexture) {
             if (this._fontTexture) {
-                FontTexture.ReleaseCachedFontTexture(this.owner.scene, this.fontName);
+                FontTexture.ReleaseCachedFontTexture(this.owner.scene, this.fontName, this._fontSuperSample);
                 this._fontTexture = null;
                 this._fontTexture = null;
             }
             }
 
 
@@ -259,6 +264,7 @@
          *  - scale: the initial scale of the primitive. default is 1
          *  - scale: the initial scale of the primitive. default is 1
          *  - origin: define the normalized origin point location, default [0.5;0.5]
          *  - origin: define the normalized origin point location, default [0.5;0.5]
          *  - fontName: the name/size/style of the font to use, following the CSS notation. Default is "12pt Arial".
          *  - fontName: the name/size/style of the font to use, following the CSS notation. Default is "12pt Arial".
+         *  - fontSuperSample: if true the text will be rendered with a superSampled font (the font is twice the given size). Use this settings if the text lies in world space or if it's scaled in.
          *  - defaultColor: the color by default to apply on each letter of the text to display, default is plain white.
          *  - defaultColor: the color by default to apply on each letter of the text to display, default is plain white.
          *  - areaSize: the size of the area in which to display the text, default is auto-fit from text content.
          *  - areaSize: the size of the area in which to display the text, default is auto-fit from text content.
          *  - tabulationSize: number of space character to insert when a tabulation is encountered, default is 4
          *  - tabulationSize: number of space character to insert when a tabulation is encountered, default is 4
@@ -289,6 +295,7 @@
             scale            ?: number,
             scale            ?: number,
             origin           ?: Vector2,
             origin           ?: Vector2,
             fontName         ?: string,
             fontName         ?: string,
+            fontSuperSample  ?: boolean,
             defaultFontColor ?: Color4,
             defaultFontColor ?: Color4,
             size             ?: Size,
             size             ?: Size,
             tabulationSize   ?: number,
             tabulationSize   ?: number,
@@ -314,7 +321,8 @@
 
 
             super(settings);
             super(settings);
 
 
-            this.fontName         = (settings.fontName==null)         ? "12pt Arial"        : settings.fontName;
+            this.fontName         = (settings.fontName==null) ? "12pt Arial" : settings.fontName;
+            this._fontSuperSample = (settings.fontSuperSample!=null && settings.fontSuperSample);
             this.defaultFontColor = (settings.defaultFontColor==null) ? new Color4(1,1,1,1) : settings.defaultFontColor;
             this.defaultFontColor = (settings.defaultFontColor==null) ? new Color4(1,1,1,1) : settings.defaultFontColor;
             this._tabulationSize  = (settings.tabulationSize == null) ? 4 : settings.tabulationSize;
             this._tabulationSize  = (settings.tabulationSize == null) ? 4 : settings.tabulationSize;
             this._textSize        = null;
             this._textSize        = null;
@@ -397,6 +405,7 @@
             if (part.id === Text2D.TEXT2D_MAINPARTID) {
             if (part.id === Text2D.TEXT2D_MAINPARTID) {
                 let d = <Text2DInstanceData>part;
                 let d = <Text2DInstanceData>part;
                 let texture = this.fontTexture;
                 let texture = this.fontTexture;
+                let superSampleFactor = texture.isSuperSampled ? 0.5 : 1;
                 let ts = texture.getSize();
                 let ts = texture.getSize();
                 let offset = Vector2.Zero();
                 let offset = Vector2.Zero();
                 let lh = this.fontTexture.lineHeight;
                 let lh = this.fontTexture.lineHeight;
@@ -436,6 +445,7 @@
                     d.sizeUV = suv;
                     d.sizeUV = suv;
                     d.textureSize = new Vector2(ts.width, ts.height);
                     d.textureSize = new Vector2(ts.width, ts.height);
                     d.color = this.defaultFontColor;
                     d.color = this.defaultFontColor;
+                    d.superSampleFactor = superSampleFactor;
 
 
                     ++d.curElement;
                     ++d.curElement;
                 }
                 }
@@ -458,6 +468,7 @@
         private _tabulationSize: number;
         private _tabulationSize: number;
         private _charCount: number;
         private _charCount: number;
         private _fontName: string;
         private _fontName: string;
+        private _fontSuperSample: boolean;
         private _defaultFontColor: Color4;
         private _defaultFontColor: Color4;
         private _text: string;
         private _text: string;
         private _textSize: Size;
         private _textSize: Size;

+ 52 - 14
src/Materials/Textures/babylon.fontTexture.ts

@@ -25,13 +25,20 @@
         private _canvas: HTMLCanvasElement;
         private _canvas: HTMLCanvasElement;
         private _context: CanvasRenderingContext2D;
         private _context: CanvasRenderingContext2D;
         private _lineHeight: number;
         private _lineHeight: number;
+        private _lineHeightSuper: number;
         private _offset: number;
         private _offset: number;
         private _currentFreePosition: Vector2;
         private _currentFreePosition: Vector2;
         private _charInfos: ICharInfoMap = {};
         private _charInfos: ICharInfoMap = {};
         private _curCharCount = 0;
         private _curCharCount = 0;
         private _lastUpdateCharCount = -1;
         private _lastUpdateCharCount = -1;
         private _spaceWidth;
         private _spaceWidth;
+        private _spaceWidthSuper;
         private _usedCounter = 1;
         private _usedCounter = 1;
+        private _superSample: boolean;
+
+        public get isSuperSampled(): boolean {
+            return this._superSample;
+        }
 
 
         public get spaceWidth(): number {
         public get spaceWidth(): number {
             return this._spaceWidth;
             return this._spaceWidth;
@@ -41,7 +48,7 @@
             return this._lineHeight;
             return this._lineHeight;
         }
         }
 
 
-        public static GetCachedFontTexture(scene: Scene, fontName: string) {
+        public static GetCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false) {
             let s = <any>scene;
             let s = <any>scene;
             if (!s.__fontTextureCache__) {
             if (!s.__fontTextureCache__) {
                 s.__fontTextureCache__ = new StringDictionary<FontTexture>();
                 s.__fontTextureCache__ = new StringDictionary<FontTexture>();
@@ -49,27 +56,27 @@
 
 
             let dic = <StringDictionary<FontTexture>>s.__fontTextureCache__;
             let dic = <StringDictionary<FontTexture>>s.__fontTextureCache__;
 
 
-            let lfn = fontName.toLocaleLowerCase();
+            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS");
             let ft = dic.get(lfn);
             let ft = dic.get(lfn);
             if (ft) {
             if (ft) {
                 ++ft._usedCounter;
                 ++ft._usedCounter;
                 return ft;
                 return ft;
             }
             }
 
 
-            ft = new FontTexture(null, lfn, scene, 200, Texture.NEAREST_SAMPLINGMODE);
+            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, Texture.BILINEAR_SAMPLINGMODE, supersample);
             dic.add(lfn, ft);
             dic.add(lfn, ft);
 
 
             return ft;
             return ft;
         }
         }
 
 
-        public static ReleaseCachedFontTexture(scene: Scene, fontName: string) {
+        public static ReleaseCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false) {
             let s = <any>scene;
             let s = <any>scene;
             let dic = <StringDictionary<FontTexture>>s.__fontTextureCache__;
             let dic = <StringDictionary<FontTexture>>s.__fontTextureCache__;
             if (!dic) {
             if (!dic) {
                 return;
                 return;
             }
             }
 
 
-            let lfn = fontName.toLocaleLowerCase();
+            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS");
             var font = dic.get(lfn);
             var font = dic.get(lfn);
             if (--font._usedCounter === 0) {
             if (--font._usedCounter === 0) {
                 dic.remove(lfn);
                 dic.remove(lfn);
@@ -84,8 +91,9 @@
          * @param scene the scene that owns the texture
          * @param scene the scene that owns the texture
          * @param maxCharCount the approximative maximum count of characters that could fit in the texture. This is an approximation because most of the fonts are proportional (each char has its own Width). The 'W' character's width is used to compute the size of the texture based on the given maxCharCount
          * @param maxCharCount the approximative maximum count of characters that could fit in the texture. This is an approximation because most of the fonts are proportional (each char has its own Width). The 'W' character's width is used to compute the size of the texture based on the given maxCharCount
          * @param samplingMode the texture sampling mode
          * @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) {
+        constructor(name: string, font: string, scene: Scene, maxCharCount=200, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, superSample: boolean = false) {
             super(null, scene, true, false, samplingMode);
             super(null, scene, true, false, samplingMode);
 
 
             this.name = name;
             this.name = name;
@@ -93,6 +101,15 @@
             this.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.wrapV = Texture.CLAMP_ADDRESSMODE;
             this.wrapV = Texture.CLAMP_ADDRESSMODE;
 
 
+            this._superSample = false;
+            if (superSample) {
+                let sfont = this.getSuperSampleFont(font);
+                if (sfont) {
+                    this._superSample = true;
+                    font = sfont;
+                }
+            }
+
             // First canvas creation to determine the size of the texture to create
             // First canvas creation to determine the size of the texture to create
             this._canvas = document.createElement("canvas");
             this._canvas = document.createElement("canvas");
             this._context = this._canvas.getContext("2d");
             this._context = this._canvas.getContext("2d");
@@ -100,14 +117,16 @@
             this._context.fillStyle = "white";
             this._context.fillStyle = "white";
 
 
             var res = this.getFontHeight(font);
             var res = this.getFontHeight(font);
-            this._lineHeight = res.height;
+            this._lineHeightSuper = res.height;
+            this._lineHeight = this._superSample ? (this._lineHeightSuper / 2) : this._lineHeightSuper;
             this._offset = res.offset-1;
             this._offset = res.offset-1;
 
 
             var maxCharWidth = this._context.measureText("W").width;
             var maxCharWidth = this._context.measureText("W").width;
-            this._spaceWidth = this._context.measureText(" ").width;
+            this._spaceWidthSuper = this._context.measureText(" ").width;
+            this._spaceWidth = this._superSample ? (this._spaceWidthSuper / 2) : this._spaceWidthSuper;
 
 
             // This is an approximate size, but should always be able to fit at least the maxCharCount
             // This is an approximate size, but should always be able to fit at least the maxCharCount
-            var totalEstSurface = this._lineHeight * maxCharWidth * maxCharCount;
+            var totalEstSurface = this._lineHeightSuper * maxCharWidth * maxCharCount;
             var edge = Math.sqrt(totalEstSurface);
             var edge = Math.sqrt(totalEstSurface);
             var textSize = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
             var textSize = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
 
 
@@ -157,11 +176,11 @@
 
 
             // we reached the end of the current line?
             // we reached the end of the current line?
             let width = Math.round(measure.width);
             let width = Math.round(measure.width);
-            var xMargin = Math.ceil(this._lineHeight/20);
+            var xMargin = Math.ceil(this._lineHeightSuper/20);
             var yMargin = xMargin;
             var yMargin = xMargin;
             if (this._currentFreePosition.x + width + xMargin > textureSize.width) {
             if (this._currentFreePosition.x + width + xMargin > textureSize.width) {
                 this._currentFreePosition.x = 0;
                 this._currentFreePosition.x = 0;
-                this._currentFreePosition.y += this._lineHeight + yMargin;
+                this._currentFreePosition.y += this._lineHeightSuper + yMargin;
 
 
                 // No more room?
                 // No more room?
                 if (this._currentFreePosition.y > textureSize.height) {
                 if (this._currentFreePosition.y > textureSize.height) {
@@ -174,8 +193,8 @@
 
 
             // Fill the CharInfo object
             // Fill the CharInfo object
             info.topLeftUV = new Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
             info.topLeftUV = new Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
-            info.bottomRightUV = new Vector2((this._currentFreePosition.x + width) / textureSize.width, info.topLeftUV.y + ((this._lineHeight + 2) / textureSize.height));
-            info.charWidth = width;
+            info.bottomRightUV = new Vector2((this._currentFreePosition.x + width) / textureSize.width, info.topLeftUV.y + ((this._lineHeightSuper + 2) / textureSize.height));
+            info.charWidth = this._superSample ? (width/2) : width;
 
 
             // Add the info structure
             // Add the info structure
             this._charInfos[char] = info;
             this._charInfos[char] = info;
@@ -224,7 +243,26 @@
             }
             }
             maxWidth = Math.max(maxWidth, curWidth);
             maxWidth = Math.max(maxWidth, curWidth);
 
 
-            return new Size(maxWidth, lineCount * this._lineHeight);
+            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*$/;
+            let res = font.toLocaleLowerCase().match(regex);
+            if (res == null) {
+                return null;
+            }
+            let size = parseInt(res[4]);
+            res[4] = (size * 2).toString() + (res[4].match(/\D+/) || []).pop();
+
+            let newFont = "";
+            for (let j = 1; j < res.length; j++) {
+                if (res[j] != null) {
+                    newFont += res[j] + " ";
+                }
+            }
+            return newFont;
         }
         }
 
 
         // More info here: https://videlais.com/2014/03/16/the-many-and-varied-problems-with-measuring-font-height-for-html5-canvas/
         // More info here: https://videlais.com/2014/03/16/the-many-and-varied-problems-with-measuring-font-height-for-html5-canvas/

+ 2 - 1
src/Shaders/text2d.vertex.fx

@@ -16,6 +16,7 @@ att vec2 topLeftUV;
 att vec2 sizeUV;
 att vec2 sizeUV;
 att vec2 textureSize;
 att vec2 textureSize;
 att vec4 color;
 att vec4 color;
+att float superSampleFactor;
 
 
 // Output
 // Output
 varying vec2 vUV;
 varying vec2 vUV;
@@ -54,7 +55,7 @@ void main(void) {
 
 
 	vColor = color;
 	vColor = color;
 	vec4 pos;
 	vec4 pos;
-	pos.xy = floor(pos2.xy * sizeUV * textureSize);	// Align on target pixel to avoid bad interpolation
+	pos.xy = floor(pos2.xy * superSampleFactor * sizeUV * textureSize);	// Align on target pixel to avoid bad interpolation
 	pos.z = 1.0;
 	pos.z = 1.0;
 	pos.w = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);