测量文字宽度
测量文本在许多层面上都存在问题。
完整和实验性的textMetric 已定义多年,但仅在 1 个主流浏览器 (Safari) 上可用,隐藏在标志后面 (Chrome),由于错误 (Firefox) 被掩盖,状态未知 (Edge, IE)。
仅使用width
您最多可以使用ctx.measureText 返回的对象的width 属性来估计宽度。此宽度大于或等于实际像素宽度(从左到右最)。注意网络字体必须完全加载,否则宽度可能是占位符字体的宽度。
蛮力
不幸的是,唯一能可靠工作的方法是一种蛮力技术,它将字体渲染到临时/或工作画布上,并通过查询像素来计算范围。
这将适用于支持画布的所有浏览器。
不适合实时动画和应用。
如下函数
然后您可以使用固定大小的字体和 2D 变换来缩放文本以适合所需的宽度。这适用于非常小的字体,从而以更小的尺寸呈现更高质量的字体。
准确度取决于被测量字体的大小。该函数使用120px的固定字体大小,您可以通过传递属性来设置基本大小
该函数可以使用部分文本(快捷方式)来减少 RAM 和处理开销。属性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函数
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中扩展画布高度