lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
96 lines • 3.44 kB
JavaScript
import { Msg } from "../core/Strings.js";
/** The rendering context used for measuring text. */
let measureContext = null;
/** The default font style. */
let defaultFontStyle = '';
/**
* The LFU cache of measured text. Contains a limited amount of text
* measurements ordered by most to least frequently used.
*/
const measureCache = [];
/** The size limit for the LFU cache (`measureCache`) */
const measureCacheLimit = 64;
/**
* The minimum amount of time since the last hit for a cache entry to be
* considered as expired, in milliseconds.
*/
const expiryThreshold = 10000;
/**
* Measures the dimensions of a given string of text with a given font.
*
* Note that the first time calling this function is slower than subsequent
* calls because a dedicated canvas context must be created.
*
* @returns Returns a the TextMetrics of the measured text.
*
* @category Helper
*/
export function measureTextDims(text, font) {
const measureTime = (new Date()).getTime();
// Get cached value
let cacheHit = null;
let cacheHitIdx = -1;
let removalIdx = measureCache.length - 1;
for (let i = 0; i < measureCache.length; i++) {
const cacheVal = measureCache[i];
if (cacheVal[0] === font && cacheVal[1] === text) {
cacheHit = cacheVal;
cacheHitIdx = i;
break;
}
// Mark last expired cache entry for removal. If none is found, the last
// entry in the cache (even if not expired) is marked for removal.
if (measureTime - cacheVal[4] > expiryThreshold) {
removalIdx = i;
}
}
// If there was a cache hit, increment hits, update last hit time, bump in
// cache and return cached metrics
if (cacheHit) {
const newFreq = ++cacheHit[3];
cacheHit[4] = measureTime;
if (cacheHitIdx > 0) {
let candidateIdx = 0;
for (; candidateIdx < cacheHitIdx; candidateIdx++) {
if (measureCache[candidateIdx][3] <= newFreq) {
break;
}
}
if (candidateIdx !== cacheHitIdx) {
measureCache.splice(cacheHitIdx, 1);
measureCache.splice(candidateIdx, 0, cacheHit);
}
}
return cacheHit[2];
}
// Get canvas context if not yet got
if (measureContext === null) {
const tempCanvas = document.createElement('canvas');
measureContext = tempCanvas.getContext('2d');
if (measureContext === null) {
throw new Error(Msg.CANVAS_CONTEXT);
}
measureContext.fontKerning = 'normal';
defaultFontStyle = measureContext.font;
}
// Set font
measureContext.font = font === '' ? defaultFontStyle : font;
// Measure text
const metrics = measureContext.measureText(text);
// Cache metrics. Remove least frequently used text entry if cache size
// exceeded.
if (measureCacheLimit > 0) {
if (measureCache.length === measureCacheLimit) {
measureCache.splice(removalIdx, 1);
}
let candidateIdx = 0;
for (; candidateIdx < measureCache.length; candidateIdx++) {
if (measureCache[candidateIdx][3] <= 1) {
break;
}
}
measureCache.splice(candidateIdx, 0, [font, text, metrics, 1, measureTime]);
}
return metrics;
}
//# sourceMappingURL=measureTextDims.js.map