如何使用 HTML5 画布在同一行中为不同的单词着色?



考虑这个循环,它贯穿对象中的键值对:

Object.keys(categories).forEach(categoryKey => {
ctx.fillText(`${categoryKey}: ${categories[categoryKey]}`, 40, (30 * i) + 160);
ctx.fillStyle = '#fff';
i++;
});

所有文本都是白色的,但我想要的是文本:${categoryKey}是红色的。

因为它在同一个字符串中运行,所以我不确定如何在不分解它的情况下做到这一点,因为我希望它在同一个ctx.fillText()调用中。

很多时候我需要格式化画布渲染的文本(特别是数学示例),所以下面是我用来使用嵌套格式指南修改渲染文本的函数的修改。

演示

来自我自己的库的修改副本,根据嵌套样式规则格式化文本。

simpleTextStyler的工作原理是首先获取字体中每个字符的大小(使用simpleTextStyler.setFont()设置当前上下文字体,否则它将不起作用),然后查看字符串中的每个字符,分离出具有相同样式的字符组并一次呈现一个。

它使用堆栈来保存当前样式,如果找到新样式"{",则将样式推送到堆栈上,并在每个关闭"}"处将样式从堆栈中弹出

如果找到"",它将添加一个新行。如果"\t",则移动到下一个制表位,并在"{""}"中使用各种嵌套样式,其中"{"后的第一个字符表示函数。

  • s子脚本
  • S超级脚本
  • +更大的文字
  • -较小的文本
  • #颜色后必须跟 6 个字符的十六进制颜色,例如红色 {#FF0000This 文本是红色}

注意:必须仅使用 7 个字符的 CSS 十六进制颜色。 例如"Change colour to {#FF0000Red}"

文本样式可以嵌套,例如"text{Ssuper{ssub{Ssuper}}{Ssuper super}}"是文本超级超级超级

该演示呈现字符串"TestingnnewlinentTabnttTabntttTabnSub{sScript} Super{SScript} Size {+Big {+Bigger}} Normal {-Small {-Smaller}}nAnd now colours n{#FF0000Red} {#00FF00Green} {#0000FFBlue}"

满足您的需求

simpleTextStyler.setFont(); // only needs to be set for the font family
// sizing text is in the last argument of the next call
simpleTextStyler.drawText(ctx,
`{#FF0000${categoryKey}}: ${categories[categoryKey]}`, 40, (30 * i) + 160,fontSize);

var ctx = canvas.getContext("2d");
ctx.font = "18px arial";
setTimeout(drawExamples,0);
function drawExamples(){
simpleTextStyler.setFont(); // set the current font
simpleTextStyler.drawText(ctx,
"Testing simple Canvas2D text styler...nnewlinentTabnttTabntttTabnSub{sScript} Super{SScript} Size {+Big {+Bigger}} Normal {-Small {-Smaller}}nAnd now colours n{#FF0000Red} {#00FF00Green} {#0000FFBlue}",
10,20,18)
}

const simpleTextStyler = (function(){
const simpleTextStyler = {
sizes: [],
baseSize: undefined,
font: undefined,
controlChars: "{}nt",
spaceSize: 0,
tabSize: 8, // in spaceSize units
tabs: (function() {var t = []; for(var i=0; i < 100; i += 8){t.push(i);}; return t;})(),
getNextTab: function(x) {
var i = 0;
while (i < this.tabs.length) {
if (x < this.tabs[i] * this.tabSize * this.spaceSize) {
return this.tabs[i] * this.tabSize * this.spaceSize;
}
i++;
}
return this.tabs[i-1] * this.tabSize * this.spaceSize;
},
getFontSize: function(font){
var numFind = /[0-9]+/;
var number = numFind.exec(font)[0];
if (isNaN(number)) {
throw Error("SimpleTextStyler Cant find font size");
}
return Number(number);
},
setFont: function(font = ctx.font) {
this.font = ctx.font = font;
this.baseSize = this.getFontSize(font);
for (var i = 32; i < 256; i ++) {
this.sizes[i - 32] = ctx.measureText(String.fromCharCode(i), 0, 0).width/this.baseSize;
}
this.spaceSize = this.sizes[0];
},
drawText: function(context, text, x, y, size) {
var i, len, subText;
var w, scale;
var xx, yy, ctx;
var state = [];
if(text === undefined){ return }
xx = x;
yy = y;
if (!context.setTransform) { // simple test if this is a 2D context
if (context.ctx) { ctx = context.ctx } // may be a image with attached ctx?
else{ return }
} else { ctx = context }
function renderText(text) {
ctx.save();
ctx.fillStyle = colour;
ctx.translate(x, y)
ctx.scale(scale, scale)
ctx.fillText(text, 0, 0);
ctx.restore();
}
var colour = ctx.fillStyle;
ctx.font = this.font;
len = text.length;
subText = "";
w = 0;
i = 0;
scale = size / this.baseSize;
while (i < len) {
const c = text[i];
const cc = text.charCodeAt(i);
if (cc < 256) { // only ascii
if (this.controlChars.indexOf(c) > -1) {
if (subText !== "") {
scale = size / this.baseSize;
renderText(subText);
x += w;
w = 0;
subText = "";                        
}
if (c === "n") {  // return move to new line
x = xx;
y += size;
} else if (c === "t") { // tab move to next tab
x = this.getNextTab(x - xx) + xx;
} else if (c === "{") {   // Text format delimiter                       
state.push({size, colour, x, y})
i += 1;
const t = text[i];
if (t === "+") {  // Increase size
size *= 1/(3/4);
} else if (t === "-") {  // decrease size
size *= 3/4;
} else if (t === "s") { // sub script
y += size * (1/3);
size  *= (2/3);
} else if (t === "S") { // super script
y -= size * (1/3);
size  *= (2/3);
} else if (t === "#") {
colour = text.substr(i,7);
i+= 6;
}
} else if (c  === "}"){
const s = state.pop();
y = s.y;
size = s.size;
colour = s.colour;
scale = size / this.baseSize;
}
} else {
subText += c;
w += this.sizes[cc-32] * size;
}
}
i += 1;
}
if (subText !== "") { renderText(subText) }
},
}
return simpleTextStyler;
})();
canvas {
border : 2px solid black;
}
<canvas id="canvas" width=512 height=160></canvas>

感谢@Blindman67建议使用measureText()

这是我最后是如何做到的(尽管此上下文中的某些代码对您来说可能没有意义,因为它是更大事情的一部分)。希望您应该能够看到关键部分:

let i = 0;
Object.keys(categories).forEach(categoryKey => {
const category = paramKeyToCategoryName(categoryKey);
const offsetTop = 190;
const offsetLeft = 40;
const spacing = 30;
if (category.length) {
// Category text.
ctx.fillStyle = '#f13f3d';
ctx.fillText(`${category.toUpperCase()}:`, offsetLeft, (spacing * i) + offsetTop);
// Measure category text length.
const measure = ctx.measureText(category.toUpperCase());
const measureWidth = measure.width + 12;
// Selected artist text.
ctx.fillStyle = '#fff';
ctx.fillText(`${categories[categoryKey].toUpperCase()}`, (offsetLeft + measureWidth), (spacing * i) + offsetTop);
i++;
}
});

希望这对某人有所帮助。

最新更新