Мені подобається ваша "єдина ідея" просто робити статичну карту ширини символів! Насправді це добре працює в моїх цілях. Іноді, з міркувань продуктивності або через те, що у вас немає простого доступу до DOM, ви можете просто захотіти швидкий витривалий окремий калькулятор, відкалібрований під один шрифт. Отже ось один відкалібрований Helvetica; передайте рядок і (необов'язково) розмір шрифту:
function measureText(str, fontSize = 10) {
const widths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2796875,0.2765625,0.3546875,0.5546875,0.5546875,0.8890625,0.665625,0.190625,0.3328125,0.3328125,0.3890625,0.5828125,0.2765625,0.3328125,0.2765625,0.3015625,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.5546875,0.2765625,0.2765625,0.584375,0.5828125,0.584375,0.5546875,1.0140625,0.665625,0.665625,0.721875,0.721875,0.665625,0.609375,0.7765625,0.721875,0.2765625,0.5,0.665625,0.5546875,0.8328125,0.721875,0.7765625,0.665625,0.7765625,0.721875,0.665625,0.609375,0.721875,0.665625,0.94375,0.665625,0.665625,0.609375,0.2765625,0.3546875,0.2765625,0.4765625,0.5546875,0.3328125,0.5546875,0.5546875,0.5,0.5546875,0.5546875,0.2765625,0.5546875,0.5546875,0.221875,0.240625,0.5,0.221875,0.8328125,0.5546875,0.5546875,0.5546875,0.5546875,0.3328125,0.5,0.2765625,0.5546875,0.5,0.721875,0.5,0.5,0.5,0.3546875,0.259375,0.353125,0.5890625]
const avg = 0.5279276315789471
return str
.split('')
.map(c => c.charCodeAt(0) < widths.length ? widths[c.charCodeAt(0)] : avg)
.reduce((cur, acc) => acc + cur) * fontSize
}
Цей гігантський потворний масив має ширину символів ASCII, індексовану кодом символів. Таким чином, це просто підтримує ASCII (інакше він передбачає середню ширину символів). На щастя, ширина в основному лінійно масштабується з розміром шрифту, тому вона працює досить добре при будь-якому розмірі шрифту. Помітно не вистачає жодної поінформованості про кернінг чи лігатури чи що завгодно.
Для "калібрування" я просто вивів кожного символу до charCode 126 (могутній тильд) на svg і отримав обмежувальне поле та зберег його до цього масиву; більше коду та пояснення та демонстрації тут .