Skip to content Skip to sidebar Skip to footer

How Can I Colour Different Words In The Same Line With Html5 Canvas?

Consider this loop that runs through key value pairs in an object: Object.keys(categories).forEach(categoryKey => { ctx.fillText(`${categoryKey}: ${categories[categoryKey]}`,

Solution 1:

There are many times I need to format text for canvas rendering (particularly for math examples) so below is a modification of the function I use to modify the rendered text using a nested formatting guide.

Demo

A modified copy from my own library that formats text according to nested style rules.

The simpleTextStyler works by first getting the size of each character in the font (use simpleTextStyler.setFont() to set the current context font or it will not work) and then looking at each character in the string separating out groups of characters with the same styles and rendering them one at a time.

It uses a stack to hold the current style pushing a style onto the stack if a new one is found "{" and popping the style off the stack at each closing "}"

It will add a new line if "\n" is found. Move to the next tab stop if "\t" and use various nested style inside "{" and "}" where the first character after the "{" denotes the function.

  • s sub script
  • S super script
  • + bigger text
  • - smaller text
  • # colour must be followed by 6 char hex colour eg red {#FF0000This text is red}

NOTE: Must use only 7 char CSS hex colour. eg "Change colour to {#FF0000Red}"

Text styles can be nested eg "text{Ssuper{ssub{Ssuper}}{Ssuper super}}" is text

The demo renders the string "Testing\nnewline\n\tTab\n\t\tTab\n\t\t\tTab\nSub{sScript} Super{SScript} Size {+Big {+Bigger}} Normal {-Small {-Smaller}}\nAnd now colours \n{#FF0000Red} {#00FF00Green} {#0000FFBlue}"

For your need

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);
functiondrawExamples(){
  simpleTextStyler.setFont(); // set the current font
  simpleTextStyler.drawText(ctx,
  "Testing simple Canvas2D text styler...\nnewline\n\tTab\n\t\tTab\n\t\t\tTab\nSub{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: "{}\n\t",
        spaceSize: 0,
        tabSize: 8, // in spaceSize unitstabs: (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) {
                    returnthis.tabs[i] * this.tabSize * this.spaceSize;
                }
                i++;
            }
            returnthis.tabs[i-1] * this.tabSize * this.spaceSize;
        },
        getFontSize: function(font){
            var numFind = /[0-9]+/;
            var number = numFind.exec(font)[0];
            if (isNaN(number)) {
                throwError("SimpleTextStyler Cant find font size");
            }
            returnNumber(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 contextif (context.ctx) { ctx = context.ctx } // may be a image with attached ctx?else{ return }
            } else { ctx = context }

            functionrenderText(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 asciiif (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;
                        } elseif (c === "\t") { // tab move to next tab
                            x = this.getNextTab(x - xx) + xx;
                        } elseif (c === "{") {   // Text format delimiter                       
                            state.push({size, colour, x, y})
                            i += 1;
                            const t = text[i];
                            if (t === "+") {  // Increase size
                                size *= 1/(3/4);
                            } elseif (t === "-") {  // decrease size
                                size *= 3/4;
                            } elseif (t === "s") { // sub script
                                y += size * (1/3);
                                size  *= (2/3);
                            } elseif (t === "S") { // super script
                                y -= size * (1/3);
                                size  *= (2/3);
                            } elseif (t === "#") {
                                colour = text.substr(i,7);
                                i+= 6;
                            }
                        } elseif (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;
}
<canvasid="canvas"width=512height=160></canvas>

Solution 2:

Credit to @Blindman67 for suggesting the use of measureText().

Here's how I did it in the end (though some of the code in this context wont make sense to you probably as it's part of bigger thing). Hopefully you should be able to see the key parts:

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++;
  }
});

Hope this helps someone.

Post a Comment for "How Can I Colour Different Words In The Same Line With Html5 Canvas?"