Измерение ширины текста
Измерение текста проблематично c на многих уровнях.
Полная и экспериментальная textMetric
была определена в течение многих лет, но доступна только в одном браузере основного потока (Safari), скрыта за флагами (Chrome), скрыта из-за ошибок (Firefox ), статус неизвестен (Edge, IE).
Использование только width
В лучшем случае вы можете использовать свойство width
объекта, возвращаемого ctx.measureText
, для оценки ширины. Эта ширина больше или равна фактической ширине пикселя (слева направо). Обратите внимание, что веб-шрифты должны быть полностью загружены, или их ширина может соответствовать ширине заполнителя шрифта.
Грубая сила
Единственный метод, обеспечивающий надежную работу швов, - это, к сожалению, метод грубой силы, который визуализирует шрифт. на временный / или рабочий холст и вычисляет экстент путем запроса пикселей.
Это будет работать во всех браузерах, поддерживающих холст.
Он не подходит для анимации и приложений в реальном времени. .
Следующая функция
Вернет объект со следующими свойствами
width
ширина в пикселях холста текста left
расстояние слева от первого пикселя в пикселях холста right
расстояние слева до последнего обнаруженного пикселя в пикселях холста rightOffset
расстояние в пикселе холста от измеренного ширина текста и обнаруженный правый край measuredWidth
измеренная ширина, возвращаемая ctx.measureText
baseSize
размер шрифта в пикселях font
используемый шрифт T o измерить текст
Он вернет undefined
, если ширина равна нулю или строка не содержит видимого текста.
Затем можно использовать шрифт фиксированного размера и двумерное преобразование для масштабирования текста до нужной ширины. Это будет работать для очень маленьких шрифтов, что приведет к более качественному отображению шрифтов при меньших размерах.
Точность зависит от размера измеряемого шрифта. Функция использует фиксированный размер шрифта 120px
. Базовый размер можно установить, передав свойство
. Функция может использовать частичный текст (Short Cut) для сокращения оперативной памяти и накладных расходов на обработку. Свойство rightOffset
- это расстояние в пикселях от правого края ctx.measureText
до первого пикселя с содержимым.
Таким образом, вы можете измерить текст "CB"
и использовать его для точного выравнивания любого текста, начиная с "C"
и заканчивается "B"
Пример использования короткого текста
const txtSize = measureText({font: "arial", text: "BB"});
ctx.font = txtSize.font;
const width = ctx.measureText("BabcdefghB").width;
const actualWidth = width - txtSize.left - txtSize.rightOffset;
const scale = canvas.width / actualWidth;
ctx.setTransform(scale, 0, 0, scale, -txtSize.left * scale, 0);
ctx.fillText("BabcdefghB",0,0);
measureText
function
const measureText = (() => {
var data, w, size = 120; // for higher accuracy increase this size in pixels.
const isColumnEmpty = x => {
var idx = x, h = size * 2;
while (h--) {
if (data[idx]) { return false }
idx += can.width;
}
return true;
}
const can = document.createElement("canvas");
const ctx = can.getContext("2d");
return ({text, font, baseSize = size}) => {
size = baseSize;
can.height = size * 2;
font = size + "px "+ font;
if (text.trim() === "") { return }
ctx.font = font;
can.width = (w = ctx.measureText(text).width) + 8;
ctx.font = font;
ctx.textBaseline = "middle";
ctx.textAlign = "left";
ctx.fillText(text, 0, size);
data = new Uint32Array(ctx.getImageData(0, 0, can.width, can.height).data.buffer);
var left, right;
var lIdx = 0, rIdx = can.width - 1;
while(lIdx < rIdx) {
if (left === undefined && !isColumnEmpty(lIdx)) { left = lIdx }
if (right === undefined && !isColumnEmpty(rIdx)) { right = rIdx }
if (right !== undefined && left !== undefined) { break }
lIdx += 1;
rIdx -= 1;
}
data = undefined; // release RAM held
can.width = 1; // release RAM held
return right - left >= 1 ? {
left, right, rightOffset: w - right, width: right - left,
measuredWidth: w, font, baseSize} : undefined;
}
})();
Пример использования
В приведенном выше примере используется функция, которая сокращает результат измерения, предоставляя только первый и последний непробельный символ.
Введите текст для ввода текста.
- Если текст слишком большой, чтобы поместиться на холсте, консоль будет отображать предупреждение.
- Если масштаб текста больше 1 (что означает, что отображаемый шрифт больше, чем измеренный шрифт), консоль отобразит предупреждение, так как могут быть некоторые потеря точности выравнивания.
inText.addEventListener("input", updateCanvasText);
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = 500;
function updateCanvasText() {
const text = inText.value.trim();
const shortText = text[0] + text[text.length - 1];
const txtSize = measureText({font: "arial", text: text.length > 1 ? shortText: text});
if(txtSize) {
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height)
ctx.font = txtSize.font;
const width = ctx.measureText(text).width;
const actualWidth = width - txtSize.left - txtSize.rightOffset;
const scale = (canvas.width - 20) / actualWidth;
console.clear();
if(txtSize.baseSize * scale > canvas.height) {
console.log("Font scale too large to fit vertically");
} else if(scale > 1) {
console.log("Scaled > 1, can result in loss of precision ");
}
ctx.textBaseline = "top";
ctx.fillStyle = "#000";
ctx.textAlign = "left";
ctx.setTransform(scale, 0, 0, scale, 10 - txtSize.left * scale, 0);
ctx.fillText(text,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "#CCC8";
ctx.fillRect(0, 0, 10, canvas.height);
ctx.fillRect(canvas.width - 10, 0, 10, canvas.height);
} else {
console.clear();
console.log("Empty string ignored");
}
}
const measureText = (() => {
var data, w, size = 120;
const isColumnEmpty = x => {
var idx = x, h = size * 2;
while (h--) {
if (data[idx]) { return false }
idx += can.width;
}
return true;
}
const can = document.createElement("canvas");
const ctx = can.getContext("2d");
return ({text, font, baseSize = size}) => {
size = baseSize;
can.height = size * 2;
font = size + "px "+ font;
if (text.trim() === "") { return }
ctx.font = font;
can.width = (w = ctx.measureText(text).width) + 8;
ctx.font = font;
ctx.textBaseline = "middle";
ctx.textAlign = "left";
ctx.fillText(text, 0, size);
data = new Uint32Array(ctx.getImageData(0, 0, can.width, can.height).data.buffer);
var left, right;
var lIdx = 0, rIdx = can.width - 1;
while(lIdx < rIdx) {
if (left === undefined && !isColumnEmpty(lIdx)) { left = lIdx }
if (right === undefined && !isColumnEmpty(rIdx)) { right = rIdx }
if (right !== undefined && left !== undefined) { break }
lIdx += 1;
rIdx -= 1;
}
data = undefined; // release RAM held
can.width = 1; // release RAM held
return right - left >= 1 ? {left, right, rightOffset: w - right, width: right - left, measuredWidth: w, font, baseSize} : undefined;
}
})();
body {
font-family: arial;
}
canvas {
border: 1px solid black;
width: 500px;
height: 500px;
}
<label for="inText">Enter text </label><input type="text" id="inText" placeholder="Enter text..."/>
<canvas id="canvas"></canvas>
Примечание декоративные шрифты могут не работать, может потребоваться увеличить высоту холста в функции measureText