3

テキストをキャンバスの中央に配置したい。水平方向には問題ないように見えますが、垂直方向にはまだ問題があります。プログラムでこれを行う方法がわかりません。

以前は 1 行でこれを行うことができましたが、複数行のテキストで機能させたいと考えています。

現在の画像は次のようになります。

ここに画像の説明を入力

テキストは少し上に配置する必要がありますか、それとも間違っていますか?

ここに画像の説明を入力

コードは次のとおりです。


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)

そして、テキストを揃えることになっている重要な関数drawMultiLineTextは次のとおりです。

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

}

私はブラウザではなく、node.js を使用しています。

4

1 に答える 1

3

そうです、最初の画像のテキストも高く配置する必要があります。

コードには 3 つの問題があります。

  1. の初期値を設定yするfontSize
  2. 変数の計算offset
  3. キャンバスの高さを超えたときに for ループを終了し、前fontSizeのもの (つまり、最後のフィッティング)に戻ることはありません。

問題1

の初期値はではなくyに設定する必要があります。lineHeightfontSize

問題2

変数の計算は、a) 最初のテキスト行の開始座標が代わりにあり 、b)中央にセットがoffsetあるという事実を反映していません。適応された計算の例は、以下のコードにあります。ylineHeight0textBaselineoffset

問題 3

の値がyキャンバスの高さを超えると (条件y > opts.rect.height)、前の に戻る必要がありますfontSize。これを解決するために、新しい変数を導入して前の (つまり、最後のフィッティング) 反復からの値を格納し、この必要な「ステップ バック」に使用することができます。(以下のコードの変数はlastFittingLineslastFittingFontlastFittingYおよびlastFittingLineHeightです。)

画像例:

テキストが垂直方向に中央揃えされた画像の例

変更されたコードは次のとおりです。

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 a text with multiple lines that is vertically centered as expected.",
    {
        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('./image3.png', buffer)

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

    
    let lastFittingLines;                       // declaring 4 new variables (addressing issue 3)
    let lastFittingFont;
    let lastFittingY;
    let lastFittingLineHeight;
    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 = lineHeight; //modified line        // setting to lineHeight as opposed to fontSize (addressing issue 1)
        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;
            
        lastFittingLines = lines;               // using 4 new variables for 'step back' (issue 3)
        lastFittingFont = ctx.font;
        lastFittingY = y;
        lastFittingLineHeight = lineHeight;

    }

    lines = lastFittingLines;                   // assigning last fitting values (issue 3)                    
    ctx.font = lastFittingFont;                                                                   
    if (opts.verbose) opts.logFunction("Font used: " + ctx.font);
    const offset = opts.rect.y - lastFittingLineHeight / 2 + (opts.rect.height - lastFittingY) / 2;     // modifying calculation (issue 2)
    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
}

于 2020-09-23T00:58:51.703 に答える