<canvas id="myCanvas" width="2000" height="1800"></canvas>
<div id='control'>
<p>Pink text is on a DIV, the blue text is on a canvas - use slider to confirm match.</p>
<input id='canvasPos' type="range" min="0" max="200" value="10">
<label for='fontSize'>Font Size (Pts)</label>
<select id='fontSize'>
<option >200</option>
<option>180</option>
<option>160</option>
<option >140</option>
<option>120</option>
<option selected>100</option>
<option>75</option>
<option>66</option>
<option >48</option>
<option >32</option>
<option>24</option>
<option>16</option>
<option>12</option>
<option>10</option>
<option>6</option>
</select>
<select id='fontName'>
<option selected value='Arial'>Arial</option>
<option value='Verdana'>Verdana</option>
<option value='Tahoma'>Tahoma</option>
<option value='Calibri'>Calibri</option>
<option value='Trebuchet MS'>Trebuchet MS</option>
<option value='Times New Roman'>Times New Roman</option>
<option value='Georgia'>Georgia</option>
<option value='Garamond'>Garamond</option>
<option value='Courier New'>Courier New</option>
<option value='Brush Script MT'>Brush Script MT</option>
</select>
<label for="usegap">Use gap
<input type="checkbox" id="usegap" value = "yes" checked="checked">
</label>
</div>
<div id='i_nfo'></div>
<script src="http://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js"></script>
body {
margin: 14px;
padding: 10px;
font: 12pt Verdana, Arial, sans-serif;
}
canvas {
border: none;
position: absolute;
top: 110px;
left: 30px;
}
#textMeasure{
border: none;
top: 150px;
left: 150px;
padding: 0;
color: magenta;
}
#control {
position: absolute;
top: 0; left: 0;
padding: 10px;
z-index: 100;
}
#info {
padding-left: 10px;
}
let canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
text = 'ÁfgMijlvy<br />ÁfgMijlvy<br />ÁfgMijlvy',
texts = text.split("<br />"),
fontName = 'Arial',
fontSizePt = $('#fontSize').val(),
x = 0,
y = 140,
w = 0,
yPos = 0,
metrics = measureLineHeight('x', '', fontName, fontSizePt);
WebFont.load({
google: {
families: [
'Anton',
'Bad Script',
'Catamaran',
'Droid Sans',
'Droid Serif',
'Hammersmith One',
'Hanalei',
'IM Fell Double Pica',
'Lobster',
'Merriweather',
'Noto Sans JP',
'Open Sans',
'Pangolin',
'Roboto',
'Shadows Into Light',
'Stalinist One',
'Ubuntu',
'Ultra'
]
},
fontloading: function(familyName, fvd) {
console.log('Loading font [' + familyName + ']')
},
fontactive: function(familyName, fvd) {
console.log('Loaded font [' + familyName + ']')
$('#fontName').append('<option value="' + familyName + '">' + familyName + '</option>');
},
});
function doMeasure(){
console.clear();
context.clearRect(0,0,canvas.width, canvas.height);
$('#textMeasure').remove();
fontName = $('#fontName').val();
let useGap = $('#usegap').prop('checked')
console.log('Using font [' + fontName + ']');
fontSizePt = $('#fontSize').val();
yPos = 40;
metrics = measureLineHeight('x', '', fontName, fontSizePt);
$('#info').html("Font: " + fontName + ' @ ' + fontSizePt + 'pt / ' + metrics.cssFontSizePx + 'px'
+ ' Asc / Desc: '
+ ' Font ' + Math.round(metrics.fontAscent * 100) / 100 + ' / ' + Math.round( metrics.fontDescent * 100) / 100
+ ' Actual ' +Math.round( metrics.actualAscent * 100) / 100 + ' / ' + Math.round( metrics.actualDescent * 100) / 100
+ ' Line gap ' + Math.round( metrics.lineGap * 100) / 100
+ ' Line height ' + Math.round( metrics.lineHeight * 100) / 100 + 'px'
+ ' Ratio ' + Math.round( (metrics.lineHeight / metrics.cssFontSizePx ) * 100) / 100
)
for (let i = 0; i < texts.length; i++){
let x = 120;
let width = canvasTextMeasure(texts[i], fontName, metrics.cssFontSizePx);
if (useGap){
yPos = yPos + metrics.lineGapTop + metrics.fontAscent;
}
else {
yPos = yPos + metrics.fontAscent;
}
textOut({x:x, y: yPos}, texts[i], fontName, metrics.cssFontSizePx, 'cyan');
// draw lines at ascent, descent and baseline
testLine(context, x, yPos - metrics.fontAscent, width, 0, 'cyan'); // ascender line
testLine(context, x, yPos + metrics.fontDescent, width, 0, 'cyan'); // descender line
testLine(context, x, yPos, width, 0, 'red'); // base line - haha we know where the base line is because its where we placed it !
// Labels
if (i === 0){
let txt = "Font: " + fontName + ' @ ' + fontSizePt + 'pt / ' + metrics.cssFontSizePx
+ 'px computed line height ' + Math.round( metrics.lineHeight * 100) / 100
+ 'px, ratio ' + Math.round(( metrics.lineHeight / metrics.cssFontSizePx ) * 100) / 100
textOut({x: x, y: 20}, txt, 'Verdana', 12, 'black')
x = 5;
let baseLine = yPos;
testLine(context, x - 4, yPos - metrics.actualAscent, 100, 0, 'cyan'); // actualAscent line
textOut({x: x, y: yPos - metrics.actualAscent + 20}, 'Actual ascent', 'Verdana', 12, 'cyan')
if (metrics.actualDescent !== 0){
testLine(context, x - 4, yPos + metrics.actualDescent, 100, 0, 'cyan'); // actualDescent line
textOut({x: x, y: yPos + metrics.actualDescent + 20}, 'Actual descent', 'Verdana', 12, 'cyan')
}
testLine(context, x - 4,baseLine, 100, 0, 'red'); // baseLine
textOut({x: x, y: baseLine - 10}, 'Baseline', 'Verdana', 12, 'red')
testLine(context, x - 4, yPos - metrics.fontAscent, 100, 0, 'cyan'); // font ascender line
textOut({x: x, y: yPos - metrics.fontAscent + 20}, 'Font ascent', 'Verdana', 12, 'cyan')
testLine(context, x - 4, yPos + metrics.fontDescent, 100, 0, 'cyan'); // font descender line
textOut({x: x, y: yPos + metrics.fontDescent - 10}, 'Font Descent', 'Verdana', 12, 'cyan')
if (metrics.lineGap !== 0){
testLine(context, x - 4, yPos + metrics.fontDescent + (2 * metrics.lineGap), 100, 0, 'cyan'); // line gap line
textOut({x: x, y: yPos + metrics.fontDescent + metrics.lineGap + 20}, 'Line gap', 'Verdana', 12, 'cyan')
}
// line height
testLine(context, 1, yPos - metrics.fontAscent, 0, metrics.lineHeight, 'cyan');
}
// Move y pos for next line out.
if (useGap){
yPos = yPos + metrics.fontDescent + metrics.lineGapBottom ;
}
else {
yPos = yPos + metrics.fontDescent ;
}
}
HTMLTextDraw(text, '', fontName, fontSizePt);
}
/* Measure the height of a line using a mixture of canvas text metrics and css sniffing.
*/
function measureLineHeight(text, bold, fontName, FontSizePt){
// get the CSS metrics.
// NB: NO CSS lineHeight value !
let div = document.createElement('DIV');
div.id = '__textMeasure';
div.innerHTML = text;
div.style.position = 'absolute';
div.style.top = '-500px';
div.style.left = '0';
div.style.fontFamily = fontName;
div.style.fontWeight = bold ? 'bold' : 'normal';
div.style.fontSize = FontSizePt + 'pt';
document.body.appendChild(div);
let cssSize = {width: div.offsetWidth, height: div.offsetHeight},
cssInfo = window.getComputedStyle(div, null),
fontSizePx = parseFloat(cssInfo['fontSize']);
document.body.removeChild(div);
// get the canvas metrics.
let canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d');
context.font = fontSizePx + 'px ' + fontName;
context.textAlign = 'left';
context.fillStyle = 'blue';
context.textBaseline = 'baseline';
let metrics = context.measureText(text),
lineGap = (cssSize.height - (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent)),
advMetrics = {
width: metrics.width,
cssHeight: cssSize.height,
cssFontSizePx: fontSizePx,
fontAscent: metrics.fontBoundingBoxAscent,
fontDescent: metrics.fontBoundingBoxDescent,
actualAscent: metrics.actualBoundingBoxAscent,
actualDescent: metrics.actualBoundingBoxDescent,
lineHeight: cssSize.height,
lineGap: lineGap,
lineGapTop: lineGap / 2,
lineGapBottom: lineGap / 2
};
return advMetrics;
}
// Draw text on the canvas
function textOut(pos, text, fontName, fontSizePx, color){
let canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d');
context.font = fontSizePx + 'px ' + fontName;
context.textAlign = 'left';
context.fillStyle = color;
context.textBaseline = 'baseline';
context.fillText(text, pos.x, pos.y);
}
// Simple canvas text measure.
function canvasTextMeasure(text, fontName, fontSizePx){
let method = 'CVS: ';
let canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d');
context.font = fontSizePx + 'px ' + fontName;
context.textAlign = 'left';
context.fillStyle = 'blue';
context.textBaseline = 'baseline';
return context.measureText(text).width;
}
function HTMLTextDraw(text, bold, font, size){
let div = document.createElement('DIV');
div.id = 'textMeasure';
div.innerHTML = text;
div.style.position = 'absolute';
div.style.fontFamily = font;
div.style.fontWeight = bold ? 'bold' : 'normal';
div.style.fontSize = size + 'pt';
document.body.appendChild(div);
}
// Draw a line on the canvas
function testLine(ctx, x, y, len, ht, style) {
ctx.strokeStyle = style;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + len, y + ht);
ctx.closePath();
ctx.stroke();
};
// move the canvas element to show how close a match
function go(){
$('#myCanvas').css({
'margin-left': parseInt($('#canvasPos').val())
})
}
$('#canvasPos').on('input', function(){
go()
})
$('#fontSize,#fontName,#usegap').on('change', function(){
doMeasure()
})
go();
doMeasure();
This Pen doesn't use any external CSS resources.