Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <h4>
  Change font name and font size in JS and get your metrics ... <br>
  ascent<br>
  xHeight<br>
  descent<br>
</h4>
<div id="illustrationContainer"></div>
<pre id="log"></pre>
<canvas id="cvs" width="220" height="220"></canvas>
              
            
!

CSS

              
                pre, canvas {
  float: left;
  margin-right: 16px;
}
pre {
 white-space: pre-wrap;
 white-space: -moz-pre-wrap;
 white-space: -pre-wrap; 
 white-space: -o-pre-wrap;
 word-wrap: break-word;
}
canvas {
  border: 1px solid black; 
  font-style: normal; 
  font-variant-caps: normal; 
  font-weight: normal;
  line-height: normal;
  visibility: hidden;
}
#illustrationContainer {
  max-width: 664px;
}
              
            
!

JS

              
                var fontName = 'Maven Pro';
var fontSize = '59px';
(getMetrics());

function getMetrics() {
  var testtext = "Sixty Handgloves ABC";
  // 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.");
  }

  // 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);
  };

  // debug function
  var show = function(canvas, ctx, xstart, w, h, metrics)
  {
    document.body.appendChild(canvas);
    ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';

    ctx.beginPath();
    ctx.moveTo(xstart,0);
    ctx.lineTo(xstart,h);
    ctx.closePath();
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(xstart+metrics.bounds.maxx,0);
    ctx.lineTo(xstart+metrics.bounds.maxx,h);
    ctx.closePath();
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(0,h/2-metrics.ascent);
    ctx.lineTo(w,h/2-metrics.ascent);
    ctx.closePath();
    ctx.stroke(); 

    ctx.beginPath();
    ctx.moveTo(0,h/2+metrics.descent);
    ctx.lineTo(w,h/2+metrics.descent);
    ctx.closePath();
    ctx.stroke();
  }

  /**
   * 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;

    // then we 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);
    }
    
    // if we ARE dealing with whitespace, most values will just be zero.
    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;
  };
  //callback();
  var WebFontConfig = {
    google: { 
      families: [ [encodeURIComponent(fontName),'::latin'].join('') ] 
    }
  };
  var wf = document.createElement('script');
  wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
    '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
  wf.type = 'text/javascript';
  wf.async = 'true';
  var s = document.getElementsByTagName('script')[0];
  s.parentNode.insertBefore(wf, s);
  document.body.style.fontFamily = ['"'+fontName+'"', "Arial sans"].join(' ')
  var canvas = document.getElementById('cvs'),
      context = canvas.getContext("2d");

  var w=220, h=220;

  canvas.style.font = [fontSize, fontName].join(' ');
  context.font = [fontSize, fontName].join(' ');
  context.clearRect(0, 0, canvas.width, canvas.height);
  // draw bounding box and text
  var xHeight = context.measureText("x").height;
  var capHeight = context.measureText("H").height;
  var metrics = context.measureText("Hxy");
  var xStart = (w - metrics.width)/2;
  context.fontFamily = fontName;
  context.fillStyle = "#FFAF00";
  context.fillRect(xStart, h/2-metrics.ascent, metrics.bounds.maxx-metrics.bounds.minx, 1+metrics.bounds.maxy-metrics.bounds.miny);
  context.fillStyle = "#333333";
  context.fillText(testtext, xStart, h/2);
  metrics.fontsize = parseInt(metrics.fontsize);
  metrics.offset = Math.ceil((metrics.leading - metrics.height) / 2);
  metrics.width = JSON.parse(JSON.stringify(metrics.width));
  metrics.capHeight = capHeight;
  metrics.xHeight = xHeight - 1;
  metrics.ascender = metrics.capHeight - metrics.xHeight;
  metrics.descender = metrics.descent;
  
  var myMetrics = {
    px: JSON.parse(JSON.stringify(metrics)),
    relative: {
      fontsize: 1,
      offset: (metrics.offset / metrics.fontsize),
      height: (metrics.height / metrics.fontsize),
      capHeight: (metrics.capHeight / metrics.fontsize),
      ascender: (metrics.ascender / metrics.fontsize),
      xHeight: (metrics.xHeight / metrics.fontsize),
      descender: (metrics.descender / metrics.fontsize)
    },
    descriptions: {
      ascent: 'distance above baseline',
      descent: 'distance below baseline',
      height: 'ascent + 1 for the baseline + descent',
      leading: 'distance between consecutive baselines',
      bounds: { 
        minx: 'can be negative',
        miny: 'can also be negative',
        maxx: 'not necessarily the same as metrics.width',
        maxy: 'not necessarily the same as metrics.height'
      },
      capHeight: 'height of the letter H',
      ascender: 'distance above the letter x',
      xHeight: 'height of the letter x (1ex)',
      descender: 'distance below the letter x'
    }
  }
  
  Array.prototype.slice.call(
    document.getElementsByTagName('canvas'), 0
  ).forEach(function(c, i){
    if (i > 0) document.body.removeChild(c);
  });
  
  document.getElementById('illustrationContainer').innerHTML = [
'<div style="margin:0; padding:0; position: relative; font-size:',fontSize,'; line-height: 1em; outline:1px solid black;">',
  testtext,
  '<div class="__ascender" style="position: absolute; width:100%; top:',myMetrics.relative.offset,'em; height:',myMetrics.relative.ascender,'em; background:rgba(220,0,5,.5);"></div>',
    '<div class="__xHeight" style="position: absolute; width:100%; top:',myMetrics.relative.offset + myMetrics.relative.ascender,'em; height:',myMetrics.relative.xHeight,'em; background:rgba(149,204,13,.5);"></div>',
    '<div class="__xHeight" style="position: absolute; width:100%; top:',myMetrics.relative.offset + myMetrics.relative.ascender + myMetrics.relative.xHeight,'em; height:',myMetrics.relative.descender,'em; background:rgba(13,126,204,.5);"></div>',
  '</div>'
  ].join('');
  
  
  document.getElementById('illustrationContainer').setAttribute("style", "font-family:" + fontName);
  
  myMetrics.illustrationMarkup = document.getElementById('illustrationContainer').innerHTML;
  var logstring = ["/* metrics for", fontName, 
                   "*/\nvar metrics =", 
                   JSON.stringify(myMetrics, null, '  ')].join(' ');
  document.getElementById('log').textContent = logstring;
  
  // Restore CanvasRenderingContext2D, needed for reusing this function ...
  CanvasRenderingContext2D.prototype.measureText = CanvasRenderingContext2D.prototype.measureTextWidth
  return myMetrics;
}
              
            
!
999px

Console