<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();
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js