如何从画布中的文本中获取特定字符数据? (在 MultiDir 中很重要:RTL / LTR 字符串)

问题描述 投票:0回答:1

我有多目录文本,我想要标记或选择一些字符(例如最后一个字符)
示例字符串:"معنی Hello سلام است یا Salam"
一些字符从左侧扩展文本,一些字符从右侧扩展文本。(您可以在编辑器中复制文本并使用 BackSpace 逐个删除字符以便看得更清楚) 我想看看这个画布中“H”字符的位置在哪里或者用户点击了哪个字符......?

canvas.measureText(txt.slice(0,dest)).width
不工作。因为我不知道我的字符是从右边还是左边长出来的?

我检查画布文本功能,然后用谷歌搜索它,什么也看不到!

javascript html canvas typography register-transfer-level
1个回答
0
投票

不幸的是,Canvas2D API 仍然缺少适当的 API 来正确处理文本。平心而论,多年来人们一直致力于各种事情,但文本非常复杂。

所以目前,没有任何东西可以为您提供每个字形级别的信息,

measureText()
只需要一堆文本,不会暴露每个字形。

如果您可以访问 CSS 渲染器(例如,因为您的脚本在浏览器中运行),您可以做的是使用 CSS 渲染您的文本,然后使用 DOM

Range
API,这足以公开信息以至少获得每个字符的边界框(细节少于画布
TextMetrics
,但对于您的情况应该足够了)。

这是一个怪物,它从我以前的一个非画布中心问题的答案中删除了一些代码。请注意,这不是像素完美的,它混合了字符和字形,这是一个问题,尤其是阿拉伯文字。

const text = "معنی Hello سلام است یا Salam";
const x = 10;
const y = 50;
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "30px sans-serif";
ctx.textBaseLine = "alphabetic";
ctx.strokeStyle = "red";

// See below for the helper functions
// Get the list of all chars and their BBox, sorted rtl-ttb
const chars = getDisplayedChars(text, ctx.font);
// Get the BBox of the whole text (fails in Firefox with this text...)
const wholeMetrics = getCanvasTextBox(ctx, text);
// Compute the relative boxes, in canvas.
const relativeChars = chars.map(({ text, rect }) => {
  const left = rect.left + wholeMetrics.left + x;
  const top = rect.top + wholeMetrics.top + y;
  return new DOMRect(left, top, rect.width, rect.height);
});

let hovered = null;
draw();

canvas.onmousemove = ({ clientX, clientY }) => {
  const { left, top } = canvas.getBoundingClientRect();
  const x = clientX - left;
  const y = clientY - top;
  const lastHovered = hovered;
  hovered = findCharAt(x, y);  
  if (lastHovered !== hovered) {
    draw();
  }
};
function findCharAt(x, y) {
  return relativeChars.find(({ top, right, bottom, left }) => {
    return left <= x && right >= x &&
      top <= y && bottom >= y;
  });
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillText(text, x, y);
  if (hovered) {
    ctx.strokeRect(hovered.left, hovered.top, hovered.width, hovered.height);
  }
}

function getDisplayedChars(text, fontStyle) {
  // Won't work in StackSnippet, but using an iframe allows to not be polluted by CSS
  const frame = document.createElement("iframe");
  document.body.append(frame);
  const doc = frame.contentDocument || document;
  const container = doc.createElement("div");
  container.style.font = fontStyle;
  container.textContent = text;
  doc.body.prepend(container);
  const range = doc.createRange(); // to get our nodes positions
  
  // First we get the full text BBox, so we can later have relative positions
  range.selectNode(container);
  const mainBBox = range.getBoundingClientRect();
  
  const chars = [];
  const node = container.firstChild;
  node.data.split("").forEach((char, index) => {
    range.setStart(node, index); // Move the range to this character
    range.setEnd(node, index + 1);
    const nodeBBox = range.getBoundingClientRect();
    const rect = getRelativeBBox(mainBBox, nodeBBox);
    chars.push({ char, rect });
  });

  container.remove();
  frame.remove();
  
  return chars.filter((char) => char.rect.height) // Keep only the displayed ones
    .sort((a, b) => { // Sort ttb-ltr
      return a.rect.top - b.rect.top ||
        a.rect.left - b.rect.left;
    })
}
function getRelativeBBox(from, to) {
  const x = to.left - from.left;
  const y = to.top - from.top;
  return new DOMRect(x, y, to.width, to.height);
}
// Returns a BBox based on a TextMetrics
// relative to {0,0}
function getCanvasTextBox(ctx, text) {
  const metrics = ctx.measureText(text);
  const left    = metrics.actualBoundingBoxLeft * -1;
  const top     = metrics.actualBoundingBoxAscent * -1;
  const right   = metrics.actualBoundingBoxRight;
  const bottom  = metrics.actualBoundingBoxDescent;
  const width   = right - left;
  const height  = bottom - top;
  return new DOMRect(left, top, width, height);
}
<canvas width=500 height=80></canvas><br>
Hover over each character to show its bounding-box.

© www.soinside.com 2019 - 2024. All rights reserved.