3

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.

titles on grid

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? enter image description here

  • Had to rectify my point, the problem could be related to a **margin-collapsing** issue and not to the font/line-height as stated – DaniP Jan 25 '18 at 16:49
  • @DaniP If I remove the line-height:1.25em; everything will be line-height:20px. That not what I want. With my method I have well 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;} –  Jan 25 '18 at 16:50
  • @DaniP I don't know what is a margin-collapsing. I 'm checking that. Thanks –  Jan 25 '18 at 16:51
  • Yep I think is more a 'margin' issue .... please check this codepen where I have removed margin for p and h elements https://codepen.io/anon/pen/ypdxBo ... it fits perfect – DaniP Jan 25 '18 at 17:00
  • Big thanks @DaniP. I get the same result on my side... But you also have the same problem. your h1->h4 are not touching the baseline –  Jan 25 '18 at 17:04
  • @DaniP that should give something like this https://i1.creativecow.net/u/130031/5.jpg –  Jan 25 '18 at 17:06
  • 1
    Mmmm seems really tricky, I think CSS makes the line-height relative from the middle of upper 'M' with space on top and bottom and maybe indesign line-height comes from the bottom getting the redundant space just at the top – DaniP Jan 25 '18 at 17:23
  • You have to keep the top and bottom margins as multiples of the line-height for every item, or zero. If you make the line heights, margins and font sizes with rem or px units will be easier to avoid mistakes. The only time I had to do someting similar I didn't used top margins, just bottom margins. – miguel-svq Jan 25 '18 at 17:25
  • 1
    It's not that easy to fit also if take in care each fonts particularities about ascendants and descendants of the typo. – DaniP Jan 25 '18 at 17:31
  • Ascenders and descenders will overlay the next/prior line if needed, but will keep the line height intact. – miguel-svq Jan 25 '18 at 17:35
  • check this link, it and example of the same approach I used. Hope will help: https://www.smashingmagazine.com/2012/12/css-baseline-the-good-the-bad-and-the-ugly/ – miguel-svq Jan 25 '18 at 17:48
  • very interesting.. thanks @miguel-svq –  Jan 25 '18 at 18:55
  • Possible duplicate of [CSS \`line-height\` relative to baseline (with JS?)](https://stackoverflow.com/questions/48451054/css-line-height-relative-to-baseline-with-js) – sebilasse Jan 28 '18 at 13:20

1 Answers1

3

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

sebilasse
  • 4,278
  • 2
  • 37
  • 36
  • waooo I'm impressed ! That's a very clever approach ! –  Jan 29 '18 at 14:40
  • note: At the time of writing the https://codepen.io/sebilasse/pen/BdaPzN had an issue with google font loading. Fixed now. Might also add a typekit loader. – sebilasse Jan 31 '18 at 10:08