javascript - 如何选择在canvas2d中制作字形纹理的字体大小

标签 javascript canvas fonts webgl

我想使用 Canvas2D 为 WebGL 制作字形纹理。字形纹理是一种充满字母的纹理,用于渲染文本。

我遇到的问题是,在给定 Canvas API 的情况下,我看不到如何以跨平台的“好”方式做到这一点。我是什么意思?例如,如果我使用一些旧的 8 位字体,其中我知道每个字符都是 8x8 像素,那么我就会知道如果我想说 32 个字形,我需要一个 32 个 8x8 单元的倍数的纹理。比如 256x8 或 128x16 或 64x32 等。我会将每个字形的字符放入每个单元格中并完成。

不幸的是,我看不到任何好的方法来使用 canvas2D api 计算出字母的大小,而无需做大量的工作。

例如:假设我想要 8x16 EGA 之类的字形。好吧,让我们检查一下。我告诉浏览器我想要一个“8px 等宽”字体。

var ctx = document.createElement("canvas").getContext("2d");
ctx.font = "8px monospace";
var dim = {
  minWidth: 100,
  maxWidth: 0,
};
for (var ii = 33; ii < 128; ++ii) {
  var letter = String.fromCharCode(ii);
  var t = ctx.measureText(letter);
  dim.minWidth = Math.min(t.width, dim.minWidth);
  dim.maxWidth = Math.max(t.width, dim.maxWidth);
}
console.log(dim);

打印

// Object {minWidth: 0, maxWidth: 4.80078125}

这显然不是我想要的。除此之外,AFAICT 的字体大小取决于浏览器和平台。我什至不确定我是否使用自己的字体是否可以保证针对给定的字体大小规范呈现字形的大小。

一种方法是尝试不同的尺寸,测量它们的字符,然后选择最接近的匹配。这听起来效率很低。

另一种可能只是选择一个尺寸并渲染所有 Angular 色,然后使用它们在 WebGL 中使用时缩放的尺寸。在这种情况下,尽管选择“8px monospace”会产生一个非常难看的字体,至少在 Chrome 中是这样。我可以迭代,直到找到看起来不错的东西,但随后我不知道它在另一个浏览器或另一个平台上是否看起来不错。

是否有一些官方方法可以知道字体将呈现的大小,以便您可以制作字形?或者还有其他一些关于如何做到这一点的引用吗?就像非浏览器 native 应用程序如何处理这个问题一样。

我想对于大多数 native 应用程序来说,您只需让用户选择字体大小,然后在空间不足时进行剪辑或换行。但对于游戏来说,您经常需要文本适应某些特定大小的空间,因此您需要选择一种能够很好地适应该空间的字体,这似乎就是问题所在。

顺便说一句:这个问题是在 this article about text in WebGL 中提出的。 .

最佳答案

我根本不建议使用canvas2d(编辑:因为canvas2d在所有与字形相关的事情上都很糟糕,不仅限于无法计算出字形的高度,它不可避免导致大量的工作。)在我看来,SVG 或 HTML(取决于您的喜好)都非常擅长与字体相关的所有内容,而 CSS 使调整所有与字体相关的选项和指标变得非常容易。

我认为(但不确定 100%)所有主流浏览器都支持通过数据 uri 将 svg 光栅化为纹理。

let img = new Image();
img.src = `data:image/svg+xml,
    <svg xmlns="http://www.w3.org/2000/svg" width="128" height="128">
        <!-- some grid layout with inlined styles -->
    </svg>
`;
 img.onload = function ( loadedTexture) { ... } 

(如果你想使用html,它显然必须是嵌入到<foreignObject>标签中的svg中的有效xhtml。我不知道这是怎么回事。)

编辑:添加了an example,基本上仅使用HTMLElement.prototype.getBoundingClientRect()来测量字形并渲染任何字体,而不仅仅是等宽字体系列。一个潜在的陷阱:您需要显式设置一种字体,它可能会为纹理呈现不同的字体。 TT

var img = new Image;

img.src = "data:image/svg+xml,"+glyphSource.outerHTML;

img.onload = function( ) {
    document.body.appendChild( img );
    var canvas = document.createElement("canvas");
    canvas.width = canvas.height = 128;
    
    document.body.appendChild( canvas );
    var container = glyphSource.children[0].children[0];
    
    

    var glyphs = [].slice.call( container.children );
    var cR = glyphs[0].getBoundingClientRect();
    var minY = cR.top;
    var minX = cR.left;
    var w = 128;
    var h = 128;
    
    const SIZE = 128;
    const DIM = .25;
    
    var glyphUV = glyphs.map( function( glyph ) {
        var d = glyph.getBoundingClientRect();
        //sampling upside down
       
        return [ 
            d.left,d.top,
            d.right,d.top,  
            d.left,d.bottom,
            d.left,d.bottom,
            d.right,d.top,
            d.right,d.bottom
        ];
    });
    
    const GLYPH_ENUM = {};
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890".split("")
    .forEach( function ( letter, index ) { GLYPH_ENUM[ letter ] = index } );
    console.log( "a",glyphs[ GLYPH_ENUM[ "m" ] ].getBoundingClientRect() );
    var posArr = [];
    var uvArr = [];
    var totalLength = 0;
    var SCALE = 2;
    function writeString( str ) {        
        var offX = 0;
        var offY = 0;
        str.split("").forEach( function( letter ) {
            var letterIndex = GLYPH_ENUM[ letter ];
            var r = glyphs[ letterIndex ].getBoundingClientRect();
            posArr.push(
                offX, offY,
                offX + r.width, offY,
                offX, offY + r.height,
                offX, offY + r.height,
                offX + r.width, offY,
                offX + r.width, offY + r.height
            );
            offX += r.width;
            totalLength++;
            uvArr.push.apply( uvArr, glyphUV[ letterIndex ] );
        });
    };
    writeString("Textrendering");

    window.gl = canvas.getContext("webgl");
    var program = gl.createProgram();
    var fs = gl.createShader( gl.FRAGMENT_SHADER );
    
    gl.shaderSource( fs, `
        precision mediump float;
        varying vec2 texCoord;
        uniform sampler2D glyphs;
        void main ( void ) {
            vec2 uv = gl_FragCoord.xy;
            gl_FragColor = texture2D( glyphs, texCoord );
        
        }
    `);
    gl.compileShader( fs );
    
    if (! gl.getShaderParameter( fs, gl.COMPILE_STATUS ) ) 
        return console.warn( gl.getShaderInfoLog( fs ) );
    
    var vs = gl.createShader( gl.VERTEX_SHADER );
    gl.shaderSource( vs, `
        attribute vec2 pos;
        attribute vec2 uv;
        varying vec2 texCoord;
        #define SCALE 2.5
        void main ( void ) {
            vec2 p = ( pos / vec2( ${w}, ${h} ) * SCALE - 1. ) * vec2( 1, -1);
            gl_Position = vec4( p, 0, 1 );
            texCoord = ( 
                uv - vec2( ${minX}, ${minY} ) 
            ) / vec2( ${w}, ${h} );
        }
    ` );
    gl.compileShader( vs );
    
    if (! gl.getShaderParameter( vs, gl.COMPILE_STATUS ) ) 
        return console.warn( gl.getShaderInfoLog( vs ) );
    
    gl.attachShader( program, vs );
    gl.attachShader( program, fs );
    gl.linkProgram( program );
    
    var tex0 = gl.createTexture();
    gl.bindTexture( gl.TEXTURE_2D, tex0 );

    gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img );
    console.log( img.width, img.height );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );  
    //gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );  
    
    var pos = gl.createBuffer( );
    gl.bindBuffer( gl.ARRAY_BUFFER, pos );
    
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array(
        posArr                  
    ), gl.STATIC_DRAW );
    gl.vertexAttribPointer( 0, 2, gl.FLOAT, 0, 0, 0 );
    gl.enableVertexAttribArray( 0 );
    var uv = gl.createBuffer();
    gl.bindBuffer( gl.ARRAY_BUFFER, uv );
    var a = 8;
    var b = 8+14;
    
    gl.bufferData( gl.ARRAY_BUFFER, new Float32Array(
        uvArr
    ), gl.STATIC_DRAW );
    gl.vertexAttribPointer( 1, 2, gl.FLOAT, 0, 0, 0 );
     gl.enableVertexAttribArray( 1 );
    
    gl.clearColor( .5, .5, .5, 1 );
    gl.clear( gl.COLOR_BUFFER_BIT );
    gl.useProgram( program );
    gl.drawArrays( gl.TRIANGLES, 0, 6*totalLength );
   
    glyphSource.style.display = "none";
    img.style.display = "none";
}
<svg id="glyphSource" xmlns="http://www.w3.org/2000/svg" width="128" height="128" style="background:#CCC;">
    <foreignObject width="100%" height="100%" style="">
        <div xmlns= "http://www.w3.org/1999/xhtml" style="font:15px Helvetica;">
            <span>A</span>
            <span>B</span>
            <span>C</span>
            <span>D</span>
            <span>E</span>
            <span>F</span>
            <span>G</span>
            <span>H</span>
            <span>I</span>
            <span>J</span>
            <span>K</span>
            <span>L</span>
            <span>M</span>
            <span>N</span>
            <span>O</span>
            <span>P</span>
            <span>Q</span>
            <span>R</span>
            <span>S</span>
            <span>T</span>
            <span>U</span>
            <span>V</span>
            <span>W</span>
            <span>X</span>
            <span>Y</span>
            <span>Z</span>
            <span>a</span>
            <span>b</span>
            <span>c</span>
            <span>d</span>
            <span>e</span>
            <span>f</span>
            <span>g</span>
            <span>h</span>
            <span>i</span>
            <span>j</span>
            <span>k</span>
            <span>l</span>
            <span>m</span>
            <span>n</span>
            <span>o</span>
            <span>p</span>
            <span>q</span>
            <span>r</span>
            <span>s</span>
            <span>t</span>
            <span>u</span>
            <span>v</span>
            <span>w</span>
            <span>x</span>
            <span>y</span>
            <span>z</span>
            <span>1</span>
            <span>2</span>
            <span>3</span>
            <span>4</span>
            <span>5</span>
            <span>6</span>
            <span>7</span>
            <span>8</span>
            <span>9</span>
            <span>0</span>
        </div>
    </foreignObject>
</svg>

关于javascript - 如何选择在canvas2d中制作字形纹理的字体大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29137989/

相关文章:

php - 检查表单提交时是否存在用户名

php - Dompdf 将 Canvas 转换为 pdf

javascript - Base64 图像到 WebGL 上下文(用于 readPixels)

css - 字体大小。我能确定什么?

php - 如何在php中改变颜色

javascript - 创建用户后如何调用 updateProfile()

javascript - 对象不支持属性或方法 'fill'

javascript - 替换 Google map API 中的图 block 图片获取文件覆盖

javascript - 了解 HTML 5 Canvas 缩放和平移顺序

c - 我无法更改文本的字体