मैं कैनवास पर टेक्स्ट केंद्रित करना चाहता हूं। क्षैतिज रूप से ठीक लगता है, लेकिन लंबवत अभी भी एक समस्या है: मैं यह नहीं समझ सकता कि प्रोग्रामेटिक रूप से यह कैसे करें।

मैं पहले इसे 1 लाइन के साथ करने में सक्षम था, लेकिन अब मैं इसे टेक्स्ट की कई पंक्तियों के लिए काम करना चाहता हूं।

छवि अभी इस तरह दिखती है:

enter image description here

पाठ थोड़ा ऊपर रखा जाना चाहिए, या मैं गलत हूँ?

enter image description here

यहाँ कोड है:


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 का उपयोग कर रहा हूं।

2
userjmillohara 27 जुलाई 2020, 15:57

1 उत्तर

सबसे बढ़िया उत्तर

आप सही कह रहे हैं, आपकी पहली छवि का टेक्स्ट भी उच्च स्थान पर होना चाहिए।

कोड में 3 समस्याएं हैं:

  1. y के प्रारंभिक मान को fontSize पर सेट करना
  2. चर की गणना offset
  3. पिछले fontSize (अर्थात अंतिम फिटिंग) पर वापस जाए बिना कैनवास की ऊंचाई पार होने पर लूप के लिए बाहर निकलना

अंक 1

y के प्रारंभिक मान को fontSize के विपरीत lineHeight पर सेट किया जाना चाहिए।

अंक 2

चर offset की गणना इस तथ्य को प्रतिबिंबित नहीं कर रही है कि a) पहली पाठ पंक्ति के लिए प्रारंभिक y निर्देशांक 0 के बजाय lineHeight है और b) एक है textBaseline मध्य पर सेट है। एक अनुकूलित offset गणना का एक उदाहरण नीचे मेरे कोड में है।

अंक 3

एक बार y का मान कैनवास की ऊंचाई (स्थिति y > opts.rect.height) से अधिक हो जाने पर, पिछले fontSize पर एक कदम पीछे हटना चाहिए। इसे हल करने के लिए, पिछले (यानी अंतिम फिटिंग) पुनरावृत्ति से मूल्यों को संग्रहीत करने के लिए नए चर पेश किए जा सकते हैं और इस आवश्यक 'स्टेप बैक' के लिए उपयोग किया जा सकता है। (नीचे मेरे कोड में वे चर हैं lastFittingLines, lastFittingFont, lastFittingY और lastFittingLineHeight।)

उदाहरण छवि:

Example of image with vertically centered text

यहां संशोधित कोड है:

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
}
1
colourCoder 23 सितंबर 2020, 03:58