如何在画布填充文本中实现自动换行和回车?



我正在尝试显示已存储在MariaDB中的文本区域信息。 我在存储文本信息时没有问题。 我遇到的问题是将格式从文本区域过渡到我希望它显示的画布。

目标是让用户在文本区域中填写注释,然后将这些注释显示在单独的画布报表中。

现在,我可以使用存储在wordWrap.js文件中的以下代码使自动换行成功运行:

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);
}

我可以添加文本,该文本根据 fillText 区域的大小和单词的长度换行。 我需要补充的是支持回车的能力。 用户使用 支持回车不会有问题,所以我只需要让它工作。

我已经看到了其他支持回车的代码。我在下面玩过的例子。

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) );
}

这些方法具有相似的属性,我相信它们可以对齐,但我无法弄清楚如何实现两个脚本的关键部分,这是驱动文本拆分的原因......

text.split(''(

text.split(' '(

在我看来,这看起来像是 for 和 while 循环的组合,就像换行使用的那样,但我需要一些帮助来确定在哪里。

在浏览器中呈现文本的最佳选择是HTML和CSS。
Canvas 2D API 仍然远远低于此,因此当您需要在画布上渲染复杂文本时,最好是使用 HTML 和 CSS 的强大功能来采取画布所需的所有措施。

我已经做了一些处理类似问题的答案,所以这个只是根据你的需求改编了这些以前的代码:

// 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>

在您的情况下,您可能希望隐藏此div,然后将其删除:

const text = "This is an example text with a few new linesn" +
"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>