How to measure text width with `italic` font style? - javascript

I am using convas context measureText to get the width of my text on the canvas. Below is the code:
ctx.fillStyle = color;
ctx.fontWeight = FONT_WEIGHT;
ctx.font = `bolder italic ${fontsize}px`;
const textWidth = ctx.measureText(text).width;
the problem is that if the font style is italic, the right of the text will be off boundary. That because measureText doesn't take italic into account. How can I calculate the text width for italic style?
Below are two screenshots for italic font on convas. The first one is the text without italic while the second one is with italic. You can see that the second one has a little off boundaries.

This is a problem inherent to italic rendering of fonts and their container box.
I am not a specialist and won't extend on the subject, which has already been treated in this Q/A about DOM + CSS. Simply note that 2DCanvas measureText also suffers from this same problem.
The only workaround I can think of is going through an svg element, which offers better graphical bounding box representation through its getBBox method.
// Calculate the BBox of a text through an svg Element
function getTextBox(txt, x, y) {
var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
var text = document.createElementNS("http://www.w3.org/2000/svg", 'text');
// so we don't see the svg element in page
svg.style = 'position: absolute; z-index:-1; width:0; height: 0';
text.setAttribute('x', x || 0);
text.setAttribute('y', y || 0);
text.setAttribute('text-anchor', getAlignment(this.textAlign));
text.setAttribute('alignment-baseline', this.textBaseline);
text.style.font = this.font;
text.textContent = txt;
svg.appendChild(text);
document.body.appendChild(svg);
var box = text.getBBox();
document.body.removeChild(svg);
return box;
// convert CSS text-align notation to correpsonding SVG text-anchor
function getAlignment(css) {
return {
'left': 'start',
'right': 'end',
'center': 'middle'
}[css] || '';
}
}
// attach it to the proto so it's easier to grab context's current status
Object.defineProperty(CanvasRenderingContext2D.prototype, 'getTextBox', {get: function() {return getTextBox;}});
var x = 20,
y = 100;
var ctx = canvas.getContext('2d');
ctx.font = 'bolder italic 100px serif';
var txt = 'Right';
ctx.fillText(txt, x, y);
// red => ctx.measureText
var m_width = ctx.measureText(txt).width;
ctx.strokeStyle = 'red';
ctx.strokeRect(x, 0, m_width, y);
ctx.strokeStyle = 'green';
// green => svg.getBBox
var box = ctx.getTextBox(txt, x, y);
ctx.strokeRect(box.x, box.y, box.width, box.height);
<canvas id="canvas"></canvas>

Set ctx.font first. The result of ctx.measureText is based on the context's current font, the same one you'd see if you were drawing. Do a ctx.fillText to check you have the font set correctly, I find the syntax is easy to get wrong.

You will want to load your text into a hidden HTML div and get the calculated width.

Related

Recreating Canvas Cuts Off Bottom Of Text?

I am attempting to make a dynamic canvas where when the user types text, the canvas rerenders for the text so the canvas width (and height) is the same as the text. I followed some guide that essentially said to create a script like this
Codesandbox.com Live Link: https://codesandbox.io/s/tender-sound-zk5x6h?file=/src/App.js
function ResizeCanvas(textInput) {
const canvas3 = canvasRef.current;
const canvasContext = canvas3.getContext("2d");
canvasContext.font = "40 40px arial";
canvasContext.fillStyle = "red";
canvasContext.textAlign = "left";
canvasContext.fillText(textInput, 0, canvas3.height - 1);
canvas3.width = canvasContext.measureText(textInput).width;
canvas3.style.width = canvas3.width + "px";
canvasContext.font = "40 40px arial";
canvasContext.fillStyle = "red";
canvasContext.textAlign = "left";
canvasContext.fillText(textInput, 0, canvas3.height - 1);
}
However, the bottom text is cut off. I noticed when I add a paddingBottom: "20px" to the canvas element, it doesnt show the rest of the text and increasing the height does not change anything.
How come I cannot see the full p, q letters?

How to crop color portion from image canvas?

I am working on ionic based application.
I want to functionality like user has Filled the red color on image(canvas) with finger. So I have done the filled functionality but I want to crop the Filled portion from canvas. I have attached one image for reference.
I want to crop red portion from above image. I has googling but not found any solution.
Creating a image mask.
If you are rendering the selection area (red) then the solution is simple.
Create a second canvas the same size as the drawing, and don't add it to the DOM. Draw the red marking content onto that canvas
The on the display canvas render the image first and then render that marking
canvas over the image with composite mode like "overlay" so that the original image can be seen and the marked areas are red.
Now you have two layers, one is the image and the other the mask you can use to get a copy of the marked content.
To do that create a 3rd canvas, draw the original image onto it, then set the composite mode to "destination-in". Then draw the mask over it. Only the marked pixels will remain.
See the example for more details
setTimeout(example,0); // ensures that the run us after parsing
function example(){
const ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var selectLayer = CImageCtx(w,h); // creates a canvas
var selectedContent = CImageCtx(w,h); // the selected content
document.body.appendChild(selectedContent);
var image = new Image; // the image
image.src = " https://i.stack.imgur.com/QhFct.png";
// updates the masked result
function updateSelected(){
var ctx = selectedContent.ctx;
ctx.drawImage(image,0,0);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(selectLayer,0,0);
ctx.globalCompositeOperation = "source-over";
}
function update(){
// if mouse down then
if(mouse.but){
// clear the mask if on the right image
if(mouse.oldBut === false && mouse.x > 256){
selectLayer.ctx.clearRect(0,0,w,h);
mouse.but = false;
}else{
// draw the red
selectLayer.ctx.fillStyle = "red";
fillCircle(mouse.x, mouse.y, 20, selectLayer.ctx);
}
// update the masked result
updateSelected();
}
// clear the canvas
ctx.clearRect(0,0,w,h);
// draw the image
ctx.drawImage(image,0,0);
// then draw the marking layer over it with comp overlay
ctx.globalCompositeOperation = "overlay";
ctx.drawImage(selectLayer,0,0);
ctx.globalCompositeOperation = "source-over";
mouse.oldBut = mouse.but;
requestAnimationFrame(update);
}
requestAnimationFrame(update);
}
//#############################################################################
// helper functions not part of the answer
//#############################################################################
const mouse = {
x : 0, y : 0, but : false,
events(e){
const m = mouse;
const bounds = canvas.getBoundingClientRect();
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
m.but = e.type === "mousedown" ? true : e.type === "mouseup" ? false : m.but;
}
};
(["down","up","move"]).forEach(name => document.addEventListener("mouse" + name,mouse.events));
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"),c.width = w,c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w,h), c.ctx = c.getContext("2d"), c);
const fillCircle = (l,y=ctx,r=ctx,c=ctx) =>{if(l.p1){c=y; r=leng(l);y=l.p1.y;l=l.p1.x }else if(l.x){c=r;r=y;y=l.y;l=l.x}c.beginPath(); c.arc(l,y,r,0,Math.PI*2); c.fill()}
body { font-family : arial; }
canvas { border : 2px solid black; }
Draw on image and the selected parts are shown on the right<br>
Click right image to reset selection<br>
<canvas id="canvas" width=256 height=256></canvas>
Already masked.
If the red mask is already applied to the image then there is not much you can do apart from do a threshold filter depending on how red the image is. But even then you are going to have problems with darker areas, and areas that already contain red.
Unless you have the original image you will have poor results.
If you have the original image then you will have to access the image data and create a new image as a mask by comparing each pixel and selecting only pixels that are different. That will force you to same domain images only (or with CORS cross origin headers)

Determine width of string in HTML5 canvas

I'm drawing text in an HTML5 canvas on top of an image (a meme generator). I want to calculate a font size so that the string will span the entire width of the canvas.
So basically, is there a way in JavaScript to determine what font size I should use for some string of a certain font for a given width?
The fillText() method has an optional fourth argument, which is the max width to render the string.
MDN's documentation says...
maxWidth
Optional; the maximum width to draw. If specified, and the string is
computed to be wider than this width, the font is adjusted to use a
more horizontally condensed font (if one is available or if a
reasonably readable one can be synthesized by scaling the current font
horizontally) or a smaller font.
However, at the time of writing, this argument isn't supported well cross browser, which leads to the second solution using measureText() to determine the dimensions of a string without rendering it.
var width = ctx.measureText(text).width;
Here is how I may do it...
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
// Font sizes must be in descending order. You may need to use `sort()`
// if you can't guarantee this, e.g. user input.
var fontSizes = [72, 36, 28, 14, 12, 10, 5, 2],
text = 'Measure me!';
// Default styles.
ctx.textBaseline = 'top';
ctx.fillStyle = 'blue';
var textDimensions,
i = 0;
do {
ctx.font = fontSizes[i++] + 'px Arial';
textDimensions = ctx.measureText(text);
} while (textDimensions.width >= canvas.width);
ctx.fillText(text, (canvas.width - textDimensions.width) / 2, 10);​
jsFiddle.
I have a list of font sizes in descending order and I iterate through the list, determining if the rendered text will fit within the canvas dimensions.
If it will, I render the text center aligned. If you must have padding on the left and right of the text (which will look nicer), add the padding value to the textDimensions.width when calculating if the text will fit.
If you have a long list of font sizes to try, you'd be better off using a binary search algorithm. This will increase the complexity of your code, however.
For example, if you have 200 font sizes, the linear O(n) iteration through the array elements could be quite slow.
The binary chop should be O(log n).
Here is the guts of the function.
var textWidth = (function me(fontSizes, min, max) {
var index = Math.floor((min + max) / 2);
ctx.font = fontSizes[index] + 'px Arial';
var textWidth = ctx.measureText(text).width;
if (min > max) {
return textWidth;
}
if (textWidth > canvas.width) {
return me(fontSizes, min, index - 1);
} else {
return me(fontSizes, index + 1, max);
}
})(fontSizes, 0, fontSizes.length - 1);
jsFiddle.
Write All Things You Want For Text. ( Ex. ctx.font )
Before Writing Actual Text Use ctx.measureText("myText").width To get Width Of Text, Because We Have Called It After Making Changes On ctx, It Will Be Different Each Time When We Change ctx Properties (ex. font)
Now We Will Scale It From Middle.
ctx.translate(midPoint_X_Of_Text, midPoint_Y_Of_Text);
ctx.scale(desiredWidth/measuredWidth, desiredWidth/measuredWidth);
ctx.translate(-midPoint_X_Of_Text, -midPoint_Y_Of_Text);
Write Text ctx.fillText() or ctx.strokeText()
Reverse All Changes By ctx.save() and ctx.restore() Or Manually like setTransform()
var can = document.getElementById('myCan');
var ctx = can.getContext("2d");
var desiredWidth = can.width;
var myText = "Hello How Are You?"
function draw(){
ctx.font = can.height/2 + "px verdana";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
let measuredWidth = ctx.measureText(myText).width;
let ratio = desiredWidth/measuredWidth;
ctx.translate(can.width/2,can.height/2);
ctx.scale(ratio,ratio);
ctx.translate(-can.width/2,-can.height/2);
ctx.fillText(myText,can.width/2,can.height/2);
ctx.setTransform();
}
draw();
let myInput = document.getElementById('myInput');
myInput.addEventListener('input',(e)=>{
ctx.clearRect(0,0,can.width,can.height);
myText = myInput.value;
draw();
})
<html>
<body>
<center>
<input id="myInput" value="Hello How Are You?"/><br>
<canvas id="myCan" height="300" width=300 style="border:black solid 2px;">
</canvas>
</center>
</body>
</html>
I have the next code and it works perfectly for me. Maybe there is a little error but I don't need to have a list of font sizes. I just get the width with a font size and if it is too big I calculate a proportional size depending of the canvas width and padding (50)
ctx.font = FONT_SIZE+"pt Verdana";
var textWidth = ctx.measureText(markText).width;
if(textWidth > canvas.width - 50) {
ctx.font = parseInt(FONT_SIZE*(canvas.width-50)/textWidth)+"pt Verdana";
textWidth = ctx.measureText(markText).width;
}
ctx.textBaseline = "middle";
ctx.fillStyle = "rgba(255,255,255,0.5)";
ctx.strokeStyle = "rgba(0,0,0,0.5)";
var xT = image.width / 2 - textWidth / 2;
var yT = image.height / 2;
ctx.fillText(markText, xT, yT);
ctx.strokeText(markText, xT, yT);
use second temporary canvas2D
function determineWidth(text, font) {
var textCtx = document.createElement("canvas").getContext("2d");
textCtx.font = font
let measure = textCtx.measureText(text);
let fontHei = parseInt(font.slice(0, font.indexOf("px")))
return [measure.width,fontHei]
}
use it before create real canvas
let font = `40px monospace`;
let [wid,hei]=determineWidth(text,font)
let textAlign = "center";
let textBaseline = "middle";
let fillStyle = "black";
var textCtx = document.createElement("canvas").getContext("2d");
textCtx.canvas.width = wid;
textCtx.canvas.height = hei;
textCtx.font = font
textCtx.textAlign = textAlign
textCtx.textBaseline = textBaseline
textCtx.fillStyle = fillStyle
textCtx.clearRect(0, 0, textCtx.canvas.width, textCtx.canvas.height);
textCtx.fillText(text, textCtx.canvas.width / 2, textCtx.canvas.height / 2);

Letter spacing in canvas element

The question says it all pretty much. I've been searching around and starting to worry that it's impossible.
I've got this canvas element that I'm drawing text to. I want to set the letter spacing similar to the CSS letter-spacing attribute. By that I mean increasing the amount of pixels between letters when a string is drawn.
My code for drawing the text is like so, ctx is the canvas context variable.
ctx.font = "3em sheepsans";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillText("Blah blah text", 1024 / 2, 768 / 2);
I've tried adding ctx.letterSpacing = "2px"; before the drawing but with no avail. Is there a way to do this just with a simple setting, or will I have to make a function to individually draw each character with the spacing in mind?
You can't set the letter spacing property, but you you can accomplish wider letter spacing in canvas by inserting one of the various white spaces in between every letter in the string. For instance
ctx.font = "3em sheepsans";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "rgb(255, 255, 255)";
var ctext = "Blah blah text".split("").join(String.fromCharCode(8202))
ctx.fillText(ctext, 1024 / 2, 768 / 2);
This will insert a hair space between every letter.
Using 8201 (instead of 8202) will insert the slightly wider thin space
For more white space options, see this list of Unicode Spaces
This method will help you to preserve the font's kerning much more easily than manually positioning each letter, however you wont be able to tighten your letter spacing this way.
I'm not sure if it should work (per specs), but in some browsers (Chrome) you can set the letter-spacing CSS property on the <canvas> element itself, and it will be applied to all text drawn on the context. (Works in Chrome v56, does not work in Firefox v51 or IE v11.)
Note that in Chrome v56 you must re-get the canvas 2d context (and re-set any values you care about) after each change to the letter-spacing style; the spacing appears to be baked into the 2d context that you get.
Example: https://jsfiddle.net/hg4pbsne/1/
var inp = document.querySelectorAll('input'),
can = document.querySelector('canvas'),
ctx = can.getContext('2d');
can.width = can.offsetWidth;
[].forEach.call(inp,function(inp){ inp.addEventListener('input', redraw, false) });
redraw();
function redraw(){
ctx.clearRect(0,0,can.width,can.height);
can.style.letterSpacing = inp[0].value + 'px';
ctx = can.getContext('2d');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '4em sans-serif';
ctx.fillText('Hello', can.width/2, can.height*1/4);
can.style.letterSpacing = inp[1].value + 'px';
ctx = can.getContext('2d');
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '4em sans-serif';
ctx.fillText('World', can.width/2, can.height*3/4);
};
canvas { background:white }
canvas, label { display:block; width:400px; margin:0.5em auto }
<canvas></canvas>
<label>hello spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label>
<label>world spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label>
Original, cross-browser answer:
This is not possible; the HTML5 Canvas does not have all the text-transformation power of CSS in HTML. I would suggest that you should combine the appropriate technologies for each usage. Use HTML layered with Canvas and perhaps even SVG, each doing what it does best.
Note also that 'rolling your own'—drawing each character with a custom offset—is going to produce bad results for most fonts, given that there are letter kerning pairs and pixel-aligned font hinting.
You can't set letter-spacing as a property of the Canvas context. You can only achieve the effect by doing manual spacing, sorry. (As in, drawing each letter manually increasing the x by some pixel amount on each)
For the record, you can set a few text properties by using ctx.font but letter-spacing is not one of them. The ones you can set are: "font-style font-variant font-weight font-size/line-height font-family"
For instance you can technically write ctx.font = "bold normal normal 12px/normal Verdana" (or any omission of any of those) and it will parse correctly.
To allow for 'letter kerning pairs' and the like, I've written the following. It should take that into account, and rough testing suggests it does. If you have any comments on it then I would point you to my question on the subject (Adding Letter Spacing in HTML Canvas)
Basically it uses measureText() to get the width of the whole string, and then removes the first character of the string and measures the width of the remaining string, and uses the difference to calculate the correct positioning - thus taking into account kerning pairs and the like. See the given link for more pseudocode.
Here's the HTML:
<canvas id="Test1" width="800px" height="200px"><p>Your browser does not support canvas.</p></canvas>
Here's the code:
this.fillTextWithSpacing = function(context, text, x, y, spacing)
{
//Start at position (X, Y).
//Measure wAll, the width of the entire string using measureText()
wAll = context.measureText(text).width;
do
{
//Remove the first character from the string
char = text.substr(0, 1);
text = text.substr(1);
//Print the first character at position (X, Y) using fillText()
context.fillText(char, x, y);
//Measure wShorter, the width of the resulting shorter string using measureText().
if (text == "")
wShorter = 0;
else
wShorter = context.measureText(text).width;
//Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
wChar = wAll - wShorter;
//Increment X by wChar + spacing
x += wChar + spacing;
//wAll = wShorter
wAll = wShorter;
//Repeat from step 3
} while (text != "");
}
Code for demo/eyeball test:
element1 = document.getElementById("Test1");
textContext1 = element1.getContext('2d');
textContext1.font = "72px Verdana, sans-serif";
textContext1.textAlign = "left";
textContext1.textBaseline = "top";
textContext1.fillStyle = "#000000";
text = "Welcome to go WAVE";
this.fillTextWithSpacing(textContext1, text, 0, 0, 0);
textContext1.fillText(text, 0, 100);
Ideally I'd throw multiple random strings at it and do a pixel by pixel comparison. I'm also not sure how good Verdana's default kerning is, though I understand it's better than Arial - suggestions on other fonts to try gratefully accepted.
So... so far it looks good. In fact it looks perfect.
Still hoping that someone will point out any flaws in the process.
In the meantime I will put this here for others to see if they are looking for a solution on this.
here's some coffeescript that allows you to set kerning to your context like so
tctx = tcanv.getContext('2d')
tctx.kerning = 10
tctx.fillStyle = 'black'
tctx.fillText 'Hello World!', 10, 10
the supporting code is:
_fillText = CanvasRenderingContext2D::fillText
CanvasRenderingContext2D::fillText = (str, x, y, args...) ->
# no kerning? default behavior
return _fillText.apply this, arguments unless #kerning?
# we need to remember some stuff as we loop
offset = 0
_.each str, (letter) =>
_fillText.apply this, [
letter
x + offset + #kerning
y
].concat args # in case any additional args get sent to fillText at any time
offset += #measureText(letter).width + #kerning
The javascript would be
var _fillText,
__slice = [].slice;
_fillText = CanvasRenderingContext2D.prototype.fillText;
CanvasRenderingContext2D.prototype.fillText = function() {
var args, offset, str, x, y,
_this = this;
str = arguments[0], x = arguments[1], y = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : [];
if (this.kerning == null) {
return _fillText.apply(this, arguments);
}
offset = 0;
return _.each(str, function(letter) {
_fillText.apply(_this, [letter, x + offset + _this.kerning, y].concat(args));
offset += _this.measureText(letter).width + _this.kerning;
});
};
Not true. You can add letter-spacing property to the canvas element in css and it works perfectly. No need for complicated workarounds. I just figured it out right now in my canvas project. i.e.:
canvas {
width: 480px;
height: 350px;
margin: 30px auto 0;
padding: 15px 0 0 0;
background: pink;
display: block;
border: 2px dashed brown;
letter-spacing: 0.1em;
}
This might an old question, but it's still relevant. I took Patrick Matte's expansion of James Carlyle-Clarke's response and got something that, I think, works quite well as is still plain-old-Javascript. The idea is to measure the space between two consecutive characters and "add" to it. Yes, negative numbers work.
Here's what I've got (the heavily commented version):
function fillTextWithSpacing (context, text, x, y, spacing) {
// Total width is needed to adjust the starting X based on text alignment.
const total_width = context.measureText (text).width + spacing * (text.length - 1);
// We need to save the current text alignment because we have to set it to
// left for our calls to fillText() draw in the places we expect. Don't
// worry, we're going to set it back at the end.
const align = context.textAlign;
context.textAlign = "left";
// Nothing to do for left alignment, but adjustments are needed for right
// and left. Justify defeats the purpose of manually adjusting character
// spacing, and it requires a width to be known.
switch (align) {
case "right":
x -= total_width;
break;
case "center":
x -= total_width / 2;
break;
}
// We have some things to keep track of and the C programmer in me likes
// declarations on their own and in groups.
let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next;
// We're going to step through the text one character at a time, but we
// can't use for(... of ...) because we need to be able to look ahead.
for (offset = 0; offset < text.length; offset = offset + 1) {
// Easy on the eyes later
char = text.charAt (offset);
// Default the spacing between the "pair" of characters to 0. We need
// for the last character.
pair_spacing = 0;
// Check to see if there's at least one more character after this one.
if (offset + 1 < text.length) {
// This is always easier on the eyes
char_next = text.charAt (offset + 1);
// Measure to the total width of both characters, including the
// spacing between them... even if it's negative.
pair_width = context.measureText (char + char_next).width;
// Measure the width of just the current character.
char_width = context.measureText (char).width;
// Measure the width of just the next character.
char_next_width = context.measureText (char_next).width;
// We can determine the kerning by subtracting the width of each
// character from the width of both characters together.
pair_spacing = pair_width - char_width - char_next_width;
}
// Draw the current character
context.fillText (char, x, y);
// Advanced the X position by adding the current character width, the
// spacing between the current and next characters, and the manual
// spacing adjustment (negatives work).
x = x + char_width + pair_spacing + spacing;
}
// Set the text alignment back to the original value.
context.textAlign = align;
// Profit
}
And here's a demo:
let canvas = document.getElementById ("canvas");
canvas.width = 600;
canvas.height = 150;
let context = canvas.getContext ("2d");
function fillTextWithSpacing (context, text, x, y, spacing) {
const total_width = context.measureText (text).width + spacing * (text.length - 1);
const align = context.textAlign;
context.textAlign = "left";
switch (align) {
case "right":
x -= total_width;
break;
case "center":
x -= total_width / 2;
break;
}
let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next;
for (offset = 0; offset < text.length; offset = offset + 1) {
char = text.charAt (offset);
pair_spacing = 0;
if (offset + 1 < text.length) {
char_next = text.charAt (offset + 1);
pair_width = context.measureText (char + char_next).width;
char_width = context.measureText (char).width;
char_next_width = context.measureText (char_next).width;
pair_spacing = pair_width - char_width - char_next_width;
}
context.fillText (char, x, y);
x = x + char_width + pair_spacing + spacing;
}
context.textAlign = align;
}
function update () {
let
font = document.getElementById ("font").value,
size = parseInt (document.getElementById ("size").value, 10),
weight = parseInt (document.getElementById ("weight").value, 10),
italic = document.getElementById ("italic").checked,
spacing = parseInt (document.getElementById ("spacing").value, 10),
text = document.getElementById ("text").value;
context.textAlign = "center";
context.textBaseline = "alphabetic";
context.fillStyle = "#404040";
context.font = (italic ? "italic " : "") + weight + " " + size + "px " + font;
context.clearRect (0, 0, canvas.width, canvas.height);
fillTextWithSpacing (context, text, canvas.width / 2, (canvas.height + size) / 2, spacing);
}
document.getElementById ("font").addEventListener (
"change",
(event) => {
update ();
}
);
document.getElementById ("size").addEventListener (
"change",
(event) => {
update ();
}
);
document.getElementById ("weight").addEventListener (
"change",
(event) => {
update ();
}
);
document.getElementById ("italic").addEventListener (
"change",
(event) => {
update ();
}
);
document.getElementById ("spacing").addEventListener (
"change",
(event) => {
update ();
}
);
document.getElementById ("text").addEventListener (
"input",
(event) => {
update ();
}
);
update ();
select, input {
display: inline-block;
}
input[type=text] {
display: block;
margin: 0.5rem 0;
}
canvas {
border: 1px solid #b0b0b0;
width: 600px;
height: 150px;
}
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
</head>
<body>
<select id="font">
<option value="serif">Serif</option>
<option value="sans-serif">Sans Serif</option>
<option value="fixed-width">Fixed Width</option>
</select>
<label>Size: <input type="number" id="size" value="60" min="1" max="200" size="3" /></label>
<label>Weight: <input type="number" id="weight" value="100" min="100" max="1000" step="100" size="4" /></label>
<label>Italic: <input type="checkbox" id="italic" checked /></label>
<label>Spacing: <input type="number" id="spacing" value="0" min="-200" max="200" size="4" /></label>
<input type="text" id="text" placeholder="Text" value="hello" size="40"/>
<canvas id="canvas"></canvas>
</body>
</html>
Actually letter spacing concept canvas is not supporting.
So i used javascript to do this.
var value = $('#sourceText1').val().split("").join(" ");
OR
var sample_text = "Praveen Chelumalla";
var text = sample_text.split("").join(" ");
I don't know about other people but I have adjusted line spacing by increasing the y value on the text that I am writing. I'm actually splitting a string by spaces and kicking each word down a line inside a loop. The numbers i use are based on the default font. If you use a different font that these numbers may need to be adjusted.
// object holding x and y coordinates
var vectors = {'x':{1:100, 2:200}, 'y':{1:0, 2:100}
// replace the first letter of a word
var newtext = YOURSTRING.replace(/^\b[a-z]/g, function(oldtext) {
// return it uppercase
return oldtext.toUpperCase();
});
// split string by spaces
newtext = newtext.split(/\s+/);
// line height
var spacing = 10 ;
// initial adjustment to position
var spaceToAdd = 5;
// for each word in the string draw it based on the coordinates + spacing
for (var c = 0; c < newtext.length; c++) {
ctx.fillText(newtext[c], vectors.x[i], vectors.y[i] - spaceToAdd);
// increment the spacing
spaceToAdd += spacing;
}
Here's another method based on James Carlyle-Clarke's previous answer. It also lets you align the text left, center and right.
export function fillTextWithSpacing(context, text, x, y, spacing, textAlign) {
const totalWidth = context.measureText(text).width + spacing * (text.length - 1);
switch (textAlign) {
case "right":
x -= totalWidth;
break;
case "center":
x -= totalWidth / 2;
break;
}
for (let i = 0; i < text.length; i++) {
let char = text.charAt(i);
context.fillText(char, x, y);
x += context.measureText(char).width + spacing;
}
}
Letter spacing in canvas IS SUPPORTED, I used this
canvas = document.getElementById('canvas');
canvas.style.letterSpacing = '2px';
I use:
ctx.font = "32px Tahoma";//set font
ctx.scale(0.75,1);//important! the scale
ctx.fillText("LaFeteParFete test text", 2, 274);//draw
ctx.setTransform(1,0,0,1,0,0);//reset transform

text based countdown on canvas

I would like to run a text countdown on canvas but last I checked there was no way to write text to a canvas.
I would like to know if anyone else has come-across an implementation where I could do a numerical countdown from 60 to 0 on the canvas.
$(function () {
var width = 200;
var height = 200;
$('canvas').width(width).height(height);
var ctx = $('canvas')[0].getContext('2d');
var i = 60;
(function draw() {
with(ctx) {
fillStyle = '#000';
fillRect(0, 0, width, height);
fillStyle = '#0f0';
font = 'bold 20px Arial';
fillText(i, 100, 50)
fill();
}
if (!(i--)) return;
setTimeout(draw, 1000);
})();
});
see in action
It is possible to draw text in canvas 2D. If you look at the w3c API documentation you can see the fillText method on context which allows you do draw text, and the font property lets you control the appearance.
Do note: not all canvas 2D implementations support the text API - I know that iOS didn't do it in the past.
This page suggests that it is indeed possible to write text on a canvas.

Categories

Resources