suppose that there is a text to be drawn inside a rotated bounding rectangle (not aligned to normal axes x-y), and that text can be also rotated, given the max width of the bounding box, how to select the best font size to use to draw a wrapped text inside that bounding box in html5 canvas and javascript?
I know that method: measureText() can measure dimensions of give font size, but I need the inverse of that: using a known width to get the problem font size.
thanks
You do not have to find the font point size to make it fit. The font will smoothly scale up and down according to the current transformation scale.
All you do is measureText
to find its textWidth
, get the pointSize
from the context.font
attribute then if you have the width
and height
of the box you need to fit then find the minimum of the width / textWidth
and height / pointSize
and you have the scale that you need to render the font at.
As a function
var scale2FitCurrentFont = function(ctx, text, width, height){
var points, fontWidth;
points = Number(ctx.font.split("px")[0]); // get current point size
points += points * 0.2; // As point size does not include hanging tails and
// other top and bottom extras add 20% to the height
// to accommodate the extra bits
var fontWidth = ctx.measureText(text).width;
// get the max scale that will allow the text to fi the current font
return Math.min(width / fontWidth, height / points);
}
The arguments are
Returns the scale to fit the text within the width and height.
The demo has it all integrated and it draws random boxes and fills with random text from your question. It keeps the font selection and point size separate from the font scaling so you can see it will work for any font and any point size.
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** FrameUpdate.js begin **/
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
var PI2 = Math.PI * 2; // 360 to save typing
var PIh = Math.PI / 2; // 90
// draws a rounded rectangle path
function roundedRect(ctx,x, y, w, h, r){
ctx.beginPath();
ctx.arc(x + r, y + r, r, PIh * 2, PIh * 3);
ctx.arc(x + w - r, y + r, r, PIh * 3, PI2);
ctx.arc(x + w - r, y + h - r, r, 0, PIh);
ctx.arc(x + r, y + h - r, r, PIh, PIh * 2);
ctx.closePath();
}
// random words
var question = "Suppose that there is a text to be drawn inside a rotated bounding rectangle (not aligned to normal axes x-y), and that text can be also rotated, given the max width of the bounding box, how to select the best font size to use to draw a wrapped text inside that bounding box in html5 canvas and javascript? I know that method: measureText() can measure dimensions of give font size, but I need the inverse of that: using a known width to get the problem font size. thanks.";
question = question.split(" ");
var getRandomWords= function(){
var wordCount, firstWord, s, i, text;
wordCount = Math.floor(rand(4)+1);
firstWord = Math.floor(rand(question.length - wordCount));
text = "";
s = "";
for(i = 0; i < wordCount; i++){
text += s + question[i + firstWord];
s = " ";
}
return text;
}
// fonts to use?? Not sure if these are all safe for all OS's
var fonts = "Arial,Arial Black,Verdanna,Comic Sans MS,Courier New,Lucida Console,Times New Roman".split(",");
// creates a random font with random points size in pixels
var setRandomFont = function(ctx){
var size, font;
size = Math.floor(rand(10, 40));
font = fonts[Math.floor(rand(fonts.length))];
ctx.font = size + "px " + font;
}
var scale2FitCurrentFont = function(ctx, text, width, height){
var points, fontWidth;
var points = Number(ctx.font.split("px")[0]); // get current point size
points += points * 0.2;
var fontWidth = ctx.measureText(text).width;
// get the max scale that will allow the text to fi the current font
return Math.min(width / fontWidth, height / points);
}
var rand = function(min, max){
if(max === undefined){
max = min;
min = 0;
}
return Math.random() * (max - min)+min;
}
var randomBox = function(ctx){
"use strict";
var width, height, rot, dist, x, y, xx, yy,cx, cy, text, fontScale;
// get random box
width = rand(40, 400);
height = rand(10, width * 0.4);
rot = rand(-PIh,PIh);
dist = Math.sqrt(width * width + height * height)
x = rand(0, ctx.canvas.width - dist);
y = rand(0, ctx.canvas.height - dist);
xx = Math.cos(rot);
yy = Math.sin(rot);
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
// rotate the box
ctx.setTransform(xx, yy, -yy, xx, x, y);
// draw the box
roundedRect(ctx, 0, 0, width, height, Math.min(width / 3, height / 3));
ctx.fill();
ctx.stroke();
// get some random text
text = getRandomWords();
// get the scale that will fit the font
fontScale = scale2FitCurrentFont(ctx, text, width - textMarginLeftRigth * 2, height - textMarginTopBottom * 2);
// get center of rotated box
cx = x + width / 2 * xx + height / 2 * -yy;
cy = y + width / 2 * yy + height / 2 * xx;
// scale the transform
xx *= fontScale;
yy *= fontScale;
// set the font transformation to fit the box
ctx.setTransform(xx, yy, -yy, xx, cx, cy);
// set up the font render
ctx.fillStyle = "Black";
ctx.textAlign = "center";
ctx.textBaseline = "middle"
// draw the text to fit the box
ctx.fillText(text, 0, 0);
}
var textMarginLeftRigth = 8; // margin for fitted text in pixels
var textMarginTopBottom = 4; // margin for fitted text in pixels
var drawBoxEveryFrame = 60; // frames between drawing new box
var countDown = 1;
// update function will try 60fps but setting will slow this down.
function update(){
// restore transform
ctx.setTransform(1, 0, 0, 1, 0, 0);
// fade clears the screen
ctx.fillStyle = "white"
ctx.globalAlpha = 1/ (drawBoxEveryFrame * 1.5);
ctx.fillRect(0, 0, w, h);
// reset the alpha
ctx.globalAlpha = 1;
// count frames
countDown -= 1;
if(countDown <= 0){ // if frame count 0 the draw another text box
countDown = drawBoxEveryFrame;
setRandomFont(ctx);
randomBox(ctx);
}
if(!STOP){ // do until told to stop.
requestAnimationFrame(update);
}else{
STOP = false;
}
}
update();
}
// demo code to restart on resize
var STOP = false; // flag to tell demo app to stop
function resizeEvent(){
var waitForStopped = function(){
if(!STOP){ // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped,200);
}
STOP = true;
setTimeout(waitForStopped,100);
}
window.addEventListener("resize",resizeEvent);
demo();
/** FrameUpdate.js end **/