简体   繁体   中英

How can I implement word wrap and carriage returns in canvas fillText?

I'm trying to display textarea information that has been stored in a MariaDB. I don't have a problem storing the text information. What I'm having a problem with is transition the formatting from the text area to the canvas I want it displayed in.

The goal is to have a user fill in the notes in a textarea and then have those displayed in the separate canvas report.

Right now, I can get the wordwrap working successfully using this code I have stored in a wordWrap.js file:

function wrapText (c, text, x, y, maxWidth, lineHeight) {

    var words = text.split(' ');
    var line = '';
    var lineCount = 0;
    var test;
    var metrics;

    for (var i = 0; i < words.length; i++) {
        test = words[i];
// add test for length of text
        metrics = c.measureText(test);
        while (metrics.width > maxWidth) {
            test = test.substring(0, test.length - 1);
            metrics = c.measureText(test);
        }

        if (words[i] != test) {
            words.splice(i + 1, 0,  words[i].substr(test.length))
            words[i] = test;
        }  

        test = line + words[i] + ' ';  
        metrics = c.measureText(test);

        if (metrics.width > maxWidth && i > 0) {
            c.fillText(line, x, y);
            line = words[i] + ' ';
            y += lineHeight;
            lineCount++;
        }
        else {
            line = test;
        }
    }

    c.fillText(line, x, y);
}

I can add the text, which word wraps based on the size of the fillText area and the length of the words. What I need to add to this is the ability to support carriage returns. The users won't have a problem using \\n to support carriage returns so I just need to get it to work.

I've seen other code out there that supports carriage returns. Example I've played with below.

ctx.font = '12px Courier';
var text = <?php echo json_encode($row['notes']);?>;
var x = 30;
var y = 30;
var lineheight = 15;
var lines = text.split('\n');

for (var i = 0; i<lines.length; i++) {
    ctx.fillText(lines[i], x, y + (i*lineheight) );
}

These methods have similar attributes and I believe they can be aligned but I'm having trouble figuring out how to implement the key piece of both scripts which is what drives text split ...

text.split('\\n')

text.split(' ')

This looks to me like a combination of for and while loops like the word wrap uses, but I need some help figuring out where.

The best at rendering text in a browser are definitively HTML and CSS.
Canvas 2D API is still far below, so when you need to render complex text on a canvas, the best is to use the power of HTML and CSS to take all the measures needed for your canvas.

I already made a few answers that deal with similar issues, so this one is just an adaptation of these previous codes to your needs:

 // see https://stackoverflow.com/questions/55604798 // added x output function getLineBreaks(node, contTop = 0, contLeft = 0) { if(!node) return []; const range = document.createRange(); const lines = []; range.setStart(node, 0); let prevBottom = range.getBoundingClientRect().bottom; let str = node.textContent; let current = 1; let lastFound = 0; let bottom = 0; let left = range.getBoundingClientRect().left; while(current <= str.length) { range.setStart(node, current); if(current < str.length -1) { range.setEnd(node, current + 1); } const range_rect = range.getBoundingClientRect(); bottom = range_rect.bottom; if(bottom > prevBottom) { lines.push({ x: left - contLeft, y: prevBottom - contTop, text: str.substr(lastFound , current - lastFound) }); prevBottom = bottom; lastFound = current; left = range_rect.left; } current++; } // push the last line lines.push({ x: left - contLeft, y: bottom - contTop, text: str.substr(lastFound) }); return lines; } function getRenderedTextLinesFromElement(elem) { elem.normalize(); // first grab all TextNodes const nodes = []; const walker = document.createTreeWalker( elem, NodeFilter.SHOW_TEXT ); while(walker.nextNode()) { nodes.push(walker.currentNode); } // now get all their positions, with line breaks const elem_rect = elem.getBoundingClientRect(); const top = elem_rect.top; const left = elem_rect.left; return nodes.reduce((lines, node) => lines.concat(getLineBreaks(node, top, left)), []); } const ctx = canvas.getContext('2d'); ctx.textBaseline = 'bottom'; txt_area.oninput = e => { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); const lines = getRenderedTextLinesFromElement(txt_area); // apply the div's style to our canvas const node_style = getComputedStyle(txt_area); const nodeFont = (prop) => node_style.getPropertyValue('font-' + prop); ctx.font = nodeFont('weight') + ' ' + nodeFont('size') + ' ' + nodeFont('family'); ctx.textAlign = node_style.getPropertyValue('text-align'); ctx.textBaseline = "bottom"; // draw each line of text lines.forEach(({text, x, y}) => ctx.fillText(text, x, y)); }; txt_area.oninput(); 
 #txt_area, canvas { width: 300px; height: 150px; resize: none; border: 1px solid; max-width: 300px; max-height: 150px; overflow: hidden; } canvas { border-color: green; } 
 <div contenteditable id="txt_area">This is an example text <br>that should get rendered as is in the nearby canvas </div> <canvas id="canvas"></canvas> 

In your case, you will probably want to make this div hidden, and to remove it afterward:

 const text = "This is an example text with a few new lines\\n" + "and some normal text-wrap.\\n" + "\\n" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\\n" + "\\n" + "At tempor commodo ullamcorper a lacus."; renderText(text); function getLineBreaks(node, contTop = 0, contLeft = 0) { if(!node) return []; const range = document.createRange(); const lines = []; range.setStart(node, 0); let prevBottom = range.getBoundingClientRect().bottom; let str = node.textContent; let current = 1; let lastFound = 0; let bottom = 0; let left = range.getBoundingClientRect().left; while(current <= str.length) { range.setStart(node, current); if(current < str.length -1) { range.setEnd(node, current + 1); } const range_rect = range.getBoundingClientRect(); bottom = range_rect.bottom; if(bottom > prevBottom) { lines.push({ x: left - contLeft, y: prevBottom - contTop, text: str.substr(lastFound , current - lastFound) }); prevBottom = bottom; lastFound = current; left = range_rect.left; } current++; } // push the last line lines.push({ x: left - contLeft, y: bottom - contTop, text: str.substr(lastFound) }); return lines; } function getRenderedTextLinesFromElement(elem) { elem.normalize(); // first grab all TextNodes const nodes = []; const walker = document.createTreeWalker( elem, NodeFilter.SHOW_TEXT ); while(walker.nextNode()) { nodes.push(walker.currentNode); } // now get all their positions, with line breaks const elem_rect = elem.getBoundingClientRect(); const top = elem_rect.top; const left = elem_rect.left; return nodes.reduce((lines, node) => lines.concat(getLineBreaks(node, top, left)), []); } function renderText(text) { // make the div we'll use to take the measures const elem = document.createElement('div'); elem.classList.add('canvas-text-renderer'); // if you wish to have new lines marked by \\n in your input elem.innerHTML = text.replace(/\\n/g,'<br>'); document.body.append(elem); const ctx = canvas.getContext('2d'); ctx.textBaseline = 'bottom'; const lines = getRenderedTextLinesFromElement(elem); // apply the div's style to our canvas const node_style = getComputedStyle(elem); const nodeFont = (prop) => node_style.getPropertyValue('font-' + prop); ctx.font = nodeFont('weight') + ' ' + nodeFont('size') + ' ' + nodeFont('family'); ctx.textAlign = node_style.getPropertyValue('text-align'); ctx.textBaseline = "bottom"; // draw each line of text lines.forEach(({text, x, y}) => ctx.fillText(text, x, y)); // clean up elem.remove(); } 
 .canvas-text-renderer, canvas { width: 300px; height: 150px; resize: none; border: 1px solid; max-width: 300px; max-height: 150px; overflow: hidden; } canvas { border-color: green; } .canvas-text-renderer { position: absolute; z-index: -1; opacity: 0; } 
 <canvas id="canvas"></canvas> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM