css font-size 和 line-height 与基线不匹配

2022-01-18 00:00:00 font-size grid html css baseline

我正在尝试做一些应该非常简单的事情,但我在失败和论坛之间度过了我的一天..

I'm trying to do something that should be very simple but I've spent my day between failures and forums..

我想调整我的字体以匹配我的基线.在 indesign 中只需单击一下,但在 css 中它看起来是世界上最困难的事情..

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.

在这张图片上,我每 20 像素有一个基线.

On this image I have a baseline every 20px.

所以对于我的 <body> 我会这样做:

So for my <body> I do:

<style>
body {font-size:16px; line-height:20px;}
</style> 

一切都很完美.我的段落符合基线.

Everything works perfectly. My paragraph matchs the baseline.

但是当我编写与基线不匹配的 <h> 脚本时..我做错了什么?那应该遵循我的基线,不是吗?

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

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?

推荐答案

这有点复杂 - 你必须先测量字体(就像 InDesign 所做的那样)并计算line-height",即所谓的bottom_gap"还有一些其他的东西

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

我很确定我们可以在 JavaScript 中做一些事情..

I'm pretty sure we can do something in JavaScript..

你是对的——但是对于 Typography JS 用于计算 CSS(取决于字体指标)

You are right – but for Typography JS is used to calculate the CSS (depending on the font metrics)

这里是否演示了第一步(测量字体)https://codepen.io/sebilasse/pen/gPBQqm它只是以图形方式显示 [针对技术背景] 测量的内容

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".

这是一个可以生成这种 Typo CSS 的生成器:

https://codepen.io/sebilasse/pen/BdaPzN

要测量的函数可以基于 <canvas>,如下所示:

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

注意,您还需要一个好的reset.css"来重置浏览器边距和填充.
您点击显示 CSS",还可以使用生成的 CSS 混合多种字体:
如果它们具有不同的基本尺寸,则标准化第二个:

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;

现在重新计算 CSS2 中的每个字体

and now recalculate each font in CSS2 with

var size = size * factor;

在 https://codepen.io/sebilasse/pen/oENGev 中查看演示?editors=1100

如果涉及图像怎么办?下面的演示使用两种具有相同度量的字体加上一个额外的 JS 部分.需要为基线网格计算媒体元素,如图像:https://codepen.io/sebilasse/pen/ddopBj

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

相关文章