Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep Text Vertically Centered on Canvas

The issue that I am having is keeping the text entered by a user vertically centered within the canvas element. I have built a test environment to try and solve this issue, which I have provided in this post along with a fiddle. Here is my code:

HTML:

Enter Your Text: <br/>
<textarea id="inputtextuser" onkeyup="inputtextgo()" name="Text">Enter Your</textarea> <br/>
TextBaseline: 
<select id="inputtextbaseline" onchange="inputtextgo()";> 
    <option value="alphabetic">alphabetic</option>
    <option value="top">top</option>
    <option value="bottom">bottom</option>
    <option value="middle">middle</option>
    <option value="hanging">hanging</option>
</select> <br/>
<canvas id="canvas1"  width="300px" height="200px" >Your browser does not support the HTML5 canvas tag.</canvas> <br/>
<div id ="textheightentered"></div>

CSS:

canvas {
    border: solid 1px black;
}

JavaScript/jQuery:

//Declaring the Canvas
var canvas1 = document.getElementById('canvas1');
context1 = canvas1.getContext('2d');

//running the function to update the canvas
 inputtextgo();

function inputtextgo() {
    //Retrieving the text entered by the user
    var inputtext = document.getElementById("inputtextuser").value;
    //Calling the function to calculate the height on the text entered by the user
    var textheight = fontheight(inputtext);
    //retrieving the baseline selected by the user
    var baseline = document.getElementById("inputtextbaseline").value;
    //calculating the new y needed to center the text based on the height of the text
    var y = 100 + textheight/2;

    //Updating the canvas for the new text entered by the user
    context1.clearRect(0, 0, 300, 200);
    context1.font = "36px arial";
    context1.textBaseline = baseline;
    context1.fillText (inputtext, 0, y);   

    //Drawing a line across the middle of the canvas for reference
    context1.strokeStyle="red";
    context1.moveTo(5,100);
    context1.lineTo(290,100);
    context1.stroke();
    $("#textheightentered").text("Text Height: " + textheight);
}

function fontheight(text){

    var canvas=document.createElement("canvas");
    canvas.width = 1000;
    canvas.height = 200;
    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.baseline = "top";
        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= "36px arial";
    var height = measureTextHeight(0, 0, canvas.width, canvas.height,text);
    //return the height of the string
    return height;
} // end $(function(){});

JSFiddle: http://jsfiddle.net/grapien/R6DWN/3/

The issue I am having is that if the user enters a lower case "y" for example the text height changes and the text does not remain in the center of the canvas. This due to the fact that the baseline correction performed in the y variable would need to differ depending on the text entered (combination of lower and uppercase characters) as the text below the baseline is skewing the correction performed in the y variable.

This is where I am having my problems as I would like to keep the text centered at all times within the canvas element. The only thing I can think of is to calculate the non-blank pixels above an below the textbaseline reference point. Then use the difference from the baseline to the ends of the text to offset the text to center it on the canvas. I am not sure how you approach writing this script or if it is even possible. But that is the best approach I have came up with.

If anyone has any thoughts or guidance it would be greatly appreciated as developing an approach to solve this issue has got me stumped.

like image 948
grapien Avatar asked Nov 12 '22 08:11

grapien


1 Answers

I believe I have solved this question using the technique I stated in the question. I created a new function that calculates the amount of the font that extends below the baseline of the text entered. Using a similar technique as to what was used to calculate the height. I use the alphabetic baseline then calculate the pixels that contain content above this baseline. See the function below.

function fontheight2(text){

    var canvas=document.createElement("canvas");
    canvas.width = 1000;
    canvas.height = 200;
    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, 100); // 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 = 100,
            c = 0;

        // 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 99 - first;
        }

        // We screwed something up...  What do you expect from free code?
        return 0;
    }
    ctx.baseline = "Alphabetic";
    ctx.font= "36px arial";
    var height = measureTextHeight(0, 0, canvas.width, canvas.height,text);
    //return the height of the string
    return height;
} // end $(function(){});

From there I simply use the following adjustment to the y to offset the text to center:

var textheight = fontheight(inputtext); //Calculate Text Height
var textheight2 = fontheight2(inputtext); //Calculate portion above the baseline
var difference = textheight - textheight2; // Calculate the portion below the baseline
var y = 100 - difference + textheight/2; // Adjust the y cordinate

This offsets the y to adjust for the portion of the text that is below the text baseline. I have updated the JSFiddle here http://jsfiddle.net/grapien/R6DWN/6/.

like image 192
grapien Avatar answered Nov 15 '22 00:11

grapien