I have a canvas which is 500*250 (w*h) and applied this code to it;
var cvs = document.getElementById('Dugong');
var ctx = cvs.getContext('2d');
var cvsHeight = cvs.height; // 250
var cvsWidth = cvs.width; // 500
var loadUpFlappy = "Flappy";
var loadUpDugong = "Dugong";
ctx.fillStyle = "#ECA20F";
ctx.fillRect(5, (cvsHeight / 2) + 1, ctx.measureText(loadUpFlappy).width + 10, 2); // across, down, width, height
ctx.fillRect(cvsWidth - (ctx.measureText(loadUpDugong).width + 5), cvsHeight / 2 + 1, ctx.measureText(loadUpDugong).width + 10, 2);
ctx.fillStyle = "#DADFE1";
ctx.font = "40px Century Schoolbook";
ctx.fillText(loadUpFlappy, 10, cvsHeight / 2);
ctx.fillText(loadUpDugong, cvsWidth - (ctx.measureText(loadUpDugong).width + 10), cvsHeight / 2);
To my knowledge, this code should ouput;
One #ECA20F line which has a height of 2, is 5 away from the left canvas border, is 1 below the canvas middle line and has the width of Flappy plus 10.
Another #ECA20F line which has a height of 2, is 5 away from the right canvas border, is 1 below the canvas middle line and has the width of Dugong plus 10.
AND
The word Flappy in #DADFE1 which is 10 away from the left border of the canvas and is in the middle the canvas.
The word Dugong also in #DADFE1 which is 10 away from the right border of the canvas and is also in the middle of the canvas.
My problem is that the lines are not matching up with the text (they should span the width of the text plus 5px on either side). Instead they do this;
View Image
Sorry if there are any glaringly obvious answers to this issue as I am a 'noob' when it comes to javascript and canvases.
Edit: I thought that it'd not be possible to take the text length. So I'm a bit wrong.
There's no easy way to place the lines because you don't know the font size in the text. What you can do is to align the text in center (ctx.textAlign = 'center') and place it on a certain point which will be its center. Then you set the line width according to the text length and size. Put the line always below to this point and set the same X as the point, subtracting it with the width you got for the line divided by 2. But the line width wouldn't be exact in this case, but it helps smaller words.
Related
Hey guys i was just going through this three.js example HERE and after playing around with the example for sometime , i saw the below function:
// NOTE :: the below function, helps in positioning the 5 static red dots
function updateHUDSprites () {
var width = window.innerWidth / 2;
var height = window.innerHeight / 2;
var material = spriteTL.material;
var imageWidth = material.map.image.width / 2;
var imageHeight = material.map.image.height / 2;
// NOTE :: the below lines, helps in positioning the 5 static red dots
spriteTL.position.set( - width + imageWidth, height - imageHeight, 1 ); // top left
spriteTR.position.set( width - imageWidth, height - imageHeight, 1 ); // top right
spriteBL.position.set( - width + imageWidth, - height + imageHeight, 1 ); // bottom left
spriteBR.position.set( width - imageWidth, - height + imageHeight, 1 ); // bottom right
spriteC.position.set( 0, 0, 1 ); // center
}
This function is basically helping position all the 5 pngs , i have played around with the above code quite a bit , but i still don't understand how the centering works , for example the below line:
spriteC.position.set( 0, 0, 1 ); // center
By playing around with the example a bit , i realized that the above line of code was actually positioning the center image , i just don't understand the values being passed to the position.set() function. if somebody could just explain how does spriteC.position.set( 0, 0, 1 ); actually center the image , it would be great.
it depends on how camera is positioned
sprites are rendered using the orthographic camera renderer.render( sceneOrtho, cameraOrtho ); and it has position (0,0,10), looking at the origin (0,0,0)
anything at the origin or along a line parallel with z axis (our sprite with (0,0,1)) will appear to be in the center, negative x coordinate will move the object to the left, positive to the right and same thing with y
I'm drawing a line chart with canvas. The chart is responsive, but the line has to have a fixed width.
I made it responsive with css
#myCanvas{
width: 80%;
}
,so the stroke is scaled.
The only solution I have found is to get the value of the lineWidth with the proportion between the width attribute of the canvas and its real width.
To apply it, I clear and draw the canvas on resize.
<canvas id="myCanvas" width="510" height="210"></canvas>
<script type="text/javascript">
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
function draw(){
var canvasattrwidth = $('#myCanvas').attr('width');
var canvasrealwidth = $('#myCanvas').width();
// n sets the line width
var n = 4;
var widthStroke = n * (canvasattrwidth / canvasrealwidth) ;
ctx.lineWidth = widthStroke;
ctx.beginPath();
ctx.moveTo( 0 , 10 );
ctx.lineTo( 200 , 100 );
ctx.stroke();
}
$(window).on('resize', function(){
ctx.clearRect(0, 0, c.width, c.height);
draw();
});
draw();
</script>
This is my first canvas and I think there is an easier way to made the lineWidth fixed (not to clear and draw everytime on resize), but I cannot find it.
There is a question with the similar problem
html5 canvas prevent linewidth scaling
but with the method scale(), so I cannot use that solution.
There is no way to get a real world dimension of details for the canvas such as millimeters or inches so you will have to do it in pixels.
As the canvas resolution decreases the pixel width of a line needs to decrease as well. The limiting property of line width is a pixel. Rendering a line narrower than a pixel will only approximate the appearance of a narrower line by reducing the opacity (this is done automatically)
You need to define the line width in terms of the lowest resolution you will expect, within reason of course and adjust that width as the canvas resolution changes in relation to this selected ideal resolution.
If you are scaling the chart by different amounts in the x and y directions you will have to use the ctx.scale or ctx.setTransform methods. As you say you do not want to do this I will assume that your scaling is always with a square aspect.
So we can pick the lowest acceptable resolution. Say 512 pixels for either width or height of the canvas and select the lineWidth in pixels for that resolution.
Thus we can create two constants
const NATIVE_RES = 512; // the minimum resolution we reasonably expect
const LINE_WIDTH = 1; // pixel width of the line at that resolution
// Note I Capitalize constants, This is non standard in Javascript
Then to calculate the actual line width is simply the actual canvas.width divided by the NATIVE_RES then multiply that result by the LINE_WIDTH.
var actualLineWidth = LINE_WIDTH * (canvas.width / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You may want to limit that size to the smallest canvas dimension. You can do that with Math.min or you can limit it in the largest dimension with Math.max
For min dimention.
var actualLineWidth = LINE_WIDTH * (Math.min(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
For max dimension
var actualLineWidth = LINE_WIDTH * (Math.max(canvas.width, canvas.height) / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
You could also consider the diagonal as the adjusting factor that would incorporate the best of both x and y dimensions.
// get the diagonal resolution
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = LINE_WIDTH * (diagonalRes / NATIVE_RES);
ctx.lineWidth = actualLineWidth;
And finally you may wish to limit the lower range of the line to stop strange artifacts when the line gets smaller than 1 pixel.
Set lower limit using the diagonal
var diagonalRes = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height)
var actualLineWidth = Math.max(1, LINE_WIDTH * (diagonalRes / NATIVE_RES));
ctx.lineWidth = actualLineWidth;
This will create a responsive line width that will not go under 1 pixel if the canvas diagonal resolution goes under 512.
The method you use is up to you. Try them out a see what you like best. The NATIVE_RES I picked "512" is also arbitrary and can be what ever you wish. You will just have to experiment with the values and method to see which you like best.
If your scaling aspect is changing then there is a completely different technique to solve that problem which I will leave for another question.
Hope this has helped.
I use fillRect(x, y, 9, 9) with integer values and see smoothed rectangles (see below). I tried the following with no luck:
this.ctx.webkitImageSmoothingEnabled = false;
this.ctx.imageSmoothingEnabled = false;
var iStrokeWidth = 1;
var iTranslate = (iStrokeWidth % 2) / 2;
this.ctx.translate(iTranslate, iTranslate);
i would like to see a line of 1 pixel between blue blocks, but i see smoothed gap:
Just add 0.5 pixel to the positions (or pre-translate half pixel):
fillRect(x + 0.5, y + 0.5, 9, 9);
This works because for some reason the canvas coordinate system define start of a pixel from the pixel's center. When drawn canvas actually has to sub-pixel the single point producing 4 anti-aliased pixels. By adding 0.5 you move it from center to the pixel's corner so it matches with the screen's coordinate system and no sub-pixeling has to take place.
These only affects images, not shapes btw.:
this.ctx.webkitImageSmoothingEnabled = false;
this.ctx.imageSmoothingEnabled = false;
A few years ago I made a Javascript script for APNGedit to draw the Laughing Man logo. It used the now defunct mozTextAlongPath
I recently rediscovered this script and redid it using translations, rotations and fillText(). However, this doesn't respect character width nor is it kerned (it looks terrible).
Original circa 2009 (not perfect, but okay):
Current version:
How can I draw text in an arc on an HTML5 canvas and make it look good?
Solution Code based on Kolink's answer:
ctx.fillStyle = primaryColor;
ctx.font = fontSize + 'px ' + fontFamily;
var textWidth = ctx.measureText(text).width,
charRotation = 0,
character, charWidth, nextChar, nextWidth, bothWidth, kern, extraRotation, charSegment;
for (var i=0, l=text.length; i<l; i++) {
character = nextChar || text[i];
charWidth = nextWidth || ctx.measureText(character).width;
// Rotate so the letter base makes a circle segment instead of a tangent
extraRotation = (Math.PI/2) - Math.acos((charWidth/2) / radius);
ctx.save();
ctx.translate(radius, h/2);
ctx.rotate(charRotation);
ctx.translate(0, -textRadius);
ctx.rotate(extraRotation);
ctx.fillText(character,0,0);
ctx.restore();
nextChar = text[i+1] || '';
nextWidth = ctx.measureText(nextChar).width;
bothWidth = ctx.measureText(character+nextChar).width;
kern = bothWidth - charWidth - nextWidth;
charSegment = (charWidth+kern) / textWidth; // percent of total text size this takes up
charRotation += charSegment * (Math.PI*2);
}
Obviously, it is no difficulty to place letters on the arc itself (just align center bottom to the circle). However, as you noted, the problem is kerning.
Luckily, we have measureText(), which can tell us the width of the letters and therefore what kerning to use.
The circumference of your circle is simply 2πr, and total width of the text is ctx.measureText("Your text here");. Get the ratio of these two values and you will find out how much you need to space out or squash together your words.
You probably want to apply the spacing modifier to the words as a whole, rather than the individual letters. To do this, use measureText() on the sentence with spaces stripped to get the width of the letters (and by extension the total width of the spaces).
Now you need to plot where each word will go. Use measureText() again to find the width of each word and plot its center point on your circle, adding a portion of the total space value between each word. Now use measureText() on each individual letter and draw it in the right place to get perfect kerning.
All being well, you should have a perfectly spaced circle of text.
So measure text is good, what I ended up doing, was Math.pow(measureText + measureTextOfLastChar, 3 / 4)
for some reason, square root of the sum of the widths of the current and previous character made some spacings too skinny, and without a square root at all, makes it bad too, but Math.pow(sum, 3/4) for some reason creates a great ratio. Heres the code ( in coffeescript )
CanvasRenderingContext2D::fillTextCircle = (str, centerX, centerY, radius, angle) ->
len = str.length
s = undefined
#save()
#translate centerX, centerY
#rotate - (1 + 1 / len) * angle / 2
n = 0
prevWidth = 0
while n < len
thisWidth = #measureText(str[n]).width
#rotate angle / len * Math.pow(thisWidth + prevWidth, 3 / 4) / #measureText(str).width
s = str[n]
prevWidth = #measureText(str[n]).width
#fillText s, -#measureText(str[n]).width / 2, -1 * radius
n++
#restore()
then call it using
context.fillTextCircle('hiya world', halfWidth, halfHeight, 95, 26)
I was guessing and checking a bit, though I took calc 4 so I subconsciously knew what I was doing. Anyway, it produces perfect character spacing, that you couldn't get without Math.pow(sum_character_widths, 3/4)
Everything can be changed, except keep the Math.pow(sum, 3/4) in the loop, since that's the part I made better than the rest of the stuff I found online.
In my html 5 canvas, I draw text (that has to be on 1 line) but it needs to be a constant font size and style and the width of the space I have is also constant. Therefore the text needs to fit in that space, but the problem occurs when the text is too long and goes past the space.
So is there a way I can horizontally stretch/compress text? (like in PhotoShop)
Something like convert it to an image then change the width of the image? Not sure if this is the best way...
Thanks
You can use measureText to determine the size of the text first, then scale the canvas if needed: http://jsfiddle.net/eGjak/887/.
var text = "foo bar foo bar";
ctx.font = "30pt Arial";
var width = ctx.measureText(text).width;
if(width <= 100) {
ctx.fillText(text, 0, 100);
} else {
ctx.save();
ctx.scale(100 / width, 1);
ctx.fillText(text, 0, 100);
ctx.restore();
}
A simple solution is to draw your text in another (in memory) canvas and then use drawImage to paste the canvas content in the real destination canvas.
Something like this (let it be parameterizable depending on your needs, here stretching with a ratio of 100 to 80) :
var tempimg = document.createElement('canvas');
tempimg.width = 100;
tempimg.height = 10;
oc = tempimg.getContext('2d');
oc.fillText(...)
yourdestcontext.drawImage(tempimg, 0, 0, 100, 10, x, y, 80, 10);