如何在canvas fillText中实现自动换行和回车? [英] How can I implement word wrap and carriage returns in canvas fillText?

查看:57
本文介绍了如何在canvas fillText中实现自动换行和回车?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

现在,我可以使用存储在wordWrap.js文件中的以下代码来使自动换行工作正常:

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

我可以添加文本,该文本根据fillText区域的大小和单词的长度进行自动换行.我需要补充的是支持回车的功能.用户使用\ n支持回车不会有问题,所以我只需要使其正常工作即可.

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('\n')

text.split('')

text.split(' ')

在我看来,这就像for和while循环的组合,例如自动换行所使用的循环,但是我需要一些帮助来弄清位置.

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.

推荐答案

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

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>

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

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>

这篇关于如何在canvas fillText中实现自动换行和回车?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆