grapien
grapien

Reputation: 323

Vertical-Align Text on HTML 5 Canvas

What I am trying to do is display multiple lines of text in the middle of a canvas element. The text is dynamic as it is inputted by the user using a text box, the canvas is then updated with the text the user has typed (code shown below). I would like the text to appear on the canvas in a similar fashion to the way that the text would appear using vertical-align: middle property in CSS. My question is what would be the easiest way to approach this problem.

A big issue that I am having is that the user can change the font. As different fonts are different heights (even if they are defined at a px height they are not consistently that height). The best idea I have had so far is to calculate the height of the text on the canvas. I read this article on the site How can you find the height of text on an HTML canvas?, see the second answer by Daniel. This should calculate the actual height of the text which can then be used to calculate the correct starting position of the text to center it on the canvas. Based on my code below I believe I would have to essentially run a similar code to predetermine the correct starting position of the font.

This is my approach to properly wrap and display the text on the canvas:

    function wrapText(context, text, x, y, maxWidth, lineHeight) {
    //manage carriage return
    text = text.replace(/(\r\n|\n\r|\r|\n)/g, "\n");
    //manage tabulation
    text = text.replace(/(\t)/g, "    "); // I use 4 spaces for tabulation, but you can use anything you want
    //array of lines
    var sections = text.split("\n"); 

     for (s = 0, len = sections.length; s < len; s++) {
          var words = sections[s].split(' ');
          var line = '';

          for (var n = 0; n < words.length; n++) {
              var testLine = line + words[n] + ' ';
              var metrics = context.measureText(testLine);
              var testWidth = metrics.width;
              if (testWidth > maxWidth) {
                  context.fillText(line, x, y);
                  line = words[n] + ' ';
                  y += lineHeight;
              } else {
                  line = testLine;
              }
          }
          context.fillText(line, x, y);

         //new line for new section of the text
         y += lineHeight;
      }
}

      var canvas = document.getElementById('myCanvas');
      var context = canvas.getContext('2d');
      var maxWidth = 350;
      var lineHeight = 25;
      var x = (canvas.width - maxWidth) / 2;
      var y = 60;
      var text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. \nLorem Ipsum has been the industry's standard dummy text ever since the 1500s.";

      context.font = '14pt Verdana';
      context.fillStyle = '#000';

      wrapText(context, text, x, y, maxWidth, lineHeight); 

I am wondering if there is another approach that I have not considered that would simplify the problem or whether this approach is the best one to take? Is is a simpler way on a canvas element to vertical-align text similar to CSS?

Upvotes: 3

Views: 10703

Answers (3)

shangwenwang
shangwenwang

Reputation: 84

ctx.textAlign = "center";
ctx.textBaseline = "middle"; 
const fix = ctx.measureText("H").actualBoundingBoxDescent / 2; // Notice Here
ctx.fillText("H", convasWidth / 2, canvaseHeight / 2 + fix);

See codepen demo

Upvotes: 3

mohammad feiz
mohammad feiz

Reputation: 317

for setting vertical align text in canvas use textBaseline. For example:

ctx.beginPath();
ctx.font = "10px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle"; // set text in center of place vertically
ctx.fillText("sample text", 100, 100);
ctx.closePath();

Upvotes: 9

markE
markE

Reputation: 105035

Cool!

I looked at your reference: How can you find the height of text on an HTML canvas?

I set up a time test for Prestaul's answer that actually uses canvas to pixel-check for lower and upper bounds of text. In my test, I used all uppercase and lowercase letters (a-z and A-Z) instead of his little/big "gM". I also used a canvas created in JS that I didn't add to the DOM.

Results: I was able to repeatedly run the test 900 +/- times per second.

I didn't run Daniel's test that manipulates a DOM element for measuring, but I assume that would be slower than Prestaul's 1+ millisecond(!) result.

My conclusion is that I would use Prestaul's method to test for max-height whenever the user changes font.

On a personal note, hacking the DOM may work, but to me it feels like using a black box that may someday contain a bomb after a browser update.

Thanks for exercising my curiosity--I enjoyed it!

[Edited to include code for stress test]

Here is code for the stress test and a Fiddle: http://jsfiddle.net/m1erickson/ttsjq/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; padding:50px; }
</style>

<script>
$(function(){

    var canvas=document.createElement("canvas");
    canvas.width=1000;
    canvas.height=50;
    var ctx=canvas.getContext("2d");

    function measureTextHeight(left, top, width, height,text) {

        // Draw the text in the specified area
        ctx.save();
        ctx.clearRect(0,0,canvas.width,canvas.height);
        ctx.fillText(text, 0, 35); // This seems like tall text...  Doesn't it?
        ctx.restore();

        // Get the pixel data from the canvas
        var data = ctx.getImageData(left, top, width, height).data,
            first = false, 
            last = false,
            r = height,
            c = 0;

        // Find the last line with a non-white pixel
        while(!last && r) {
            r--;
            for(c = 0; c < width; c++) {
                if(data[r * width * 4 + c * 4 + 3]) {
                    last = r;
                    break;
                }
            }
        }

        // Find the first line with a non-white pixel
        while(r) {
            r--;
            for(c = 0; c < width; c++) {
                if(data[r * width * 4 + c * 4 + 3]) {
                    first = r;
                    break;
                }
            }

            // If we've got it then return the height
            if(first != r) return last - first;
        }

        // We screwed something up...  What do you expect from free code?
        return 0;
    }

    ctx.font='32px Arial';
    var start = new Date().getTime();
    var text="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for(var x=0;x<1000;x++){
      var height = measureTextHeight(0, 0, canvas.width, canvas.height,text);
    }
    console.log(measureTextHeight(0, 0, canvas.width, canvas.height,text));
    console.log((new Date().getTime()-start));



}); // end $(function(){});
</script>

</head>

<body>
    <div>Stress testing Prestaul's measureTextHeight function...</div>
</body>
</html>

Upvotes: 3

Related Questions