Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine width of string in HTML5 canvas

I'm drawing text in an HTML5 canvas on top of an image (a meme generator). I want to calculate a font size so that the string will span the entire width of the canvas.

So basically, is there a way in JavaScript to determine what font size I should use for some string of a certain font for a given width?

like image 905
edc1591 Avatar asked Apr 11 '12 02:04

edc1591


People also ask

How do you measure the width of a canvas?

You can get the width and height of a canvas element simply by accessing those properties of the element. For example: var canvas = document. getElementById('mycanvas'); var width = canvas.

How do you change the height and width of a canvas?

The width attribute specifies the width of the <canvas> element, in pixels. Tip: Use the height attribute to specify the height of the <canvas> element, in pixels. Tip: Each time the height or width of a canvas is re-set, the canvas content will be cleared (see example at bottom of page).


2 Answers

The fillText() method has an optional fourth argument, which is the max width to render the string.

MDN's documentation says...

maxWidth

Optional; the maximum width to draw. If specified, and the string is computed to be wider than this width, the font is adjusted to use a more horizontally condensed font (if one is available or if a reasonably readable one can be synthesized by scaling the current font horizontally) or a smaller font.

However, at the time of writing, this argument isn't supported well cross browser, which leads to the second solution using measureText() to determine the dimensions of a string without rendering it.

var width = ctx.measureText(text).width;

Here is how I may do it...

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d');

// Font sizes must be in descending order. You may need to use `sort()`
// if you can't guarantee this, e.g. user input.
var fontSizes = [72, 36, 28, 14, 12, 10, 5, 2],
    text = 'Measure me!';

// Default styles.
ctx.textBaseline = 'top';
ctx.fillStyle = 'blue';

var textDimensions,
    i = 0;

do {
   ctx.font = fontSizes[i++] + 'px Arial';
   textDimensions = ctx.measureText(text);        
} while (textDimensions.width >= canvas.width);

ctx.fillText(text, (canvas.width - textDimensions.width) / 2, 10);​

jsFiddle.

I have a list of font sizes in descending order and I iterate through the list, determining if the rendered text will fit within the canvas dimensions.

If it will, I render the text center aligned. If you must have padding on the left and right of the text (which will look nicer), add the padding value to the textDimensions.width when calculating if the text will fit.

If you have a long list of font sizes to try, you'd be better off using a binary search algorithm. This will increase the complexity of your code, however.

For example, if you have 200 font sizes, the linear O(n) iteration through the array elements could be quite slow.

The binary chop should be O(log n).

Here is the guts of the function.

var textWidth = (function me(fontSizes, min, max) {

    var index = Math.floor((min + max) / 2);

    ctx.font = fontSizes[index] + 'px Arial';

    var textWidth = ctx.measureText(text).width;

    if (min > max) {
        return textWidth;
    }

    if (textWidth > canvas.width) {
        return me(fontSizes, min, index - 1);
    } else {
        return me(fontSizes, index + 1, max);
    }

})(fontSizes, 0, fontSizes.length - 1);

jsFiddle.

like image 157
alex Avatar answered Sep 30 '22 03:09

alex


  1. Write All Things You Want For Text. ( Ex. ctx.font )

  2. Before Writing Actual Text Use ctx.measureText("myText").width To get Width Of Text, Because We Have Called It After Making Changes On ctx, It Will Be Different Each Time When We Change ctx Properties (ex. font)

  3. Now We Will Scale It From Middle.

     ctx.translate(midPoint_X_Of_Text, midPoint_Y_Of_Text);
     ctx.scale(desiredWidth/measuredWidth, desiredWidth/measuredWidth);
     ctx.translate(-midPoint_X_Of_Text, -midPoint_Y_Of_Text);
    
  4. Write Text ctx.fillText() or ctx.strokeText()

  5. Reverse All Changes By ctx.save() and ctx.restore() Or Manually like setTransform()

var can = document.getElementById('myCan');
var ctx = can.getContext("2d");

var desiredWidth = can.width;
var myText = "Hello How Are You?"

function draw(){
  ctx.font = can.height/2 + "px verdana";
  ctx.fillStyle = "red";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  let measuredWidth = ctx.measureText(myText).width;
  let ratio = desiredWidth/measuredWidth;
  ctx.translate(can.width/2,can.height/2);
  ctx.scale(ratio,ratio);
  ctx.translate(-can.width/2,-can.height/2);
  ctx.fillText(myText,can.width/2,can.height/2);
  ctx.setTransform();
}

draw();

let myInput = document.getElementById('myInput');
myInput.addEventListener('input',(e)=>{
    ctx.clearRect(0,0,can.width,can.height);
        myText = myInput.value;
        draw();
})
<html>
<body>
  <center>
    <input id="myInput" value="Hello How Are You?"/><br>
    <canvas id="myCan" height="300" width=300 style="border:black solid 2px;">
    </canvas>
  </center>
</body>
</html>
like image 39
rutvik rana Avatar answered Sep 30 '22 05:09

rutvik rana