3

I have a canvas element which has labels. And each label is created using the fillText method.

I want to be able to send text like this: "CH2", but I don't get this as the final result. The <sub> element doesn't get parsed properly. How can I solve this issue?

Here is some example code:

 var ctx = document.getElementById('canvas').getContext('2d');
  ctx.font = "48px serif";
  ctx.fillText("Hello <sub>world</sub>", 10, 50);
<canvas id="canvas" width=500 height=500 ><canvas>

1

2 Answers 2

4

You can get around this using as @lipp mentions in comments Simon's solution and insert already sub-scripted characters into the string.

You can also make a simple parser that detects some code and renders next segment differently (see below).

There is also the possibility to use SVG to use HTML for drawing on canvas, but it has its backdraws such as async behavior, limited content (before security mechanisms prevent it from being drawn to a canvas) and lacking cross-browser support in some cases.

An example parser

This is just a start example. You can chose any code as well as adding new codes etc. This is just one way, there are many others...

If you have a HTML source string simply replace those tags with a code, or extend the parser to handle those as well.

var ctx = c.getContext("2d"),
    fontSize = 28,
    str = "This string has codes to enable |subscripted| text.";

setFontSize(fontSize);

// parse string
for(var i = 0, x = 10, tx = 0, isSub = false; i < str.length; i++) { // iterate over chars
  if (str[i] === "|") {                                              // special code?
    ctx.fillText(str.substring(tx, i), x, 50 + (isSub ? 7 : 0));     // draw current text seg
    x += ctx.measureText(str.substring(tx, i)).width;                // add width to x
    tx = ++i;                                                        // update start pointer
    isSub = !isSub;                                                  // toggle subscript mode
    setFontSize(isSub ? fontSize * 0.5 : fontSize);                  // set font size
  }
}

ctx.fillText(str.substring(tx, i), x, 50);                           // draw last text part

function setFontSize(sz) {ctx.font = sz + "px sans-serif"}
<canvas id=c width=600></canvas>

Sign up to request clarification or add additional context in comments.

Comments

1

Here is a function that converts an html string to a series of fillText statements. It handles multiline strings and lets you specify alignment (left, right, center).

<canvas id="textCanvas" width="700" height="150" style="border:1px solid #d3d3d3;">

<script>

function parse_html(ctx, s, x0, y0, align, font, fontsize, col) {
    // 2d canvas context, string, pos.x, pos.y, left/right/center, font, font height, color
    // Convert html code to a series of individual strings, each displayable by fillText().
    font = 'px '+font
    var lines = []
    var line = [0]
    var part = '' // the text element preceding a '<'
    var cmd = ''
    var bold = false
    var italic = false
    var sup = false
    var sub = false
    var x = 0, y = 0
    var dx, start
    var legal = ['b', 'strong', 'i', 'em', 'sup', 'sub']

    function add_part() {
        var style = ''
        var fs = fontsize
        if (bold) style += 'bold '
        if (italic) style += 'italic '
        if (sup || sub) {
            fs = 0.8*fontsize
            if (sup) y -= 0.3*fontsize // y increases downward in 2D canvas
            else y += 0.3*fontsize
        }
        ctx.font = style+fs+font
        dx = ctx.measureText(part).width
        line.push([x, y, ctx.font, part])
        part = ''
        x += dx
    }

    function end_line() {
        if (part !== '') add_part()
        line[0] = x
        lines.push(line)
        line = [0]
        x = y = 0
    }

    for (var i=0; i<s.length; i++) {
        var c = s[i]
        if (c == '\n') {
            end_line()
        } else if (c != '<') {
            part += c // a part of the text
        } else { // encountered '<'
            //if (part !== '') add_part()
            start = i+1
            i++
            cmd = s[i]
            var end = false
            if (cmd == '/') {
                cmd = ''
                end = true
            }
            var ok = true
            for (i=i+1; i<s.length; i++) {
                if (s[i] == '<') { // This means that the intial '<' did not start a command
                    i = i-1 // back up
                    part += '<'+cmd
                    add_part()
                    ok = false // signal that we encountered '<'
                    break
                }
                if (s[i] == '>') break
                cmd += s[i]
            }
            if (!ok) continue
            if (cmd == 'br' || cmd == 'br/') {
                end_line()
            } else {
                if (legal.indexOf(cmd) >= 0 && part !== '') add_part()
                switch (cmd) {
                    case 'b':
                    case 'strong':
                        bold = !end
                        break
                    case 'i':
                    case 'em':
                        italic = !end
                        break
                    case 'sup':
                        sup = !end
                        if (end) y = 0
                        break
                    case 'sub':
                        sub = !end
                        if (end) y = 0
                        break
                    default:
                        part += '<'+cmd+'>'
                }
            }
        }
    }
    if (part.length > 0) line.push([x, y, fontsize+font, part])
    ctx.font = fontsize+font
    line[0] = x + ctx.measureText(part).width
    lines.push(line)

    function rgb_to_html(rgb) { // convert RGB 0-1 to html 0-255
        var r = Math.floor(255 * rgb[0])
        var g = Math.floor(255 * rgb[1])
        var b = Math.floor(255 * rgb[2])
        return 'rgb(' + r + ',' + g + ',' + b + ')'
    }

    var width, L
    var nline = 0
    // Each line in lines starts with the total width of the line, followed by
    // elements of the form {x, y, font, text}, where x and y start at zero.
    var maxwidth = -1
    for (L in lines) {
        if (lines[L][0] > maxwidth) maxwidth = lines[L][0]
    }
    for (L in lines) {
        y0 += nline*1.2*fontsize
        nline++
        for (var p in lines[L]) {
            var k = lines[L][p]
            if (k[1] === undefined) {
                width = k
                continue
            }
            ctx.font = k[2]
            ctx.fillStyle = rgb_to_html(col)
            switch (align) {
                case 'left':
                    x = x0 + k[0]
                    y = y0 + k[1]
                    break
                case 'center':
                    x = x0 + k[0] - width/2
                    y = y0 + k[1]
                    break
                case 'right':
                    x = x0 + k[0] - maxwidth
                    y = y0 + k[1]
                    break
                default:
                    throw new Error(align+' is not a possible alignment option.')
            }
            ctx.fillText(k[3], x, y)
        }
    }
}

var c = document.getElementById("textCanvas")
var ctx = c.getContext("2d")
var s = 'The <b><i>quick</i> fox</b> <i>jumps.</i><br><i>M</i><sub>sys</sub> >= 10<sup>-3</sup> kg'
parse_html(ctx, s, 350, 50, 'center', 'Verdana', 30, [0,0,1])
</script>

2 Comments

Forgot to mention that it handles <b> (or <strong>), <i> (or <em>), <sup>, <sub>, and <br> (or <br/>. To display '<' you need to use '\\<' in your original string.
Modified the code to permit '\n' to be a synonym for '<br>', and no special treatment is needed to display '<'. I'll also mention that the arguments to parse_html are canvas context, string, x, y, alignment, font, fontsize, and RGB color (color elements are in range 0-1).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.