到目前为止,要进行居中,我正在使用以下两行代码:
ctx.textAlign="center";
ctx.textBaseline = "middle";
这几乎可以完成工作,但是某些字符,例如“ g”和“ y”并没有完全居中。如何确保所有这些功能均受支持? Here是一个JSbin,它显示大多数字符(例如“ g”)在中心线下方。
期望:
现实:
为了使我的“期望”起作用,我从字母的y值中减去了15px,但这会使诸如“ a”的小字母弄乱,并使它们超出顶部的界限。
最佳答案
测量文字。
一种方法是渲染角色,然后扫描像素以找到范围,顶部,底部,左侧和右侧,以找到角色的真实中心。
这是一个非常昂贵的过程,因此您需要将以前的测量结果存储在地图中,并以相同的字符和字体返回这些结果。
下面的示例创建对象charSizer
。设置字体charSizer.font = "28px A font"
即可获取有关任何字符的信息。 charSizer.measure(char)
返回一个包含有关字符尺寸信息的对象。
您可以测量生产中的字符并将信息提供给页面以减少客户端处理,但是您将需要针对每个浏览器,因为它们都以不同的方式呈现文本。
例
该示例包含说明。左画布显示使用ctx.textAlign = "center"
和ctx.textBaseline = "middle"
将char渲染到正常中心。还包括彩色拼合线,以显示范围,中心,边界中心和加权中心。中间的画布使用边界中心画圆,而右画布使用加权中心。
这仅是示例,未经测试且未达到生产质量。
const charSizer = (() => {
const known = new Map();
var w,h,wc,hc;
const workCan = document.createElement("canvas");
const ctx = workCan.getContext("2d");
var currentFont;
var fontHeight = 0;
var fontId = "";
function resizeCanvas(){
wc = (w = workCan.width = fontHeight * 2.5 | 0) / 2;
hc = (h = workCan.height = fontHeight * 2.5 | 0) / 2;
ctx.font = currentFont;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
}
function measure(char){
const info = {
char,
width : ctx.measureText(char).width,
top : null,
left : w,
right : 0,
bottom : 0,
weightCenter : { x : 0, y : 0 },
center : { x : 0, y : 0 },
offset : { x : 0, y : 0 },
wOffset : { x : 0, y : 0 },
area : 0,
width : 0,
height : 0,
}
ctx.clearRect(0,0,w,h);
ctx.fillText(char,wc,hc);
const pixels8 = ctx.getImageData(0,0,w,h).data;
const pixels = new Uint32Array(pixels8.buffer);
var x,y,i;
i = 0;
for(y = 0; y < h; y ++){
for(x = 0; x < w; x ++){
const pix = pixels[i++];
if(pix){
const alpha = pixels8[(i<<2)+3];
info.bottom = y;
info.right = Math.max(info.right, x);
info.left = Math.min(info.left, x);
info.top = info.top === null ? y : info.top;
info.area += alpha;
info.weightCenter.x += (x - wc) * (alpha/255);
info.weightCenter.y += (y - hc) * (alpha/255);
}
}
}
if(info.area === 0){
return {empty : true};
}
info.area /= 255;
info.weightCenter.x /= info.area;
info.weightCenter.y /= info.area;
info.height = info.bottom - info.top + 1;
info.width = info.right - info.left + 1;
info.center.x = info.left + info.width / 2;
info.center.y = info.top + info.height / 2;
info.offset.x = wc - info.center.x;
info.offset.y = hc - info.center.y;
info.wOffset.x = -info.weightCenter.x;
info.wOffset.y = -info.weightCenter.y;
info.top -= hc;
info.bottom -= hc;
info.left -= wc;
info.right -= wc;
info.center.x -= wc;
info.center.y -= hc;
return info;
}
const API = {
set font(font){
currentFont = font;
fontHeight = Number(font.split("px")[0]);
resizeCanvas();
fontId = font;
},
measure(char){
var info = known.get(char + fontId);
if(info) { return {...info} } // copy so it is save from change
info = measure(char);
known.set(char + fontId,info);
return info;
}
}
return API;
})()
//==============================================================================
//==============================================================================
// Demo code from here down not part of answer code.
const size = 160;
const sizeh = 80;
const fontSize = 120;
function line(x,y,w,h){
ctx.fillRect(x,y,w,h);
}
function hLine(y){ line(0,y,size,1) }
function vLine(x){ line(x,0,1,size) }
function circle(ctx,col = "red",x= sizeh,y = sizeh,r = sizeh*0.8,lineWidth = 2) {
ctx.lineWidth = lineWidth;
ctx.strokeStyle = col;
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI * 2);
ctx.stroke();
}
const ctx = canvas.getContext("2d");
const ctx1 = canvas1.getContext("2d");
const ctx2 = canvas2.getContext("2d");
canvas.width = size;
canvas.height = size;
canvas1.width = size;
canvas1.height = size;
canvas2.width = size;
canvas2.height = size;
canvas.addEventListener("click",nextChar);
canvas1.addEventListener("click",nextFont);
ctx.font = "20px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Click this canvas", sizeh,sizeh-30);
ctx.fillText("cycle", sizeh,sizeh);
ctx.fillText("characters", sizeh,sizeh + 30);
ctx1.font = "20px Arial";
ctx1.textAlign = "center";
ctx1.textBaseline = "middle";
ctx1.fillText("Click this canvas", sizeh,sizeh - 30);
ctx1.fillText("cycle", sizeh,sizeh);
ctx1.fillText("fonts", sizeh,sizeh + 30);
charSizer.font = "128px Arial";
ctx1.textAlign = "center";
ctx1.textBaseline = "middle";
ctx2.textAlign = "center";
ctx2.textBaseline = "middle";
const chars = "\"ABCDQWZ{@pqjgw|/*";
const fonts = [
fontSize+"px Arial",
fontSize+"px Arial Black",
fontSize+"px Georgia",
fontSize+"px Impact, Brush Script MT",
fontSize+"px Rockwell Extra Bold",
fontSize+"px Franklin Gothic Medium",
fontSize+"px Brush Script MT",
fontSize+"px Comic Sans MS",
fontSize+"px Impact",
fontSize+"px Lucida Sans Unicode",
fontSize+"px Tahoma",
fontSize+"px Trebuchet MS",
fontSize+"px Verdana",
fontSize+"px Courier New",
fontSize+"px Lucida Console",
fontSize+"px Georgia",
fontSize+"px Times New Roman",
fontSize+"px Webdings",
fontSize+"px Symbol",];
var currentChar = 0;
var currentFont = 0;
var firstClick = true;
function nextChar(){
if(firstClick){
setCurrentFont();
firstClick = false;
}
ctx.clearRect(0,0,size,size);
ctx1.clearRect(0,0,size,size);
ctx2.clearRect(0,0,size,size);
var c = chars[(currentChar++) % chars.length];
var info = charSizer.measure(c);
if(!info.empty){
ctx.fillStyle = "red";
hLine(sizeh + info.top);
hLine(sizeh + info.bottom);
vLine(sizeh + info.left);
vLine(sizeh + info.right);
ctx.fillStyle = "black";
hLine(sizeh);
vLine(sizeh);
ctx.fillStyle = "red";
hLine(sizeh + info.center.y);
vLine(sizeh + info.center.x);
ctx.fillStyle = "blue";
hLine(sizeh + info.weightCenter.y);
vLine(sizeh + info.weightCenter.x);
ctx.fillStyle = "black";
circle(ctx,"black");
ctx.fillText(c,sizeh,sizeh);
ctx1.fillStyle = "black";
circle(ctx1);
ctx1.fillText(c,sizeh + info.offset.x,sizeh+ info.offset.y);
ctx2.fillStyle = "black";
circle(ctx2,"blue");
ctx2.fillText(c,sizeh + info.wOffset.x, sizeh + info.wOffset.y);
}
}
function setCurrentFont(){
fontUsed.textContent = fonts[currentFont % fonts.length];
charSizer.font = fonts[currentFont % fonts.length];
ctx.font = fonts[currentFont % fonts.length];
ctx2.font = fonts[currentFont % fonts.length];
ctx1.font = fonts[(currentFont ++) % fonts.length];
}
function nextFont(){
setCurrentFont();
currentChar = 0;
nextChar();
}
canvas { border : 2px solid black; }
.red {color :red;}
.blue {color :blue;}
<canvas id="canvas"></canvas><canvas id="canvas1"></canvas><canvas id="canvas2"></canvas><br>
Font <span id="fontUsed">not set</span> [center,middle] <span class=red>[Spacial center]</span> <span class=blue> [Weighted center]</span><br>
Click left canvas cycles char, click center to cycle font. Not not all browsers support all fonts