您面临的问题是TextMetrics.width代表文本的“ 前进宽度
如何在HTML5画布上将单行文本调整为精确的宽度?到目前为止,我一直尝试以初始字体大小写文本,用measureText(my_text).width
测量文本的宽度,然后根据所需的文本宽度和实际的文本宽度之间的比率来计算新的字体大小。它给出的结果大致正确,但是根据文本的不同,边缘会有一些空白。
这里是一些示例代码:
// Draw "guard rails" with 200px space in between
c.fillStyle = "lightgrey";
c.fillRect(90, 0, 10, 200);
c.fillRect(300, 0, 10, 200);
// Measure how wide the text would be with 100px font
var my_text = "AA";
var initial_font_size = 100;
c.font = initial_font_size + "px Arial";
var initial_text_width = c.measureText(my_text).width;
// Calculate the font size to exactly fit the desired width of 200px
var desired_text_width = 200;
new_font_size = initial_font_size * desired_text_width / initial_text_width;
// Draw the text with the new font size
c.font = new_font_size + "px Arial";
c.fillStyle = "black";
c.textBaseline = "top";
c.fillText(my_text, 100, 0, 500);
此结果对于某些字符串是完美的,例如"AA"
:
但是对于其他字符串,例如"BB"
,在边缘处有一个间隙,您会看到文本没有到达“ guardrails”:
我该怎么做,使文本始终到达边缘的右边?
测量文本在许多级别上都是有问题的。
完整且实验性的textMetric
已经定义了很多年,但仅在一种主流浏览器(Safari)上可用,隐藏在标志后面(Chrome),由于错误(Firefox)被掩盖,状态未知(Edge,IE )。
width
充其量您可以使用width
返回的对象的ctx.measureText
属性来估计宽度。该宽度大于或等于实际像素宽度(最左到最右)。请注意,网络字体必须完全加载,否则宽度可能等于占位符字体的宽度。
不幸的是,接缝可靠工作的唯一方法是蛮力技术,该技术将字体呈现为临时/或工作画布,并通过查询像素来计算范围。
这将在支持画布的所有浏览器中都起作用。
不适用于实时动画和应用程序。
以下功能
将返回具有以下属性的对象
width
文本的画布像素中的宽度left
距画布像素中第一个像素左侧的距离right
从左到画布像素中最后检测到的像素的距离rightOffset
在画布像素中距测量的文本宽度和检测到的右边缘的距离measuredWidth
由ctx.measureText
返回的测量宽度baseSize
字体大小以像素为单位font
用于测量文本的字体如果宽度为零或字符串不包含可见文本,它将返回undefined
。
然后,您可以使用固定大小的字体和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; } })();
用法示例
该示例使用上面的功能,并且通过仅提供第一个和最后一个非空白字符来简化测量。
在文本输入中输入文本。
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>
[Note
measureText
]中扩展画布的高度。 前进宽度是字形的初始笔位置与下一个字形的初始笔位置之间的距离。
您想要的是bounding-box
宽度,要获得此宽度,您需要计算TextMetric.actualBoundingBoxLeft
+TextMetric.actualBoundingBoxLeft
的总和。还请注意,在渲染文本时,必须考虑边界框的TextMetric.actualBoundingBoxRight
偏移量才能使其正确适合。
不幸的是,所有浏览器都不支持扩展的TextMetrics对象,对于这些浏览器,我们很不幸。
TextMetric.actualBoundingBoxRight
actualBoundingBoxLeft
您面临的问题是TextMetrics.width代表文本的“ 前进宽度