HTML5 画布:绘制的换行旋转文本的自动字体大小

html5 canvas: auto font size for drawn wrapped rotated text

本文关键字:字体 文本 旋转 画布 绘制 换行 HTML5      更新时间:2023-09-26

假设在旋转的边框内绘制文本(不与法轴 x-y 对齐),并且该文本也可以旋转,给定边界框的最大宽度,如何选择用于在HTML5 Canvas和JavaScript中在该边界框内绘制包装文本的最佳字体大小

我知道这种方法:measureText() 可以测量给定字体大小的尺寸,但我需要相反:使用已知宽度来获取问题的字体大小。

谢谢

您不必找到字体磅值来使其适合。字体将根据当前转换比例平滑地向上和向下缩放。

您要做的就是measureText找到它的textWidth,从 context.font 属性中获取pointSize,然后如果您有需要适合的框的widthheight,然后找到width / textWidthheight / pointSize的最小值,并且您拥有渲染字体所需的比例。

作为函数

var scale2FitCurrentFont = function(ctx, text, width, height){
    var points, fontWidth;
    points = Number(ctx.font.split("px")[0]); // get current point size
    points += points * 0.2; // As point size does not include hanging tails and
                            // other top and bottom extras add 20% to the height
                            // to accommodate the extra bits  
    var fontWidth = ctx.measureText(text).width;
    // get the max scale that will allow the text to fi the current font
    return Math.min(width / fontWidth, height / points);
}

参数是

  • CTX 是要绘制到的当前上下文
  • 为要绘制的文本发送文本
  • 宽度以适合文本
  • 高度以适合文本

返回使文本适合宽度和高度的比例。

该演示集成了所有内容,并绘制了随机框并填充了您问题的随机文本。它将字体选择和磅值与字体缩放分开,因此您可以看到它适用于任何字体和任何磅值。

var demo = function(){
    
    /** fullScreenCanvas.js begin **/
    var canvas = (function(){
        var canvas = document.getElementById("canv");
        if(canvas !== null){
            document.body.removeChild(canvas);
        }
        // creates a blank image with 2d context
        canvas = document.createElement("canvas"); 
        canvas.id = "canv";    
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight; 
        canvas.style.position = "absolute";
        canvas.style.top = "0px";
        canvas.style.left = "0px";
        canvas.style.zIndex = 1000;
        canvas.ctx = canvas.getContext("2d"); 
        document.body.appendChild(canvas);
        return canvas;
    })();
    var ctx = canvas.ctx;
    
    /** fullScreenCanvas.js end **/
    /** FrameUpdate.js begin **/
    var w = canvas.width;
    var h = canvas.height;
    var cw = w / 2;
    var ch = h / 2;
    
    var PI2 = Math.PI * 2; // 360 to save typing 
    var PIh = Math.PI / 2; // 90 
    
    
    
    // draws a rounded rectangle path
    function roundedRect(ctx,x, y, w, h, r){
        ctx.beginPath(); 
        ctx.arc(x + r, y + r, r, PIh * 2, PIh * 3);  
        ctx.arc(x + w - r, y + r, r, PIh * 3, PI2);
        ctx.arc(x + w - r, y + h - r, r, 0, PIh);  
        ctx.arc(x + r, y + h - r, r, PIh, PIh * 2);  
        ctx.closePath(); 
    }
    
    // random words
    var question = "Suppose that there is a text to be drawn inside a rotated bounding rectangle (not aligned to normal axes x-y), and that text can be also rotated, given the max width of the bounding box, how to select the best font size to use to draw a wrapped text inside that bounding box in html5 canvas and javascript? I know that method: measureText() can measure dimensions of give font size, but I need the inverse of that: using a known width to get the problem font size. thanks.";
    question = question.split(" ");
    var getRandomWords= function(){
        var wordCount, firstWord, s, i, text;
        wordCount = Math.floor(rand(4)+1);
        firstWord = Math.floor(rand(question.length - wordCount));
        text = "";
        s = "";
        for(i = 0; i < wordCount; i++){
            text += s + question[i + firstWord];
            s = " ";  
        }
        return text;
    }
    
    // fonts to use?? Not sure if these are all safe for all OS's
    var fonts = "Arial,Arial Black,Verdanna,Comic Sans MS,Courier New,Lucida Console,Times New Roman".split(",");
    // creates a random font with random points size in pixels
    var setRandomFont = function(ctx){
        var size, font;
        size = Math.floor(rand(10, 40));
        font = fonts[Math.floor(rand(fonts.length))];
        ctx.font = size + "px " + font;
    }
    var scale2FitCurrentFont = function(ctx, text, width, height){
        var points, fontWidth;
        var points = Number(ctx.font.split("px")[0]); // get current point size
        points += points * 0.2;
        var fontWidth = ctx.measureText(text).width;
        // get the max scale that will allow the text to fi the current font
        return Math.min(width / fontWidth, height / points);
    }
    var rand = function(min, max){
        if(max === undefined){
            max = min;
            min = 0;
        }
        return Math.random() * (max - min)+min;
    }
    var randomBox = function(ctx){
        "use strict";
        var width, height, rot, dist, x, y, xx, yy,cx, cy, text, fontScale;
        // get random box
        width = rand(40, 400);
        height = rand(10, width * 0.4);
        rot = rand(-PIh,PIh);
        dist = Math.sqrt(width * width + height * height)
        x = rand(0, ctx.canvas.width - dist);
        y = rand(0, ctx.canvas.height - dist);
        xx = Math.cos(rot);
        yy = Math.sin(rot);
        ctx.fillStyle = "white";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 2;
        // rotate the box
        ctx.setTransform(xx, yy, -yy, xx, x, y);
        // draw the box
        roundedRect(ctx, 0, 0, width, height, Math.min(width / 3, height / 3));
        ctx.fill();
        ctx.stroke();
        
        // get some random text
        text = getRandomWords();
        // get the scale that will fit the font
        fontScale = scale2FitCurrentFont(ctx, text, width - textMarginLeftRigth * 2, height - textMarginTopBottom * 2);
        // get center of rotated box
        cx = x + width / 2 * xx + height / 2 * -yy;
        cy = y + width / 2 * yy + height / 2 * xx;
        // scale the transform
        xx *= fontScale;
        yy *= fontScale;
        // set the font transformation to fit the box
        ctx.setTransform(xx, yy, -yy, xx, cx, cy);
        // set up the font render
        ctx.fillStyle = "Black";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle"
        // draw the text to fit the box
        ctx.fillText(text, 0, 0);
    }
    var textMarginLeftRigth = 8; // margin for fitted text in pixels
    var textMarginTopBottom = 4; // margin for fitted text in pixels
    var drawBoxEveryFrame = 60; // frames between drawing new box
    var countDown = 1;
    // update function will try 60fps but setting will slow this down.    
    function update(){
        // restore transform
        ctx.setTransform(1, 0, 0, 1, 0, 0);
      
        // fade clears the screen 
        ctx.fillStyle = "white"
        ctx.globalAlpha = 1/ (drawBoxEveryFrame * 1.5);
        ctx.fillRect(0, 0, w, h);
      
        // reset the alpha
        ctx.globalAlpha = 1;
        
        // count frames
        countDown -= 1;
        if(countDown <= 0){ // if frame count 0 the draw another text box
            countDown = drawBoxEveryFrame;
            setRandomFont(ctx);
            randomBox(ctx);
        }
        
        if(!STOP){ // do until told to stop.
            requestAnimationFrame(update);
        }else{
            STOP = false;
            
        }
    }
    update();
}
// demo code to restart on resize
var STOP = false;  // flag to tell demo app to stop 
function resizeEvent(){
    var waitForStopped = function(){
        if(!STOP){  // wait for stop to return to false
            demo();
            return;
        }
        setTimeout(waitForStopped,200);
    }
    STOP = true;
    setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
demo();
/** FrameUpdate.js end **/