/*
* This file is part of TREB.
*
* TREB is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with TREB. If not, see .
*
* Copyright 2022-2026 trebco, llc.
* info@treb.app
*
*/
/*
* as of 2024 it looks like we can use proper fontmetrics in all browsers,
* so we'll switch to that. this is a replacement for the old fontmetrics
* (which read pixles), and any other font measurement utils.
*
* as far as I can tell the alphabatic baseline is constant, and reliable,
* in all browsers. so let's use that. other baselines seem to be slightly
* different, at least in firefox.
*/
export interface FontMetrics {
/** from textmetrics, this is the font ascent (max, essentially) */
ascent: number;
/** from textmetrics, this is the font descent (max) */
descent: number;
/** total height for the font (line height). just ascent + descent. should we +1 for baseline? */
height: number;
/** width of one paren */
paren: number;
/** width of one hash (#) character */
hash: number;
}
// these two will be engine global, which is what we want
const cache: Map = new Map();
let canvas: HTMLCanvasElement | undefined;
/**
* get font metrics for the given font, which includes a size.
* precompute the size, we're not doing that anymore.
*/
export const Get = (font: string, variants?: string) => {
const key = font;
// console.info({key});
let metrics = cache.get(key);
if (metrics) {
return metrics;
}
metrics = Measure(font, variants);
cache.set(key, metrics);
return metrics;
};
/**
* flush cache. this should be called when you update the theme
*/
export const Flush = () => {
cache.clear();
};
/**
* do the actual measurement
*/
const Measure = (font: string, variants?: string): FontMetrics => {
if (!canvas) {
if (typeof document !== 'undefined') {
canvas = document.createElement('canvas');
}
}
if (canvas) {
if (variants) {
canvas.style.fontVariant = variants;
}
else {
canvas.style.fontVariant = '';
}
}
const context = canvas?.getContext('2d', { alpha: false });
if (!context) {
throw new Error('invalid context');
}
context.textBaseline = 'alphabetic';
context.textAlign = 'center';
context.font = font;
let metrics = context.measureText('(');
const paren = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft;
metrics = context.measureText('#');
const hash = metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft;
metrics = context.measureText('Mljy!');
return {
paren,
hash,
ascent: metrics.fontBoundingBoxAscent,
descent: metrics.fontBoundingBoxDescent,
height: (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent),
};
};