Lowtech font metrics

by phloe

HTML

<link rel="stylesheet" href="https://www.dr.dk/global/publik.css" />


<table>
  <caption>CSS vars</caption>
  <tbody>
    <tr>
      <th>--cap-height<span aria-hidden="true">:</span></th>
      <td><span id="vars__cap_height">0.718</span><span aria-hidden="true">;</span></td>
    </tr>
    <tr>
      <th>--x-height<span aria-hidden="true">:</span></th>
      <td><span id="vars__x_height">0.538</span><span aria-hidden="true">;</span></td>
    </tr>
  </tbody>
</table>

<div id="sample">
  <div id="sample__text">
    Hx
  </div>
</div>

<div class="measure">
  <div id="linebox">
    <span>M</span>
  </div>
  <div id="metrics" />
</div>

SCSS

html {
  font-family: "DR Publik";
	--xxx-small: 0.125;
	--xx-small: 0.25;
	--x-small: 0.375;
	--small: 0.625;
	--medium: 1;
	--large: 1.625;
	--x-large: 2.625;
	--xx-large: 4.25;
	--xxx-large: 6.875;
	--xxxx-large: 11.0625;
  
  --fg-color: #333333;
	--bg-color: #EEEEEE;
  --secondary-fg-color: #BBBBBB;
	--tertiary-fg-color: #999999;

  --horizontal-line: linear-gradient(90deg, var(--secondary-fg-color) 50%, transparent 50%) 0 0 / 0.5rem 0.0625rem repeat-x;
  --vertical-line: linear-gradient(var(--tertiary-fg-color), var(--tertiary-fg-color)) 0 0 / 0.0625rem 1rem no-repeat;
  --sample-linebox-bg-color: #D0D0D0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
  background-color: var(--bg-color);
}

body {
  max-width: 40rem;
  height: min-content;
  background: transparent;
}

.measure {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  /*
  */
}

#linebox {
  position: absolute;
  font-size: 5000px;
  line-height: 1ex;
  display: flex;
  align-items: baseline;
  background-color: yellow;
  
   &::after {
    content: "";
    visibility: hidden;
    font-size: 0;
    height: 100%;
    width: 0;
    display: grid;
    align-items: end;
  }
}

#metrics {
  position: absolute;
  overflow: hidden;
  width: 1cap;
  height: 1ex;
}

#sample {
  --size: 6;
  --padding-top: calc(3 * 1rem);
  font-size: calc(var(--size) / var(--char-height) * 1rem);
  height: calc(var(--size) * 1rem);
  padding-block: var(--padding-top) 0;

  background:
    var(--horizontal-line),
    var(--horizontal-line),
    var(--horizontal-line),
    var(--horizontal-line),
    var(--vertical-line),
    var(--vertical-line),
    var(--vertical-line);
  background-position:
    0 calc((var(--size) - (1 / var(--char-height) * var(--size))) * 1rem + var(--padding-top)),
    0 calc((var(--size) - (var(--cap-height) / var(--char-height) * var(--size))) * 1rem +...

JavaScript

function setVar(name, value) {
  document.documentElement.style.setProperty(name, value);
}

function getHeight(element) {
  return element.getBoundingClientRect().height;
}

function updateMetrics({ capRatio, exRatio, offsetRatio }) {
  setVar("--char-height", capRatio);
  setVar("--cap-height", capRatio);
  setVar("--x-height", exRatio);
  setVar("--offset", offsetRatio);
  
  vars__cap_height.innerHTML = capRatio;
  vars__x_height.innerHTML = exRatio;
}

function getMetrics() {
	const em = parseFloat(window.getComputedStyle(metrics).getPropertyValue("font-size"), 10);
  const { width: cap, height: ex } = metrics.getBoundingClientRect();

  const offset = (getHeight(linebox) - ex) - ((cap - ex) / 2);

	const capRatio = cap / em;
  const exRatio = ex / em;
  const offsetRatio = offset / em;

  console.log(em, cap, ex, capRatio, exRatio, offsetRatio);

  updateMetrics({ capRatio, exRatio, offsetRatio });
  scheduleCheck = false;
}

let scheduleCheck = false;

const resizeObserver = new ResizeObserver((entries) => {
  for (const entry of entries) {
    if (!scheduleCheck) {
    	scheduleCheck = true;
      requestAnimationFrame(getMetrics);
    }
  }
});

resizeObserver.observe(metrics);

getMetrics();

function blobToDataURL(blob) {
  return new Promise((resolve) => {
    var reader = new FileReader();
    reader.addEventListener("load", async function(event) {
      resolve(event.target.result);
    });
    reader.readAsDataURL(blob);
  });
}

const formatMap = {
  ttf: "truetype",
  otf: "opentype",
  woff: "woff",
  woff2: "woff2"
};

function createFontStyles(name, file) {
  const {
    url,
    format
  } = file || {};
  const css = `
			${
				url && format ?
				`@font-face {
					font-family: "${name}";
					font-display: swap;
					src: url("${url}") format("${formatMap[format]}");
				}` : ""
			}
				[font="${name}"] {
					font-family: "${name}";
				}
			`;

  const style = document.createElement("style");

  style.innerHTML = css;

 ...