CSS `line-height` relative to baseline (with JS?) [duplicate] - javascript

I'm trying to do something that should be very simple but I've spent my day between failures and forums..
I would like to adjust my font in order to match my baseline. On indesign it's one click but in css it looks like the most difficult thing on earth..
Lets take a simple example with rational values.
On this image I have a baseline every 20px.
So for my <body> I do:
<style>
body {font-size:16px; line-height:20px;}
</style>
Everything works perfectly. My paragraph matchs the baseline.
But when I'm scripting my <h> that doesn't match the baseline anymore.. what am I doing wrong? That should follow my baseline, shouldn't it?
<style type="text/css">
body{font-size: 16px; line-height: 20px;}
h1{font-size: 5em; line-height: 1.25em;}
h2{font-size: 4em; line-height: 1.25em;}
h3{font-size: 3em; line-height: 1.25em;}
h4{font-size: 2em; line-height: 1.25em;}
</style>
ps: 20/16=1.25em
In my inspector, computed returns the expected values
h1{font-size: 84px; line-height: 100px;}
h2{font-size: 68px; line-height: 80px;}
h3{font-size: 52px; line-height: 60px;}
h4{font-size: 36px; line-height: 40px;}
So that should display something like this no?

It is a bit complicated - you have to measure the fonts first (as InDesign does) and calculate "line-height", the thing you called "bottom_gap" and some other stuff
I'm pretty sure we can do something in JavaScript..
You are right – but for Typography JS is used to calculate the CSS (depending on the font metrics)
Did demo the first step (measuring a font) here
https://codepen.io/sebilasse/pen/gPBQqm
It is just showing graphically what is measured [for the technical background]
This measuring is needed because every font behaves totally different in a "line".
Here is a generator which could generate such a Typo CSS:
https://codepen.io/sebilasse/pen/BdaPzN
A function to measure could be based on <canvas> and look like this :
function getMetrics(fontName, fontSize) {
// NOTE: if there is no getComputedStyle, this library won't work.
if(!document.defaultView.getComputedStyle) {
throw("ERROR: 'document.defaultView.getComputedStyle' not found. This library only works in browsers that can report computed CSS values.");
}
if (!document.querySelector('canvas')) {
var _canvas = document.createElement('canvas');
_canvas.width = 220; _canvas.height = 220;
document.body.appendChild(_canvas);
}
// Store the old text metrics function on the Canvas2D prototype
CanvasRenderingContext2D.prototype.measureTextWidth = CanvasRenderingContext2D.prototype.measureText;
/**
* Shortcut function for getting computed CSS values
*/
var getCSSValue = function(element, property) {
return document.defaultView.getComputedStyle(element,null).getPropertyValue(property);
};
/**
* The new text metrics function
*/
CanvasRenderingContext2D.prototype.measureText = function(textstring) {
var metrics = this.measureTextWidth(textstring),
fontFamily = getCSSValue(this.canvas,"font-family"),
fontSize = getCSSValue(this.canvas,"font-size").replace("px",""),
isSpace = !(/\S/.test(textstring));
metrics.fontsize = fontSize;
// For text lead values, we meaure a multiline text container.
var leadDiv = document.createElement("div");
leadDiv.style.position = "absolute";
leadDiv.style.margin = 0;
leadDiv.style.padding = 0;
leadDiv.style.opacity = 0;
leadDiv.style.font = fontSize + "px " + fontFamily;
leadDiv.innerHTML = textstring + "<br/>" + textstring;
document.body.appendChild(leadDiv);
// Make some initial guess at the text leading (using the standard TeX ratio)
metrics.leading = 1.2 * fontSize;
// Try to get the real value from the browser
var leadDivHeight = getCSSValue(leadDiv,"height");
leadDivHeight = leadDivHeight.replace("px","");
if (leadDivHeight >= fontSize * 2) { metrics.leading = (leadDivHeight/2) | 0; }
document.body.removeChild(leadDiv);
// if we're not dealing with white space, we can compute metrics
if (!isSpace) {
// Have characters, so measure the text
var canvas = document.createElement("canvas");
var padding = 100;
canvas.width = metrics.width + padding;
canvas.height = 3*fontSize;
canvas.style.opacity = 1;
canvas.style.fontFamily = fontFamily;
canvas.style.fontSize = fontSize;
var ctx = canvas.getContext("2d");
ctx.font = fontSize + "px " + fontFamily;
var w = canvas.width,
h = canvas.height,
baseline = h/2;
// Set all canvas pixeldata values to 255, with all the content
// data being 0. This lets us scan for data[i] != 255.
ctx.fillStyle = "white";
ctx.fillRect(-1, -1, w+2, h+2);
ctx.fillStyle = "black";
ctx.fillText(textstring, padding/2, baseline);
var pixelData = ctx.getImageData(0, 0, w, h).data;
// canvas pixel data is w*4 by h*4, because R, G, B and A are separate,
// consecutive values in the array, rather than stored as 32 bit ints.
var i = 0,
w4 = w * 4,
len = pixelData.length;
// Finding the ascent uses a normal, forward scanline
while (++i < len && pixelData[i] === 255) {}
var ascent = (i/w4)|0;
// Finding the descent uses a reverse scanline
i = len - 1;
while (--i > 0 && pixelData[i] === 255) {}
var descent = (i/w4)|0;
// find the min-x coordinate
for(i = 0; i<len && pixelData[i] === 255; ) {
i += w4;
if(i>=len) { i = (i-len) + 4; }}
var minx = ((i%w4)/4) | 0;
// find the max-x coordinate
var step = 1;
for(i = len-3; i>=0 && pixelData[i] === 255; ) {
i -= w4;
if(i<0) { i = (len - 3) - (step++)*4; }}
var maxx = ((i%w4)/4) + 1 | 0;
// set font metrics
metrics.ascent = (baseline - ascent);
metrics.descent = (descent - baseline);
metrics.bounds = { minx: minx - (padding/2),
maxx: maxx - (padding/2),
miny: 0,
maxy: descent-ascent };
metrics.height = 1+(descent - ascent);
} else {
// Only whitespace, so we can't measure the text
metrics.ascent = 0;
metrics.descent = 0;
metrics.bounds = { minx: 0,
maxx: metrics.width, // Best guess
miny: 0,
maxy: 0 };
metrics.height = 0;
}
return metrics;
};
Note that you also need a good "reset.css" to reset the browser margins and paddings.
You click "show CSS" and you can also use the generated CSS to mix multiple fonts:
If they have different base sizes, normalize the second:
var factor = CSS1baseSize / CSS2baseSize;
and now recalculate each font in CSS2 with
var size = size * factor;
See a demo in https://codepen.io/sebilasse/pen/oENGev?editors=1100
What if it comes to images?
The following demo uses two fonts with the same metrics plus an extra JS part. It is needed to calculate media elements like images for the baseline grid :
https://codepen.io/sebilasse/pen/ddopBj

Related

javascript generate similar random color (shader || Tint || Monochromatic )

(HI) I am not a specialist in colorimetry, but I would like to know how to realize a script that generates random colors, but based on a master color.
maybe the random Shades or Tints
Ex:of #f25f9a.
http://www.color-hex.com/color/f25f9a
#f25f9a
#f36fa4
#f47eae
#f58fb8
#f79fc2
#f8afcc
#f9bfd6
#fbcfe0
#fcdfea
#fdeff4
#ffffff
so i need to make a random color
function colors() { return ('0x' + Math.floor(Math.random() * 16777215).toString(16) || 0xffffff) };
and after that convert it to hex
function ColorToHex(hexb){return '#'+hexb.slice(2);}
then generate random tint or shader or Monochromatic Colors based on the ColorToHex
it for a plugin developement with frames for debugging sprite.
thank for help , if you know any snippets?.
You could take the delta of a single color to 255 as variable part for random multiplication. Take the same random value for every color and build a string in the wanted format.
function getRandomColor(color) {
var p = 1,
temp,
random = Math.random(),
result = '#';
while (p < color.length) {
temp = parseInt(color.slice(p, p += 2), 16)
temp += Math.floor((255 - temp) * random);
result += temp.toString(16).padStart(2, '0');
}
return result;
}
var color = '#f25f9a',
i,
rc;
for (i = 0; i < 10; i++) {
rc = getRandomColor(color);
document.body.innerHTML += '<div style="background-color: ' + rc + ';">' + rc + '</div>';
}
I'm not sure it is really what is being asked (I'm still not sure what is being asked) and I'm almost sure it will make colorimetry guys angry, but here is a lazy (i.e non-mathy) way to achieve something similar:
This solution uses an offscreen canvas to draw a gradient, and then extract the pixels from this gradient.
// returns an array of CSS color strings
// #from: CSS color string of first color
// #to: CSS color string of last color
// #numberOfShades: number of shades to be returned (from and end included)
function getGradColors(from, to, numberOfShades){
// generate a canvas context and store it in cache
var ctx = getGradColors.ctx || (getGradColors.ctx = document.createElement('canvas').getContext('2d'));
// set our canvas dimensions according to the number of shades required
var w = ctx.canvas.width = numberOfShades || 10;
ctx.canvas.height = 1;
// create a linear gradient
// (to keep 'from' and 'to' values, we set its x to 1 and width to width -1)
var grad = ctx.createLinearGradient(1,0,w-1, 0);
grad.addColorStop(0, from || 'white');
grad.addColorStop(1, to || 'black');
ctx.fillStyle = grad;
ctx.fillRect(0,0,w,1); // draw it
var data = ctx.getImageData(0,0,w,1); // get the pixels info ([r, g, b, a, r, g...])
var colors = [];
data.data.forEach(function(comp, i){
if(i%4===0){ // map each pixel in its own array
colors.push([]);
}
if(i%4===3){ // alpha
comp /= 255;
}
colors[colors.length - 1].push(comp);
});
return colors.map(function(c){
// return a CSS computed value
ctx.fillStyle = 'rgba('+c.join()+')';
return ctx.fillStyle;
});
}
var shadesOfWhite = getGradColors('#f25f9a', 'white', 10);
console.log('to white: ', shadesOfWhite);
shadesOfWhite.forEach(generateSpan);
container.appendChild(document.createElement('br'));
var shadesOfBlack = getGradColors('#f25f9a', 'black', 10);
console.log('to black: ', shadesOfBlack);
shadesOfBlack.forEach(generateSpan);
function generateSpan(color){
var span = document.createElement('span');
span.style.backgroundColor = color;
container.appendChild(span);
}
#container > span{
width: 10vw;
height: 5vw;
display: inline-block;
}
body{margin: 0}
<div id="container"></div>
let randomColor = Math.ceil((Math.random() * Math.pow(255, 3))).toString(16);
while (randomColor.length < 6) {
randomColor = '0' + randomColor;
}
randomColor = '#' + randomColor;

Turn list into polygon that scales

I have used latex and in particular tikz quite a bit. Using this I was able to create the image shown below.
The following short code was used to create the image.
\documentclass[tikz]{standalone}
\begin{document}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary{backgrounds}
\begin{tikzpicture}[background rectangle/.style={fill=black},
show background rectangle]
\def\pages{
Home,
Events,
Pictures,
Video,
Contact,
About,
Map
}
\def\ngon{7}
\node[regular polygon,regular polygon sides=\ngon,minimum size=3cm] (p) {};
\foreach\page [count=\x] in \pages{\node[color=white, shift={(\x*360/7+35:0.4)}] (p\x) at (p.corner \x){\page};}
\foreach\i in {1,...,\numexpr\ngon-1\relax}{
\foreach\j in {\i,...,\x}{
\draw[thin, orange, dashed] (p\i) -- (p\j);
}
}
\end{tikzpicture}
\end{document}
I have tried for the last few hours to recreate the same image using 'HTMLæ, 'CSS' and 'Javascript'. I used the 'canvas' element to draw the lines, however I ran into a series of problems as can be seen in the image below
Which was made with the following code. I tried to the best of my abilities to minimize the code. The code can be found at the bottom of the post. The code has the following problems
Scalability. The text in the image is not the same as in the 'body' of the page.
The image hides the rest of the text in the body
To place the text outside the figure is hardcoded
The last minor problem is that the first element in the list is not drawn
I would like to address the problems above, but I am unsure how to proceed. Again I am not married to the idea of using canvas (can a better result be done using nodes and elements instead). However, the output should mimic the first image as closely as possible.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Canvas octagon</title>
<style>
* {
margin: 0;
padding: 0;
color:white;
background:black;
}
canvas {
display: block;
}
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
/* Disable scrollbars */
display: block;
/* No floating content on sides */
}
</style>
</head>
<body>
<canvas id="polygon"></canvas>
<h2>more space</h2>
<ol id="poly">
<li>About</li>
<li>Home</li>
<li>Pictures</li>
<li>Video</li>
<li>Events</li>
<li>Map</li>
<li>Apply?</li>
<li>Recepies</li>
</ol>
some more text here
<script>
(function() {
var canvas = document.getElementById('polygon'),
context = canvas.getContext('2d');
// resize the canvas to fill browser window dynamically
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/**
* Your drawings need to be inside this function otherwise they will be reset when
* you resize the browser window and the canvas goes will be cleared.
*/
drawStuff();
}
resizeCanvas();
function drawStuff() {
// do your drawing stuff here
context.beginPath();
context.translate(120, 120);
context.textAlign = "center";
var edges = document.getElementById("poly").getElementsByTagName("li");
var sides = edges.length
var angle = (Math.PI * 2) / sides;
var radius = 50;
context.save();
for (var i = 0, item; item = edges[i]; i++) {
console.log("Looping: index ", i, "item " + item.innerText);
var start_x = radius * Math.cos(angle * i);
var start_y = radius * Math.sin(angle * i);
context.lineTo(start_x, start_y);
var new_x_text = 1.4 * radius * Math.cos(angle * i);
var new_y_text = 1.4 * radius * Math.sin(angle * i);
context.fillText(item.innerText, new_x_text, new_y_text);
context.strokeStyle = 'orange';
for (var j = 0; j < i; j++) {
var new_x = radius * Math.cos(angle * j);
var new_y = radius * Math.sin(angle * j);
context.moveTo(start_x, start_y);
context.lineTo(new_x, new_y);
console.log(new_x, new_y);
}
context.fillStyle = 'white'
}
var new_x = radius * Math.cos(0);
var new_y = radius * Math.sin(0);
context.lineTo(new_x, new_y);
context.stroke();
}
})();
</script>
</body>
</html>
Using the canvas to render content
First I will say that using javascript will be longer than if you use some symbolic representation language like Latex. It is designed to do graphical representations with the minimum of fuss. The actual code base that makes it work is substantial but hidden for the general user.
Using the DOM
As the content for the canvas is stored in the DOM it also a good idea to store as much information as you can in the DOM, the colors, fonts, etc can all be stored in an element`s dataset.
For this I have put the settings in the ordered list. It contains all the settings, but there is also a default set of settings in the rendering function. The elements dataset will overwrite the defaults, or you can not add any dataset properties and let it all use the defaults.
Vetting settings
In the example below I have only put a minimum of vetting. People tend to put quotes around everything in the DOM as numbers can sometimes not work if represented as a string, I force all the numbers to the correct type. Though to be safe I should have checked to see if indeed they are valid numbers, the same for the other settings. I have just assumed that they will be correctly formatted.
The function
All the work is done in a function, you pass it the query string needed to find the list and canvas. It then uses the list items to render to the canvas.
Relative sizes
As the canvas size is not always known (it could be scaled via CSS) you need to have some way to specify size independent of pixels. For this I use a relative size. Thus the font size is as a fraction of the canvas size eg data-font-size = 16 means that the font will be 1/16th of the canvas height. The same for the line width, and the dash size is a multiple of the line width. eg data-line-dash = 4 means that the dashes are 4 times the length of the line width.
Element's data properties
To use data set you add the property to the element in the HTML prefixed with the word data- then the property name/s separated by "-". In javascript you can not use "-" directly as part of a variable name (it's a subtract operator) so the property names are converted to camelcase (the same as CSS properties), and stored in the element's dataset property.
<!-- HTML -->
<div id="divElement" data-my-Value = "some data"></div>
<script>
// the property of divElement is available as
console.log(divElement.dataset.myValue); // output >> "some data"
</script>
Scaling & rendering
The canvas is rendered at a ideal size (512 in this case) but the transform is set to ensure that the render fits the canvas. In this example I scale the x and y axis) the result is that the image does not have a fixed aspect.
Background
The canvas is transparent by default, but I do clear it in case you rerender to it. Anything under the canvas should be visible.
I first render the lines, then the text, clearing a space under the text to remove the lines. ctx.clearRect ensure the a canvas rect is transparent.
Drawing lines
To draw the lines you have two loops, From each item you draw a line to every other item. You don't want to draw a line more than once, so the inner loop starts at the current outer loops position + 1. This ensures a line is only rendered one.
Example
The example shows what I think you are after. I have add plenty of comments, but if you have questions do ask in the comments below.
I assumed you wanted the ordered list visible. If not use a CSS rule to hide it, it will not affect the canvas rendering.
Also if you size the canvas via CSS you may get a mismatch between canvas resolution and display size. This can result in blurred pixels, and also some high res displays will set canvas pixels to large. If this is a problem there are plenty of answers on SO on how to deal with blurred canvas rendering and hi res displays (like retina).
function drawConnected(listQ, canvasQ) {
const list = document.querySelector(listQ);
if(list === null){
console.warn("Could not find list '" + listQ +"'");
return;
}
const canvas = document.querySelector(canvasQ);
if(canvas === null){
console.warn("Could not find canvas '" + canvasQ + "'");
return;
}
const ctx = canvas.getContext("2d");
const size = 512; // Generic size. This is scaled to fit the canvas
const xScale = canvas.width / size;
const yScale = canvas.height / size;
// get settings or use dsefault
const settings = Object.assign({
fontSize : 16,
lineWidth : 128,
lineDash : 4,
textColor : "White",
lineColor : "#F90", // orange
startAngle : -Math.PI / 2,
font : "arial",
}, list.dataset);
// calculate relative sizes. convert deg to randians
const fontSize = size / Number(settings.fontSize) | 0; // (| 0 floors the value)
const lineWidth = size / Number(settings.lineWidth) | 0;
const lineDash = lineWidth * Number(settings.lineDash);
const startAngle = Number(settings.startAngle) * Math.PI / 180; // -90 deg is top of screen
// get text in all the list items
const items = [...list.querySelectorAll("li")].map(element => element.textContent);
// Set up the canvas
// Scale the canvas content to fit.
ctx.setTransform(xScale,0,0,yScale,0,0);
ctx.clearRect(0,0,size,size); // clear as canvas may have content
ctx.font = fontSize + "px " + settings.font;
// align text to render from its center
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// set the line details
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.setLineDash([lineDash, lineDash]);
// need to make room for text so calculate all the text widths
const widths = [];
for(let i = 0; i < items.length; i ++){
widths[i] = ctx.measureText(items[i]).width;
}
// use the max width to find a radius that will fit all text
const maxWidth = Math.max(...widths);
const radius = (size/2 - maxWidth * 0.6);
// this function returns the x y position on the circle for item at pos
const getPos = (pos) => {
const ang = pos / items.length * Math.PI * 2 + startAngle;
return [
Math.cos(ang) * radius + size / 2,
Math.sin(ang) * radius + size / 2
];
};
// draw lines first
ctx.strokeStyle = settings.lineColor;
ctx.beginPath();
for(let i = 0; i < items.length; i ++){
const [x,y] = getPos(i);
for(let j = i+1; j < items.length; j ++){
const [x1,y1] = getPos(j);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
}
}
ctx.stroke();
// draw text
ctx.fillStyle = settings.textColor;
for(let i = 0; i < items.length; i ++){
const [x,y] = getPos(i);
ctx.clearRect(x - widths[i] * 0.6, y - fontSize * 0.6, widths[i] * 1.2, fontSize * 1.2);
ctx.fillText(items[i],x,y);
}
// restore default transform;
ctx.setTransform(1,0,0,1,0,0);
}
// draw the diagram with selector query for ordered list and canvas
drawConnected("#poly","#polygon");
* {
margin: 0;
padding: 0;
color:white;
background:black;
}
canvas {
display: block;
}
html,
body {
font-family : arial;
width: 100%;
height: 100%;
margin: 0px;
border: 0;
display: block;
}
<canvas id="polygon" width = "256" height = "256"></canvas>
<h2>more space</h2>
<ol id="poly"
data-font-size = 16
data-line-width = 128
data-line-dash = 2
data-text-color = "white"
data-line-color = "#F80"
data-start-angle = "-90"
data-font = "arial"
>
<li>About</li>
<li>Home</li>
<li>Pictures</li>
<li>Video</li>
<li>Events</li>
<li>Map</li>
<li>Apply?</li>
<li>Recepies</li>
</ol>

Use Javascript to get Maximum font size in canvas

I am drawing a canvas that needs to be on full available screen (100% width and height). I set the width and height of canvas using javascript like this
var w = window.innerWidth;
var h = window.innerHeight;
var canvas = document.getElementById("myCanvas");
canvas.width = w;
canvas.height = h;
After setting the canvas, I need to draw some text in it which needs to get the maximum available font size. Please help me finding the way how can I display text with maximum font size. The text may contain a single character (both upper and small case) or a string and can also contain numbers. I need to do it with javascript not jquery.
Thank you for your help.
Challenge
As canvas' measureText doesn't currently support measuring height (ascent + descent) we need to do a little DOM trick to get the line-height.
As the actual height of a font - or typeface - does not necessarily (rarely) correspond with the font-size, we need a more accurate way of measuring it.
Solution
Fiddle demo
The result will be a vertical aligned text which always fit the canvas in width.
This solution will automatically get the optimal size for the font.
The first function is to wrap the measureText to support height. If the new implementation of the text metrics isn't available then use DOM:
function textMetrics(ctx, txt) {
var tm = ctx.measureText(txt),
w = tm.width,
h, el; // height, div element
if (typeof tm.fontBoundingBoxAscent === "undefined") {
// create a div element and get height from that
el = document.createElement('div');
el.style.cssText = "position:fixed;font:" + ctx.font +
";padding:0;margin:0;left:-9999px;top:-9999px";
el.innerHTML = txt;
document.body.appendChild(el);
h = parseInt(getComputedStyle(el).getPropertyValue('height'), 10);
document.body.removeChild(el);
}
else {
// in the future ...
h = tm.fontBoundingBoxAscent + tm.fontBoundingBoxDescent;
}
return [w, h];
}
Now we can loop to find the optimal size (here not very optimized, but works for the purpose - and I wrote this just now so there might be bugs in there, one being if text is just a single char that doesn't exceed width before height).
This function takes minimum two arguments, context and the text, the others are optional such as font name (name only), tolerance [0.0, 1.0] (default 0.02 or 2%) and style (ie. bold, italic etc.):
function getOptimalSize(ctx, txt, fontName, tolerance, tolerance, style) {
tolerance = (tolerance === undefined) ? 0.02 : tolerance;
fontName = (fontName === undefined) ? 'sans-serif' : fontName;
style = (style === undefined) ? '' : style + ' ';
var w = ctx.canvas.width,
h = ctx.canvas.height,
current = h,
i = 0,
max = 100,
tm,
wl = w - w * tolerance * 0.5,
wu = w + w * tolerance * 0.5,
hl = h - h * tolerance * 0.5,
hu = h + h * tolerance * 0.5;
for(; i < max; i++) {
ctx.font = style + current + 'px ' + fontName;
tm = textMetrics(ctx, txt);
if ((tm[0] >= wl && tm[0] <= wu)) {
return tm;
}
if (tm[1] > current) {
current *= (1 - tolerance);
} else {
current *= (1 + tolerance);
}
}
return [-1, -1];
}
Try this.
FIDDLE DEMO
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2 - 10;
var text = 'I M # CENTER!';
context.font = '10pt Calibri'; //try changing values 10,15,20
context.textAlign = 'center';
context.fillStyle = 'red';
context.fillText(text, x, y);
You can provide your conditions depending on the window size.Create a simple function like following:
function Manipulate_FONT(window.widht, window.height) {
if (window.widht == something && window.height == something) {
font_size = xyz //put this in ---> context.font = '10pt Calibri'
}
}

Calculate letter size in javascript

I need to calculate the exact size of the letter in javascript. The letter can have different font-size or font-family attributes, etc.
I tried to use div element for this, but this method gives only the size of the div, not letter.
<div style="display: inline; background-color: yellow; font-size: 53px; line-height:32px">A</div>
Does anybody know how to solve this issue?
This is basically not possible for the general case. Font kerning will result in variable "widths" of letters depending on the operating system, browser, etc etc, and based on which letters are next to each other. Font substitution may happen if the os+browser don't have the font you specify.
Perhaps re-asking the question with the higher-level goal you're shooting for might result in proposed other approaches to your problem that might be more fruitful?
As others have mentioned, this isn't possible to measure directly. But you can get at it in a more roundabout way: draw the letter onto a canvas and determine which pixels are filled in.
Here's a demo that does this. The meat is this function:
/**
* Draws a letter in the given font to an off-screen canvas and returns its
* size as a {w, h, descenderH} object.
* Results are cached.
*/
function measureLetter(letter, fontStyle) {
var cacheKey = letter + ' ' + fontStyle;
var cache = measureLetter.cache;
if (!cache) {
measureLetter.cache = cache = {};
}
var v = cache[cacheKey];
if (v) return v;
// Create a reasonably large off-screen <canvas>
var cnv = document.createElement('canvas');
cnv.width = '200';
cnv.height = '200';
// Draw the letter
var ctx = cnv.getContext('2d');
ctx.fillStyle = 'black';
ctx.font = fontStyle;
ctx.fillText(letter, 0.5, 100.5);
// See which pixels have been filled
var px = ctx.getImageData(0, 0, 200, 200).data;
var minX = 200, minY = 200, maxX = 0, maxY = 0;
var nonZero = 0;
for (var x = 0; x < 200; x++) {
for (var y = 0; y < 200; y++) {
var i = 4 * (x + 200 * y);
var c = px[i] + px[i + 1] + px[i + 2] + px[i + 3];
if (c === 0) continue;
nonZero++;
minX = Math.min(x, minX);
minY = Math.min(y, minY);
maxX = Math.max(x, maxX);
maxY = Math.max(y, maxY);
}
}
var o = {w: maxX - minX, h: maxY - minY, descenderH: maxY - 100};
cache[cacheKey] = o;
return o;
}
Note that this is sensitive to antialiasing—the results might be off by a pixel.
#Irongaze.com is right that your fonts, depending on conditions, will have varying actual sizes.
If you want to calibrate for a specific letter, I believe element.getBoundingClientRect() will give you useful coordinates. Be sure to fully reset the container wich you are using as a control box. Mind that on different systems you might get different results.
jsFiddle
Please note that this will not give you the size of the actual visible part of the letter, but the size of the container it determines. line-height for example, will not change the actual letter size, but it will affect other letters' positioning. Be aware of that.
It will help us if you describe the problem you are trying to solve. There might be better solutions.

How can you find the height of text on an HTML canvas?

The spec has a context.measureText(text) function that will tell you how much width it would require to print that text, but I can't find a way to find out how tall it is. I know it's based on the font, but I don't know to convert a font string to a text height.
Browsers are beginning to support advanced text metrics, which will make this task trivial when it's widely supported:
let metrics = ctx.measureText(text);
let fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
fontHeight gets you the bounding box height that is constant regardless of the string being rendered. actualHeight is specific to the string being rendered.
Spec: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent and the sections just below it.
Support status (20-Aug-2017):
Chrome has it behind a flag (https://bugs.chromium.org/p/chromium/issues/detail?id=277215).
Firefox has it in development (https://bugzilla.mozilla.org/show_bug.cgi?id=1102584).
Edge has no support (https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/30922861-advanced-canvas-textmetrics).
node-canvas (node.js module), mostly supported (https://github.com/Automattic/node-canvas/wiki/Compatibility-Status).
UPDATE - for an example of this working, I used this technique in the Carota editor.
Following on from ellisbben's answer, here is an enhanced version to get the ascent and descent from the baseline, i.e. same as tmAscent and tmDescent returned by Win32's GetTextMetric API. This is needed if you want to do a word-wrapped run of text with spans in different fonts/sizes.
The above image was generated on a canvas in Safari, red being the top line where the canvas was told to draw the text, green being the baseline and blue being the bottom (so red to blue is the full height).
Using jQuery for succinctness:
var getTextHeight = function(font) {
var text = $('<span>Hg</span>').css({ fontFamily: font });
var block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>');
var div = $('<div></div>');
div.append(text, block);
var body = $('body');
body.append(div);
try {
var result = {};
block.css({ verticalAlign: 'baseline' });
result.ascent = block.offset().top - text.offset().top;
block.css({ verticalAlign: 'bottom' });
result.height = block.offset().top - text.offset().top;
result.descent = result.height - result.ascent;
} finally {
div.remove();
}
return result;
};
In addition to a text element, I add a div with display: inline-block so I can set its vertical-align style, and then find out where the browser has put it.
So you get back an object with ascent, descent and height (which is just ascent + descent for convenience). To test it, it's worth having a function that draws a horizontal line:
var testLine = function(ctx, x, y, len, style) {
ctx.strokeStyle = style;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + len, y);
ctx.closePath();
ctx.stroke();
};
Then you can see how the text is positioned on the canvas relative to the top, baseline and bottom:
var font = '36pt Times';
var message = 'Big Text';
ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top'; // important!
ctx.font = font;
ctx.fillText(message, x, y);
// Canvas can tell us the width
var w = ctx.measureText(message).width;
// New function gets the other info we need
var h = getTextHeight(font);
testLine(ctx, x, y, w, 'red');
testLine(ctx, x, y + h.ascent, w, 'green');
testLine(ctx, x, y + h.height, w, 'blue');
You can get a very close approximation of the vertical height by checking the length of a capital M.
ctx.font = 'bold 10px Arial';
lineHeight = ctx.measureText('M').width;
The canvas spec doesn't give us a method for measuring the height of a string. However, you can set the size of your text in pixels and you can usually figure out what the vertical bounds are relatively easily.
If you need something more precise then you could throw text onto the canvas and then get pixel data and figure out how many pixels are used vertically. This would be relatively simple, but not very efficient. You could do something like this (it works, but draws some text onto your canvas that you would want to remove):
function measureTextHeight(ctx, left, top, width, height) {
// Draw the text in the specified area
ctx.save();
ctx.translate(left, top + Math.round(height * 0.8));
ctx.mozDrawText('gM'); // This seems like tall text... Doesn't it?
ctx.restore();
// Get the pixel data from the canvas
var data = ctx.getImageData(left, top, width, height).data,
first = false,
last = false,
r = height,
c = 0;
// Find the last line with a non-white pixel
while(!last && r) {
r--;
for(c = 0; c < width; c++) {
if(data[r * width * 4 + c * 4 + 3]) {
last = r;
break;
}
}
}
// Find the first line with a non-white pixel
while(r) {
r--;
for(c = 0; c < width; c++) {
if(data[r * width * 4 + c * 4 + 3]) {
first = r;
break;
}
}
// If we've got it then return the height
if(first != r) return last - first;
}
// We screwed something up... What do you expect from free code?
return 0;
}
// Set the font
context.mozTextStyle = '32px Arial';
// Specify a context and a rect that is safe to draw in when calling measureTextHeight
var height = measureTextHeight(context, 0, 0, 50, 50);
console.log(height);
For Bespin they do fake a height by measuring the width of a lowercase 'm'... I don't know how this is used, and I would not recommend this method. Here is the relevant Bespin method:
var fixCanvas = function(ctx) {
// upgrade Firefox 3.0.x text rendering to HTML 5 standard
if (!ctx.fillText && ctx.mozDrawText) {
ctx.fillText = function(textToDraw, x, y, maxWidth) {
ctx.translate(x, y);
ctx.mozTextStyle = ctx.font;
ctx.mozDrawText(textToDraw);
ctx.translate(-x, -y);
}
}
if (!ctx.measureText && ctx.mozMeasureText) {
ctx.measureText = function(text) {
ctx.mozTextStyle = ctx.font;
var width = ctx.mozMeasureText(text);
return { width: width };
}
}
if (ctx.measureText && !ctx.html5MeasureText) {
ctx.html5MeasureText = ctx.measureText;
ctx.measureText = function(text) {
var textMetrics = ctx.html5MeasureText(text);
// fake it 'til you make it
textMetrics.ascent = ctx.html5MeasureText("m").width;
return textMetrics;
}
}
// for other browsers
if (!ctx.fillText) {
ctx.fillText = function() {}
}
if (!ctx.measureText) {
ctx.measureText = function() { return 10; }
}
};
EDIT: Are you using canvas transforms? If so, you'll have to track the transformation matrix. The following method should measure the height of text with the initial transform.
EDIT #2: Oddly the code below does not produce correct answers when I run it on this StackOverflow page; it's entirely possible that the presence of some style rules could break this function.
The canvas uses fonts as defined by CSS, so in theory we can just add an appropriately styled chunk of text to the document and measure its height. I think this is significantly easier than rendering text and then checking pixel data and it should also respect ascenders and descenders. Check out the following:
var determineFontHeight = function(fontStyle) {
var body = document.getElementsByTagName("body")[0];
var dummy = document.createElement("div");
var dummyText = document.createTextNode("M");
dummy.appendChild(dummyText);
dummy.setAttribute("style", fontStyle);
body.appendChild(dummy);
var result = dummy.offsetHeight;
body.removeChild(dummy);
return result;
};
//A little test...
var exampleFamilies = ["Helvetica", "Verdana", "Times New Roman", "Courier New"];
var exampleSizes = [8, 10, 12, 16, 24, 36, 48, 96];
for(var i = 0; i < exampleFamilies.length; i++) {
var family = exampleFamilies[i];
for(var j = 0; j < exampleSizes.length; j++) {
var size = exampleSizes[j] + "pt";
var style = "font-family: " + family + "; font-size: " + size + ";";
var pixelHeight = determineFontHeight(style);
console.log(family + " " + size + " ==> " + pixelHeight + " pixels high.");
}
}
You'll have to make sure you get the font style correct on the DOM element that you measure the height of but that's pretty straightforward; really you should use something like
var canvas = /* ... */
var context = canvas.getContext("2d");
var canvasFont = " ... ";
var fontHeight = determineFontHeight("font: " + canvasFont + ";");
context.font = canvasFont;
/*
do your stuff with your font and its height here.
*/
As JJ Stiff suggests, you can add your text to a span and then measure the offsetHeight of the span.
var d = document.createElement("span");
d.font = "20px arial";
d.textContent = "Hello world!";
document.body.appendChild(d);
var emHeight = d.offsetHeight;
document.body.removeChild(d);
As shown on HTML5Rocks
Isn't the height of the text in pixels equal to the font size (in pts) if you define the font using context.font ?
I solved this problem straitforward - using pixel manipulation.
Here is graphical answer:
Here is code:
function textHeight (text, font) {
var fontDraw = document.createElement("canvas");
var height = 100;
var width = 100;
// here we expect that font size will be less canvas geometry
fontDraw.setAttribute("height", height);
fontDraw.setAttribute("width", width);
var ctx = fontDraw.getContext('2d');
// black is default
ctx.fillRect(0, 0, width, height);
ctx.textBaseline = 'top';
ctx.fillStyle = 'white';
ctx.font = font;
ctx.fillText(text/*'Eg'*/, 0, 0);
var pixels = ctx.getImageData(0, 0, width, height).data;
// row numbers where we first find letter end where it ends
var start = -1;
var end = -1;
for (var row = 0; row < height; row++) {
for (var column = 0; column < width; column++) {
var index = (row * width + column) * 4;
// if pixel is not white (background color)
if (pixels[index] == 0) {
// we havent met white (font color) pixel
// on the row and the letters was detected
if (column == width - 1 && start != -1) {
end = row;
row = height;
break;
}
continue;
}
else {
// we find top of letter
if (start == -1) {
start = row;
}
// ..letters body
break;
}
}
}
/*
document.body.appendChild(fontDraw);
fontDraw.style.pixelLeft = 400;
fontDraw.style.pixelTop = 400;
fontDraw.style.position = "absolute";
*/
return end - start;
}
Just to add to Daniel's answer (which is great! and absolutely right!), version without JQuery:
function objOff(obj)
{
var currleft = currtop = 0;
if( obj.offsetParent )
{ do { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
while( obj = obj.offsetParent ); }
else { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
return [currleft,currtop];
}
function FontMetric(fontName,fontSize)
{
var text = document.createElement("span");
text.style.fontFamily = fontName;
text.style.fontSize = fontSize + "px";
text.innerHTML = "ABCjgq|";
// if you will use some weird fonts, like handwriting or symbols, then you need to edit this test string for chars that will have most extreme accend/descend values
var block = document.createElement("div");
block.style.display = "inline-block";
block.style.width = "1px";
block.style.height = "0px";
var div = document.createElement("div");
div.appendChild(text);
div.appendChild(block);
// this test div must be visible otherwise offsetLeft/offsetTop will return 0
// but still let's try to avoid any potential glitches in various browsers
// by making it's height 0px, and overflow hidden
div.style.height = "0px";
div.style.overflow = "hidden";
// I tried without adding it to body - won't work. So we gotta do this one.
document.body.appendChild(div);
block.style.verticalAlign = "baseline";
var bp = objOff(block);
var tp = objOff(text);
var taccent = bp[1] - tp[1];
block.style.verticalAlign = "bottom";
bp = objOff(block);
tp = objOff(text);
var theight = bp[1] - tp[1];
var tdescent = theight - taccent;
// now take it off :-)
document.body.removeChild(div);
// return text accent, descent and total height
return [taccent,theight,tdescent];
}
I've just tested the code above and works great on latest Chrome, FF and Safari on Mac.
EDIT: I have added font size as well and tested with webfont instead of system font - works awesome.
one line answer
var height = parseInt(ctx.font) * 1.2;
CSS "line-height: normal" is between 1 and 1.2
read here for more info
I'm kind of shocked that there are no correct answers here. There is no need to make an estimate or a guess. Also, the font-size is not the actual size of the bounding box of the font. The font height depends on whether you have ascenders and descenders.
To calculate it, use ctx.measureText() and add together the actualBoundingBoxAscent and the actualBoundingBoxDescent. That'll give you the actual size. You can also add together the font* versions to get the size that is used to calculate things like element height, but isn't strictly the height of the actual used space for the font.
const text = 'Hello World';
const canvas = document.querySelector('canvas');
canvas.width = 500;
canvas.height = 200;
const ctx = canvas.getContext('2d');
const fontSize = 100;
ctx.font = `${fontSize}px Arial, Helvetica, sans-serif`;
// top is critical to the fillText() calculation
// you can use other positions, but you need to adjust the calculation
ctx.textBaseline = 'top';
ctx.textAlign = 'center';
const metrics = ctx.measureText(text);
const width = metrics.width;
const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
// fallback to using fontSize if fontBoundingBoxAscent isn't available, like in Firefox. Should be close enough that you aren't more than a pixel off in most cases.
const fontHeight = (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent) ?? fontSize;
ctx.fillStyle = '#00F'; // blue
ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (fontHeight / 2), width, fontHeight);
ctx.fillStyle = '#0F0'; // green
ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (actualHeight / 2), width, actualHeight);
// canvas.height / 2 - actualHeight / 2 gets you to the top of
// the green box. You have to add actualBoundingBoxAscent to shift
// it just right
ctx.fillStyle = '#F00'; // red
ctx.fillText(text, canvas.width / 2, canvas.height / 2 - actualHeight / 2 + metrics.actualBoundingBoxAscent);
<canvas></canvas>
This is what I did based on some of the other answers here:
function measureText(text, font) {
const span = document.createElement('span');
span.appendChild(document.createTextNode(text));
Object.assign(span.style, {
font: font,
margin: '0',
padding: '0',
border: '0',
whiteSpace: 'nowrap'
});
document.body.appendChild(span);
const {width, height} = span.getBoundingClientRect();
span.remove();
return {width, height};
}
var font = "italic 100px Georgia";
var text = "abc this is a test";
console.log(measureText(text, font));
I'm writing a terminal emulator so I needed to draw rectangles around characters.
var size = 10
var lineHeight = 1.2 // CSS "line-height: normal" is between 1 and 1.2
context.font = size+'px/'+lineHeight+'em monospace'
width = context.measureText('m').width
height = size * lineHeight
Obviously if you want the exact amount of space the character takes up, it won't help. But it'll give you a good approximation for certain uses.
I have implemented a nice library for measuring the exact height and width of text using HTML canvas. This should do what you want.
https://github.com/ChrisBellew/text-measurer.js
Here is a simple function. No library needed.
I wrote this function to get the top and bottom bounds relative to baseline. If textBaseline is set to alphabetic. What it does is it creates another canvas, and then draws there, and then finds the top most and bottom most non blank pixel. And that is the top and bottom bounds. It returns it as relative, so if height is 20px, and there is nothing below the baseline, then the top bound is -20.
You must supply characters to it. Otherwise it will give you 0 height and 0 width, obviously.
Usage:
alert(measureHeight('40px serif', 40, 'rg').height)
Here is the function:
function measureHeight(aFont, aSize, aChars, aOptions={}) {
// if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
// if you dont pass in a width to aOptions, it will return it to you in the return object
// the returned width is Math.ceil'ed
console.error('aChars: "' + aChars + '"');
var defaultOptions = {
width: undefined, // if you specify a width then i wont have to use measureText to get the width
canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
range: 3
};
aOptions.range = aOptions.range || 3; // multiples the aSize by this much
if (aChars === '') {
// no characters, so obviously everything is 0
return {
relativeBot: 0,
relativeTop: 0,
height: 0,
width: 0
};
// otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
}
// validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined
var can;
var ctx;
if (!aOptions.canAndCtx) {
can = document.createElement('canvas');;
can.mozOpaque = 'true'; // improved performanceo on firefox i guess
ctx = can.getContext('2d');
// can.style.position = 'absolute';
// can.style.zIndex = 10000;
// can.style.left = 0;
// can.style.top = 0;
// document.body.appendChild(can);
} else {
can = aOptions.canAndCtx.can;
ctx = aOptions.canAndCtx.ctx;
}
var w = aOptions.width;
if (!w) {
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left';
ctx.font = aFont;
w = ctx.measureText(aChars).width;
}
w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number
// must set width/height, as it wont paint outside of the bounds
can.width = w;
can.height = aSize * aOptions.range;
ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left';
ctx.fillStyle = 'white';
console.log('w:', w);
var avgOfRange = (aOptions.range + 1) / 2;
var yBaseline = Math.ceil(aSize * avgOfRange);
console.log('yBaseline:', yBaseline);
ctx.fillText(aChars, 0, yBaseline);
var yEnd = aSize * aOptions.range;
var data = ctx.getImageData(0, 0, w, yEnd).data;
// console.log('data:', data)
var botBound = -1;
var topBound = -1;
// measureHeightY:
for (y=0; y<=yEnd; y++) {
for (var x = 0; x < w; x += 1) {
var n = 4 * (w * y + x);
var r = data[n];
var g = data[n + 1];
var b = data[n + 2];
// var a = data[n + 3];
if (r+g+b > 0) { // non black px found
if (topBound == -1) {
topBound = y;
}
botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
break;
}
}
}
return {
relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
height: (botBound - topBound) + 1,
width: w// EDIT: comma has been added to fix old broken code.
};
}
relativeBot, relativeTop, and height are the useful things in the return object.
Here is example usage:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script>
function measureHeight(aFont, aSize, aChars, aOptions={}) {
// if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
// if you dont pass in a width to aOptions, it will return it to you in the return object
// the returned width is Math.ceil'ed
console.error('aChars: "' + aChars + '"');
var defaultOptions = {
width: undefined, // if you specify a width then i wont have to use measureText to get the width
canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
range: 3
};
aOptions.range = aOptions.range || 3; // multiples the aSize by this much
if (aChars === '') {
// no characters, so obviously everything is 0
return {
relativeBot: 0,
relativeTop: 0,
height: 0,
width: 0
};
// otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
}
// validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined
var can;
var ctx;
if (!aOptions.canAndCtx) {
can = document.createElement('canvas');;
can.mozOpaque = 'true'; // improved performanceo on firefox i guess
ctx = can.getContext('2d');
// can.style.position = 'absolute';
// can.style.zIndex = 10000;
// can.style.left = 0;
// can.style.top = 0;
// document.body.appendChild(can);
} else {
can = aOptions.canAndCtx.can;
ctx = aOptions.canAndCtx.ctx;
}
var w = aOptions.width;
if (!w) {
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left';
ctx.font = aFont;
w = ctx.measureText(aChars).width;
}
w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number
// must set width/height, as it wont paint outside of the bounds
can.width = w;
can.height = aSize * aOptions.range;
ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left';
ctx.fillStyle = 'white';
console.log('w:', w);
var avgOfRange = (aOptions.range + 1) / 2;
var yBaseline = Math.ceil(aSize * avgOfRange);
console.log('yBaseline:', yBaseline);
ctx.fillText(aChars, 0, yBaseline);
var yEnd = aSize * aOptions.range;
var data = ctx.getImageData(0, 0, w, yEnd).data;
// console.log('data:', data)
var botBound = -1;
var topBound = -1;
// measureHeightY:
for (y=0; y<=yEnd; y++) {
for (var x = 0; x < w; x += 1) {
var n = 4 * (w * y + x);
var r = data[n];
var g = data[n + 1];
var b = data[n + 2];
// var a = data[n + 3];
if (r+g+b > 0) { // non black px found
if (topBound == -1) {
topBound = y;
}
botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
break;
}
}
}
return {
relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
height: (botBound - topBound) + 1,
width: w
};
}
</script>
</head>
<body style="background-color:steelblue;">
<input type="button" value="reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg', {canAndCtx:{can:document.getElementById('can'), ctx:document.getElementById('can').getContext('2d')}}).height)">
<input type="button" value="dont reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg').height)">
<canvas id="can"></canvas>
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
</body>
</html>
The relativeBot and relativeTop are what you see in this image here:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text
Funny that TextMetrics has width only and no height:
http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#textmetrics
Can you use a Span as on this example?
http://mudcu.be/journal/2011/01/html5-typographic-metrics/#alignFix
First of all, you need to set the height of a font size, and then according to the value of the font height to determine the current height of your text is how much, cross-text lines, of course, the same height of the font need to accumulate, if the text does not exceed the largest text box Height, all show, otherwise, only show the text within the box text. High values need your own definition. The larger the preset height, the greater the height of the text that needs to be displayed and intercepted.
After the effect is processed(solve)
Before the effect is processed(
unsolved)
AutoWrappedText.auto_wrap = function(ctx, text, maxWidth, maxHeight) {
var words = text.split("");
var lines = [];
var currentLine = words[0];
var total_height = 0;
for (var i = 1; i < words.length; i++) {
var word = words[i];
var width = ctx.measureText(currentLine + word).width;
if (width < maxWidth) {
currentLine += word;
} else {
lines.push(currentLine);
currentLine = word;
// TODO dynamically get font size
total_height += 25;
if (total_height >= maxHeight) {
break
}
}
}
if (total_height + 25 < maxHeight) {
lines.push(currentLine);
} else {
lines[lines.length - 1] += "…";
}
return lines;};
I found that JUST FOR ARIAL the simplest, fastest and accuratest way to find height of bounding box is to use the width of certain letters. If you plan to use a certain font without letting user to choose one different, you can do a little research to find the right letter that do the job for that font.
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="700" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = "100px Arial";
var txt = "Hello guys!"
var Hsup=ctx.measureText("H").width;
var Hbox=ctx.measureText("W").width;
var W=ctx.measureText(txt).width;
var W2=ctx.measureText(txt.substr(0, 9)).width;
ctx.fillText(txt, 10, 100);
ctx.rect(10,100, W, -Hsup);
ctx.rect(10,100+Hbox-Hsup, W2, -Hbox);
ctx.stroke();
</script>
<p><strong>Note:</strong> The canvas tag is not supported in Internet
Explorer 8 and earlier versions.</p>
</body>
</html>
setting the font size might not be practical though, since setting
ctx.font = ''
will use the one defined by CSS as well as any embedded font tags. If you use the CSS font you have no idea what the height is from a programmatic way, using the measureText method, which is very short sighted. On another note though, IE8 DOES return the width and height.
This works 1) for multiline text as well 2) and even in IE9!
<div class="measureText" id="measureText">
</div>
.measureText {
margin: 0;
padding: 0;
border: 0;
font-family: Arial;
position: fixed;
visibility: hidden;
height: auto;
width: auto;
white-space: pre-wrap;
line-height: 100%;
}
function getTextFieldMeasure(fontSize, value) {
const div = document.getElementById("measureText");
// returns wrong result for multiline text with last line empty
let arr = value.split('\n');
if (arr[arr.length-1].length == 0) {
value += '.';
}
div.innerText = value;
div.style['font-size']= fontSize + "px";
let rect = div.getBoundingClientRect();
return {width: rect.width, height: rect.height};
};
I know this is an old answered question, but for future reference I'd like to add a short, minimal, JS-only (no jquery) solution I believe people can benefit from:
var measureTextHeight = function(fontFamily, fontSize)
{
var text = document.createElement('span');
text.style.fontFamily = fontFamily;
text.style.fontSize = fontSize + "px";
text.textContent = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
document.body.appendChild(text);
var result = text.getBoundingClientRect().height;
document.body.removeChild(text);
return result;
};
I monkey patched CanvasRenderingContext2D.measureText() in one of my project to include actual height of the text. It's written in vanilla JS and has zero dependencies.
/*
* Monkeypatch CanvasRenderingContext2D.measureText() to include actual height of the text
*/
; (function (global) {
"use strict";
var _measureText = global.CanvasRenderingContext2D.prototype.measureText;
global.CanvasRenderingContext2D.prototype.measureText = function () {
var textMetrics = _measureText.apply(this, arguments);
var _getHeight = function (text) {
var $span = global.document.createElement("span");
var spanTextNode = global.document.createTextNode(text);
$span.appendChild(spanTextNode);
$span.setAttribute("style", `font: ${this.font}`);
var $div = global.document.createElement("div");
$div.setAttribute("style", "display: inline-block; width: 1px; height: 0; vertical-align: super;");
var $parentDiv = global.document.createElement("div");
$parentDiv.appendChild($span);
$parentDiv.appendChild($div);
var $body = global.document.getElementsByTagName("body")[0];
$body.appendChild($parentDiv);
var divRect = $div.getBoundingClientRect();
var spanRect = $span.getBoundingClientRect();
var result = {};
$div.style.verticalAlign = "baseline";
result.ascent = divRect.top - spanRect.top;
$div.style.verticalAlign = "bottom";
result.height = divRect.top - spanRect.top;
result.descent = result.height - result.ascent;
$body.removeChild($parentDiv);
return result.height - result.descent;
}.bind(this);
var height = _getHeight(arguments[0]);
global.Object.defineProperty(textMetrics, "height", { value: height });
return textMetrics;
};
})(window);
You can use it like this
ctx.font = "bold 64px Verdana, sans-serif"; // Automatically considers it as part of height calculation
var textMetrics = ctx.measureText("Foobar");
var textHeight = textMetrics.height;
parseInt(ctx.font, 10)
e.g.
let text_height = parseInt(ctx.font, 10)
e.g. returns 35
In normal situations the following should work:
var can = CanvasElement.getContext('2d'); //get context
var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable
This is madding... The height of the text is the font size.. Didn't any of you read the documentation?
context.font = "22px arial";
this will set the height to 22px.
the only reason there is a..
context.measureText(string).width
is because that the width of the string can not be determined unless it knows the string you want the width of but for all the strings drawn with the font.. the height will be 22px.
if you use another measurement than px then the height will still be the same but with that measurement so at most all you would have to do is convert the measurement.
Approximate solution:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = "100px Arial";
var txt = "Hello guys!"
var wt = ctx.measureText(txt).width;
var height = wt / txt.length;
This will be accurate result in monospaced font.

Categories