Size to fit font on a canvas
Solution 1
You can use context.measureText to get the pixel width of any given text in the current font.
Then if that width is too big, reduce the font size until it fits.
context.font="14px verdana";
var width = context.measureText("Hello...Do I fit on the canvas?").width
if(width>myDesiredWidth) // then reduce the font size and re-measure
Demo:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
fitTextOnCanvas("Hello, World!", "verdana", 125);
function fitTextOnCanvas(text, fontface, yPosition) {
// start with a large font size
var fontsize = 300;
// lower the font size until the text fits the canvas
do {
fontsize--;
context.font = fontsize + "px " + fontface;
} while (context.measureText(text).width > canvas.width)
// draw the text
context.fillText(text, 0, yPosition);
alert("A fontsize of " + fontsize + "px fits this text on the canvas");
}
body {
background-color: ivory;
}
#canvas {
border: 1px solid red;
}
<canvas id="canvas" width=300 height=300></canvas>
Solution 2
Simple and efficient solution for DOM environment, no loops, just do one sample measurement and scale the result appropriately.
function getFontSizeToFit(text: string, fontFace: string, maxWidth: number) {
const ctx = document.createElement('canvas').getContext('2d');
ctx.font = `1px ${fontFace}`;
return maxWidth / ctx.measureText(text).width;
}
Beware that if you use npm 'canvas' module for NodeJs environment, the result won't be that accurate as they use some custom C++ implementation that returns only integer sample width.
Solution 3
I have created an improved version of markE's code. It becomes apparent that his code is slower if you have multiple texts. The browsers are good at caching but on the first run you can definitely get a noticeable lag even with just a handful of lines that need scaling.
Try these two versions:
Original method (markE):
http://jsfiddle.net/be6ppdre/29/
Faster method:
http://jsfiddle.net/ho9thkvo/2/
The main code is here:
function fitTextOnCanvas(text, fontface){
var size = measureTextBinaryMethod(text, fontface, 0, 600, canvas.width);
return size;
}
function measureTextBinaryMethod(text, fontface, min, max, desiredWidth) {
if (max-min < 1) {
return min;
}
var test = min+((max-min)/2); //Find half interval
context.font=test+"px "+fontface;
measureTest = context.measureText(text).width;
if ( measureTest > desiredWidth) {
var found = measureTextBinaryMethod(text, fontface, min, test, desiredWidth)
} else {
var found = measureTextBinaryMethod(text, fontface, test, max, desiredWidth)
}
return found;
}
Solution 4
Add the maxWidth
Parameter to your context.textfill
$(document).ready(function () {
var canvas = $('#myCanvas')[0];
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0);
};
imageObj.src = "http://dummyimage.com/282x177/FFF/FFF";
$('#nametag').bind('change keyup input', updateCanvas);
$('#line2').bind('click', updateCanvas);
$('#line3').bind('click', updateCanvas);
$('#line4').bind('click', updateCanvas);
function updateCanvas() {
var maxWith = canvas.width;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(imageObj, 0, 0);
context.textAlign = "center";
context.font = "bold 18pt Arial";
context.fillText($('#line1').val(), canvas.width * 0.5, 70, maxWith);
context.font = "12pt Arial";
context.fillText($('#line2').val(), canvas.width * 0.5, 90, maxWith);
context.fillText($('#line3').val(), canvas.width * 0.5, 120, maxWith);
context.fillText($('#line4').val(), canvas.width * 0.5, 140, maxWith);
}
});
Solution 5
Base on @Veetaha answer, here is my function to fit multiple lines of center text
function getFontSizeToFit(ctx, text, fontFace, width, height) {
ctx.font = `1px ${fontFace}`;
let fitFontWidth = Number.MAX_VALUE
const lines = text.match(/[^\r\n]+/g);
lines.forEach(line => {
fitFontWidth = Math.min(fitFontWidth, width / ctx.measureText(line).width)
})
let fitFontHeight = height / (lines.length * 1.2); // if you want more spacing between line, you can increase this value
return Math.min(fitFontHeight, fitFontWidth)
}
Demo
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
let text = "Hello World \n Hello World 2222 \n AAAAA \n BBBB"
fitTextCenter()
function fitTextCenter() {
let fontSize = getFontSizeToFit(ctx, text, "Arial", c.width, c.height)
ctx.font = fontSize + "px Arial"
fillTextCenter(ctx, text, 0, 0, c.width, c.height)
}
function fillTextCenter(ctx, text, x, y, width, height) {
ctx.textBaseline = 'middle';
ctx.textAlign = "center";
const lines = text.match(/[^\r\n]+/g);
for (let i = 0; i < lines.length; i++) {
let xL = (width - x) / 2
let yL = y + (height / (lines.length + 1)) * (i + 1)
ctx.fillText(lines[i], xL, yL)
}
}
function getFontSizeToFit(ctx, text, fontFace, width, height) {
ctx.font = `1px ${fontFace}`;
let fitFontWidth = Number.MAX_VALUE
const lines = text.match(/[^\r\n]+/g);
lines.forEach(line => {
fitFontWidth = Math.min(fitFontWidth, width / ctx.measureText(line).width)
})
let fitFontHeight = height / (lines.length * 1.2); // if you want more spacing between line, you can increase this value
return Math.min(fitFontHeight, fitFontWidth)
}
function testScaleUpX() {
c.width += 1
fitTextCenter()
}
function testScaleUpY() {
c.height += 1
fitTextCenter()
}
function testScaleDownX() {
c.width -= 1
fitTextCenter()
}
function testScaleDownY() {
c.height -= 1
fitTextCenter()
}
<canvas id="myCanvas" width="200" height="80" style="border:1px solid #000;"></canvas>
<button onclick="testScaleUpX()">+ X</button>
<button onclick="testScaleUpY()">+ Y</button>
<button onclick="testScaleDownX()">- X</button>
<button onclick="testScaleDownY()">- Y</button>
Arian Faurtosh
Blog: https://arian.io Github: https://github.com/arianf Linkedin: http://linkedin.com/in/arianf
Updated on February 10, 2022Comments
-
Arian Faurtosh about 2 years
I currently have this http://jsfiddle.net/dgAEY/ which works perfectly, I just need to figure out a way to size the font when it gets too long. I've looked into Auto-size dynamic text to fill fixed size container and I've tried to apply the Jquery function they posted but I couldn't get it to work.
HTML
<form action="" method="POST" id="nametag" class="nametag"> Line1: <input type="text" id="line1" name="line1" style="width:250px;" /><br> Line2: <input type="text" id="line2" name="line2" style="width:250px;" /><br> Line3: <input type="text" id="line3" name="line3" style="width:250px;" /><br> Line4: <input type="text" id="line4" name="line4" style="width:250px;" /><br> <br><br><b>Name Tag</b><br> <canvas width="282px" height="177px" id="myCanvas" style="border: black thin solid;"></canvas> </form>
JavaScript
$(document).ready(function () { var canvas = $('#myCanvas')[0]; var context = canvas.getContext('2d'); var imageObj = new Image(); imageObj.onload = function() { context.drawImage(imageObj, 0, 0); }; imageObj.src = "http://dummyimage.com/282x177/FFF/FFF"; $('#nametag').bind('change keyup input', updateCanvas); $('#line2').bind('click', updateCanvas); $('#line3').bind('click', updateCanvas); $('#line4').bind('click', updateCanvas); function updateCanvas() { context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(imageObj, 0, 0); context.textAlign = "center"; context.font = "bold 18pt Arial"; context.fillText($('#line1').val(), canvas.width * 0.5, 70); context.font = "12pt Arial"; context.fillText($('#line2').val(), canvas.width * 0.5, 90); context.fillText($('#line3').val(), canvas.width * 0.5, 120); context.fillText($('#line4').val(), canvas.width * 0.5, 140); } });
-
daker over 10 yearsSo you want the whole text to always fit on one row? Or do you want the text to fill as much as possible?
-
Arian Faurtosh over 10 yearsThe whole text should only fit on one row or line.
-
Mark over 10 yearsI see in the linked post they extend jQuery to add the function
textfill
but I don't see you using the function? Did you extend jQuery with that function and use it? -
Arian Faurtosh over 10 yearsI did extend it, and tried using the function with
context.textfill
but it broke the javascript, and didn't do anything... Here is that example: jsfiddle.net/dgAEY/1 -
Veetaha about 4 yearsNo loops are needed, just do sample and scale appropriately, please see my answer.
-
-
Arian Faurtosh over 10 yearsDoing so will just squeeze the text together after reaching max width, this doesn't actually shrink text size: jsfiddle.net/dgAEY/2
-
daker over 10 yearsThen you should look up
measureText()
developer.mozilla.org/en-US/docs/… -
daker over 10 years@Arian Yes, but you can do it in a loop as well and loop until width < desired width and make a step in text size each turn.
-
markE over 10 yearsAs @daker says, let the computer do the work for you by putting the resizing in a loop. I updated my answer with code that finds the appropriate font size and draws the text to fit the canvas.
-
ccnokes about 10 yearsIt should be: context.font=fontsize+"px "+fontface; (you forgot the "px"). Without that, it doesn't work. Otherwise, it works. Thanks for the code.
-
Anonymous over 8 yearsI'd suggest binary search loop instead of linear search, so for max font size = 1024px only up to 10 iterations are needed in order to get fit size with 1px precision (instead of ~1000 for tiny test area).
-
Vibber about 8 yearsThe 0 and 600 in the call to measureTextBinaryMethod is the range of sizes that the text can have. So in other words you need to set the max size to a font size that is big enough for the shortest of your texts.
-
CrazyTim almost 4 yearsTested and works, and no loop required like other answers. Returns a decimal which is more accurate.
-
CrazyTim almost 4 yearsCould be improved slightly to pass a reference to an existing canvas context instead of creating a new canvas each time its called.
-
Will over 2 yearsthanks! multiple-line is important for us.