Text position on canvas is different in FireFox and Chrome - javascript

I need to draw a text string at a precise position on HTML5 canvas.
Here's my test code:
<!DOCTYPE html>
<html>
<body>
<canvas id="mainCanvas" width="320" height="240" style = "border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.textBaseline = "top";
ctx.font = '100px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(0, 0, 0, 255)';
ctx.fillText('Test', 0, 0);
}
</script>
</body>
</html>
The margin at the top is different in Chrome and Firefox:
I'm going to draw other elements (e.g. images, shapes) on the canvas, and I need to make sure the text appears at the same position in all browsers. Is it possible?

Cause
As #iftah says: This mis-alignment is caused by a Firefox bug.
Please add your voice to the Firefox's bug page so they fix it soon.
Workaround
(Unfortunately), The workaround is to draw the text on a second canvas and scan that pixel data to find the topmost & leftmost pixel of the text.
Then draw the text on the main canvas but pulled upward and leftward by the calculated pixel offsets.
Here is annotated code and a Demo:
// canvas vars
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// test vars
var text='Test';
var fontsize=100;
var fontface='arial';
drawTextAtXY(0,0,text,fontsize,fontface,'black');
function drawTextAtXY(x,y,text,fontsize,fontface,fill){
// find the leftmost & topmost pixel of the text
var minXY=getTextTop(text,fontsize,fontface);
// set the font styles
ctx.textBaseline='top';
ctx.font=fontsize+'px '+fontface;
ctx.fillStyle=fill;
// draw the text
// Pull the text leftward and topward by minX & minY
ctx.fillText(text,x-minXY.x,y-minXY.y);
}
function getTextTop(text,fontsize,fontface){
// create temp working canvas
var c=document.createElement('canvas');
var w=canvas.width;
var h=fontsize*2;
c.width=w;
c.height=h;
var cctx=c.getContext('2d');
// set font styles
cctx.textBaseline='top';
cctx.font=fontsize+'px '+fontface;
cctx.fillStyle='red';
// draw the text
cctx.fillText(text,0,0);
// get pixel data
var imgdata=cctx.getImageData(0,0,w,h);
var d=imgdata.data;
// scan pixel data for minX,minY
var minX=10000000;
var minY=minX;
for(var y=0;y<h;y++){
for(var x=0;x<w;x++){
var n=(y*w+x)*4
if(d[n+3]>0){
if(y<minY){minY=y;}
if(x<minX){minX=x;}
}
}}
// return the leftmost & topmost pixel of the text
return({x:minX,y:minY});
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Text drawn exactly at specified X,Y</h4>
<canvas id="canvas" width=300 height=200></canvas>

You can fix it easily.
you can use textBaseline = "middle";
and must use transform to 50px of top
means:
<canvas id="mainCanvas" width="320" height="240" style = "border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.textBaseline = "middle";
ctx.font = '100px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.save();
ctx.transform(1, 0, 0, 1, 0, 50);
ctx.fillText('Test', 0, 0);
ctx.restore();
}
</script>
A another bug is in your code: rgba(0, 0, 0, 255)
fourth number in rgba must be between 0-1 for example 0.75.
why I write .save() and .restore() ?
for reset transform and clear after use it.
Of course you can use
ctx.fillText('Test', 0, 50);
Instead
ctx.save();
ctx.transform(1, 0, 0, 1, 0, 50);
ctx.fillText('Test', 0, 0);
ctx.restore();

This appears to be a bug in Firefox dating back to 2012 and still not fixed!
See https://bugzilla.mozilla.org/show_bug.cgi?id=737852
Edit:
Setting the baseLine to "alphabetic" (the default) results in both Chrome and Firefox having the same vertical location for the text.
Edit:
Unfortunately, the vertical location isn't the only difference between Firefox and Chrome. Changing the string to "Testing" shows clearly that not only is the vertical location different, but also the space between the letters is slightly different - resulting in different width of the text.
If you really must have pixel perfect location AND size of the text maybe you should use Bitmap fonts

As stated in other answer it's some king of bug (or maybe just a different implementation). textBaseline bottom and Y position to 100 can fix it.
<canvas id="mainCanvas" width="320" height="240" style = "border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
window.onload = function() {
var canvas = document.getElementById("mainCanvas");
var ctx = canvas.getContext("2d");
ctx.textBaseline = "bottom";
ctx.font = '100px Arial';
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(150, 50, 0, 1)';
ctx.fillText('Test', 0, 100);
}
</script>
In general: Use baseline bottom and increase Y position the font size amount

Related

Removing text in HTML5 Canvas?

I'm working on a HTML5 Canvas project and have some text drawn on the screen. Right now, it appears and just stay there, but what I need is for it to disappear after a few seconds so that every time it's called it's not just new text being drawn on top of the old text.
I tried clearRect() but that completely clears the entire rectangle and removes some of my background too, which I don't want.
Is there a way to do this?
I'm drawing the text with this basic function:
function drawText() {
ctx.font = "30px Arial";
ctx.fillText("Please wait...", 575, 130);
}
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<button onclick="showTheText()">Click to show text</button>
<canvas id="myCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
function showTheText() {
var myText = {
text: 'Hello World',
x: 0,
y: 75,
fill: 1
};
drawText(myText, context);
// wait one second before starting animation
setTimeout(function() {
animate(myText, canvas, context);
}, 1000);
}
function drawText(myText, context) {
context.font="30px Arial";
context.fillStyle="rgb(0, 0, 0, " + myText.fill + ")";
context.fillText(myText.text, myText.x, myText.y);
}
function animate(myText, canvas, context) {
// clear
context.clearRect(0, 0, canvas.width, canvas.height);
// !!!!!!!! redraw your background !!!!!!!!
// then redraw your text with new opacity
myText.fill -= .1;
drawText(myText, context);
// request new frame
if (myText.fill > -.1) {
setTimeout(function() {
animate(myText, canvas, context);
}, 2000 / 60);
}
}
</script>
</body>
</html>
Hope this can help you. What it does it immediately draws the text (and the rest of your background, you didn't provide it), then sets up a recursive timeout that will clear the rect, redraw your background, decrement the amount of opacity to apply to the text's fill, and redraw the text as well. You can slow it down by changing the time of the last setTimeout.
I adapted it from this example: https://www.html5canvastutorials.com/advanced/html5-canvas-animation-stage/
you can create a simple function to delete ,
you will need some values. For example, font height. width of character ... etc ,
for example in this case , you can use the following function , With few modifications :
function removeText(x,y,txt_length,font_height,char_width,ctx) {
ctx.clearRect(x, y-font_height ,char_width*txt_length,font_height);
}
and this function for write your string :
function drawText(x,y,text,ctx) {
ctx.font = "30px Arial";
ctx.fillText(text,x,y);
}
The same parameters x and y are passed to the functions and txt_length = text.length;
Once you've drawn text to canvas, it's no longer text - it's just a bunch of pixels! Since those pixels overwrite what is already there, there's no way to delete just the text.
Generally what you can do is re-draw the entire scene without the text, if you want to get rid of it.
You could also try re-drawing the text in the same position using the background color, which would remove the text, but this only works if the text is on a solid background.
To achieve expected use below option of fillStyle in clearText function
Create another function clearText with fillStyle white color with same text and x , y coordinates
Call clearText function from the drawText
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
function drawText() {
clearText()
ctx.font = "30px Arial";
ctx.fillStyle = "black";
ctx.fillText("Please wait...", 575, 130);
}
function clearText() {
ctx.font = "30px Arial";
ctx.fillStyle = "white";
ctx.fillText("Please wait...", 575, 130);
}
<button onclick="drawText()">draw text</button><br>
<canvas id="myCanvas" width="800" height="800"
style="border:1px solid #d3d3d3;">
Your browser does not support the canvas element.
</canvas>
code sample - https://codepen.io/nagasai/pen/oqOXxz

Lines outside of canvas

I define a canvas as <canvas id="maincanvas" class="canvas"></canvas> with the style
.canvas {
width: 1000px;
height: 750px;
border: 1px dotted;
}
Then I try to draw a line from the upper-left to the lower-right with:
function GenerateSym() {
var c = document.getElementById("maincanvas");
var ctx = c.getContext("2d");
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(5, 5);
ctx.lineTo(995, 745);
ctx.stroke();
Unfortunately, the line seems to start just a few px too low. While it leaves the canvas entirely on the bottom-center instead of ending just before the bottom-right corner. Additionally, the line seems to be at least 3px wide with terrible anti-aliasing.
I'm running Mint 18 with Firefox 58. Thanks!
You cannot use CSS style for resizing of the canvas element, or you will run into this issue. The canvas DOM element has properties width and height which equel to attributes "width=.." and "height=.." . As said here: Canvas width and height in HTML5 .
So the correct thing to do would be:
.canvas{ style: 1px dotted;}
and
<script>
var c = document.getElementById("maincanvas");
var ctx = c.getContext("2d");
c.width= 1000;
c.height= 750;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(5, 5);
ctx.lineTo(995, 745);
ctx.stroke();</script>

Keep text, dynamically updated, centered within a canvas

I have the following canvas:
<canvas id="myCanvas" width="400" height="400" style="border:0px;">
Your browser does not support the HTML5 canvas tag.</canvas>
javascript:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(200, 200, 100, 0, 2 * Math.PI);
ctx.stroke();
I want to write a score within the center of the canvas:
var score = 0;score++;
ctx.fillText(score,200,200);
However this score will be updated and the more digit this value will be the less centered it will be.
I attempted to use the solution offered here, but the "text" here, is merely a javascript variable named score and thus not considered a text.
Here is a fiddle where I played around to try to solve my problem:
https://jsfiddle.net/27xfvLhh/
I see two potential way to obtain the result wanted:
Is there a way to fill the text directly centered within my canvas?
Is there a way to calculate what will be the size of the text?
Thanks
Is there a way to fill the text directly centered within my canvas?
You can use the properties textAlign for horizontal alignment and textBaseline for vertical alignment. Then calculate center point by for example using half the size of the canvas:
Is there a way to calculate what will be the size of the text?
The ctx.measureText("Text").width will give you the width of the text in pixels using the current set font.
var ctx = c.getContext("2d");
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Some Centered Text", ctx.canvas.width>>1, ctx.canvas.height>>1);
// measure text
var width = ctx.measureText("Some Centered Text").width;
ctx.font = "20px sans-serif";
ctx.fillText(width + "px", ctx.canvas.width>>1, 90 + ctx.canvas.height>>1);
#c {border:1px solid}
<canvas id=c width=600 height=300></canvas>

Why does the Canvas.FillText method seem to use the wrong coordinates?

I am rendering text onto a HTML-Canvas like so:
https://www.w3schools.com/code/tryit.asp?filename=FMSIQUFFO5PL
As you can see, the text is supposed to be rendered at 32, 32. But for some reasons, the string appears too high.
It this because the Canvas' coordinates start at the top left corner of the Canvas-Element and the String's coordinates at the lower left corner of the String?
How do I work around this issue so that I can render text at exactly the position I am expecting it to be?
By default the vertical text-alignment (baseline) is set to "alphabetic" which uses the general bottom of the text for the y coordinate.
You can change this behavior by setting textBaseline to "top".
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "32px Arial";
ctx.textAlign = "left";
ctx.textBaseline = "top"; // change baseline property
ctx.fillText("Hello World", 32, 32);
<canvas id="myCanvas" style="border:1px solid #d3d3d3;"></canvas>

Rendering gray text on canvas looks awful

Why is it that rendering gray text onto a canvas looks absolutely terrible? In my example, the first piece of text looks great, the second piece looks awful, and the third piece looks passable.
Here is a screenshot of what I see:
var g = document.getElementById('canvas').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#bbbbbb";
g.fillText("This is some example text. (CANVAS, gray)", 0, 30);
g.fillStyle = "black";
g.fillText("This is some example text. (CANVAS, black)", 0, 60);
div {
font-family: "arial";
font-size: 12px;
color: #bbbbbb;
}
<div>This is some example text. (DOM, gray)</div>
<canvas id="canvas" width="377" height="174"></canvas>
Before marking as a duplicate, please note that I've searched on StackOverflow and Google and the answers there are insufficient for the following reasons:
- I'm not using a retina monitor -- my pixel ratio is 1, so this is not
about using a larger canvas size.
- I've tried rendering the font on the "half pixel", but that doesn't change anything. Rendering on the half pixel helps with things like lines, but not text.
Awful text rendering fixes
Oh you have found the magic colour combination. Put it down on the long list of canvas problems.
Note I only found this problem on Chrome, Firefox does not have an issues. I can not use Edge as for some reason it over heats my laptop resulting in shutdown. So don't know about Edge.
The reason is because the canvas is transparent and the standard says that fully transparent pixels must be alpha multiplied making them black (This is to stop dumb image encoders from encoding transparent colour information that can not be seen). The renderer thinks that what is under the canvas is Black, because that is the colour of transparent pixels.
How to fix.
There are three solutions, nope scratch that the second solution does not work.
There are two solutions I can think of that involve less than 100 lines of code.
Draw a the background first then draw the text on top of that
The second solution I thought would be just draw a outline in a very low alpha value. But that still picks up the bad pre-multiplied transparent black
And the third is to fill (fillRect) the canvas with the text colour and then set the comp mode to "destination-in" and the draw the text. This allows you to still have the transparent pixels under the text
Snippet showing code.
var g = document.getElementById('canvas').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#bbbbbb";
g.fillText("Very bad text rendering", 0, 12);
var g = document.getElementById('canvas1').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#f3f5f6"
g.fillRect(0,0,g.canvas.width,g.canvas.height);
g.fillStyle = "#bbbbbb";
g.fillText("Fixed with solid background", 0, 12);
var g = document.getElementById('canvas2').getContext('2d');
g.font = "12px arial";
g.strokeStyle = "rgba("+0xf3+","+0xf5+","+0xf6+",0.05)";
g.lineWidth = 2;
g.lineJoin = "round";
g.strokeText("Tried with pixel outline", 0, 12);
g.fillStyle = "#bbbbbb";
g.fillText("Tried with pixel outline", 0, 12);
var g = document.getElementById('canvas3').getContext('2d');
g.font = "12px arial";
g.fillStyle = "#bbbbbb";
g.fillRect(0,0,g.canvas.width,g.canvas.height);
g.globalCompositeOperation = "destination-in";
g.fillText("Fixed with comp op destination-in on text colour", 0, 12);
g.globalCompositeOperation = "source-over";
div {
font-family: "arial";
font-size: 12px;
color: #bbbbbb;
background : #f3f5f6;
}
<div>This is some example text. (DOM, gray)<br>
<canvas id="canvas" width="377" height="18"></canvas>
<canvas id="canvas1" width="377" height="18"></canvas>
<canvas id="canvas2" width="377" height="18"></canvas>
<canvas id="canvas3" width="377" height="18"></canvas>
</div>

Categories