Reputation: 323
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
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
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
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