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