Monday, 21 September 2020

Center Text with Multi Lines on Canvas in NodeJS

I want to center text on a canvas. Horizontally seems fine, but vertical is still a problem: I cannot figure out how to programmatically do this.

I was previously able to do this with 1 line, but now I'd like to get it to work for multiple lines of text.

The image right now looks like this:

enter image description here

the text should be positioned a bit higher, or am I mistaken?

enter image description here

Here is the code:


const fs = require('fs')
const {  createCanvas } = require('canvas')


const width = 2000;
const height = 2000;

const canvas = createCanvas(width, height)
const context = canvas.getContext('2d')

context.fillStyle = '#edf4ff'
context.fillRect(0, 0, width, height)


context.textAlign = 'center'
context.textBaseline = 'middle';
context.fillStyle = '#002763'


const fontSizeUsed = drawMultilineText(
    context,
    "This is yet another test",
    {
        rect: {
            x: 1000,
            y: 0,
            width: 2000,
            height: 2000 
        },
        font: 'Arial',
        verbose: true,
        lineHeight: 1,
        minFontSize: 100,
        maxFontSize: 200
      }
)

const buffer = canvas.toBuffer('image/png')
  fs.writeFileSync('./image.png', buffer)

and the crucial function drawMultiLineText, that's supposed to align the text, is this one:

function drawMultilineText(ctx, text, opts) {

    // Default options
    if(!opts)
        opts = {}
    if (!opts.font)
        opts.font = 'sans-serif'
    if (typeof opts.stroke == 'undefined')
        opts.stroke = false
    if (typeof opts.verbose == 'undefined')
        opts.verbose = false
    if (!opts.rect)
        opts.rect = {
            x: 0,
            y: 0,
            width: ctx.canvas.width,
            height: ctx.canvas.height
        }
    if (!opts.lineHeight)
        opts.lineHeight = 1.1
    if (!opts.minFontSize)
        opts.minFontSize = 30
    if (!opts.maxFontSize)
        opts.maxFontSize = 100
    // Default log function is console.log - Note: if verbose il false, nothing will be logged anyway
    if (!opts.logFunction)
        opts.logFunction = function(message) { console.log(message) }


        const words = require('words-array')(text)
        if (opts.verbose) opts.logFunction('Text contains ' + words.length + ' words')
        var lines = []
        let y;  //New Line

    // Finds max font size  which can be used to print whole text in opts.rec
    for (var fontSize = opts.minFontSize; fontSize <= opts.maxFontSize; fontSize++) {

        // Line height
        var lineHeight = fontSize * opts.lineHeight

        // Set font for testing with measureText()
        ctx.font = ' ' + fontSize + 'px ' + opts.font

        // Start
        var x = opts.rect.x;
        y = fontSize; //modified line
        lines = []
        var line = ''

        // Cycles on words
        for (var word of words) {
            // Add next word to line
            var linePlus = line + word + ' '
            // If added word exceeds rect width...
            if (ctx.measureText(linePlus).width > (opts.rect.width)) {
                // ..."prints" (save) the line without last word
                lines.push({ text: line, x: x, y: y })
                // New line with ctx last word
                line = word + ' '
                y += lineHeight
            } else {
                // ...continues appending words
                line = linePlus
            }
        }

        // "Print" (save) last line
        lines.push({ text: line, x: x, y: y })

        // If bottom of rect is reached then breaks "fontSize" cycle
        if (y > opts.rect.height)
            break

  }
  
    if (opts.verbose) opts.logFunction("Font used: " + ctx.font);
    const offset = opts.rect.y + (opts.rect.height - y) / 2; //New line, calculates offset
    for (var line of lines)
            // Fill or stroke
            if (opts.stroke)
                    ctx.strokeText(line.text.trim(), line.x, line.y + offset) //modified line
            else
                    ctx.fillText(line.text.trim(), line.x, line.y + offset) //modified line
    
    // Returns font size
    return fontSize

}

I am not in a browser, I am using node.js.



from Center Text with Multi Lines on Canvas in NodeJS

No comments:

Post a Comment