"Отменить" преобразования холста для ввода текста

При применении преобразования с холстом полученный текст также (очевидно) преобразуется. Есть ли способ предотвратить определенные преобразования, такие как отражение, влияющих на текст?

Например, я устанавливаю глобальную матрицу преобразований, поэтому ось Y направлена ​​вверх, ось X вправо, а точка (0, 0) находится в центре экрана (что вы ожидаете от математической координаты система).

Однако это также переводит текст в обратном порядке.

const size = 200;

const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');

ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);

const triangle = [
  {x: -70, y: -70, label: 'A'},
  {x:  70, y: -70, label: 'B'},
  {x:   0, y:  70, label: 'C'},
];

// draw lines  
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();
  
// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
triangle.forEach(v => ctx.fillText(v.label, v.x, v.y - 8));
<canvas></canvas>

Ответ 1

Чтобы построить тайский ответ, который является фантастическим, вы можете рассмотреть следующее:

    const size = 200;

    const canvas = document.getElementsByTagName('canvas')[0]
    canvas.width = canvas.height = size;
    const ctx = canvas.getContext('2d');

    // Create a custom fillText funciton that flips the canvas, draws the text, and then flips it back
    ctx.fillText = function(text, x, y) {
      this.save();       // Save the current canvas state
      this.scale(1, -1); // Flip to draw the text
      this.fillText.dummyCtx.fillText.call(this, text, x, -y); // Draw the text, invert y to get coordinate right
      this.restore();    // Restore the initial canvas state
    }
    // Create a dummy canvas context to use as a source for the original fillText function
    ctx.fillText.dummyCtx = document.createElement('canvas').getContext('2d');

    ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);

    const triangle = [
      {x: -70, y: -70, label: 'A'},
      {x:  70, y: -70, label: 'B'},
      {x:   0, y:  70, label: 'C'},
    ];

    // draw lines  
    ctx.beginPath();
    ctx.strokeStyle = 'black';
    ctx.moveTo(triangle[2].x, triangle[2].y);
    triangle.forEach(v => ctx.lineTo(v.x, v.y));
    ctx.stroke();
    ctx.closePath();
      
    // draw labels
    ctx.textAlign = 'center';
    ctx.font = '24px Arial';
    // For this particular example, multiplying x and y by small factors >1 offsets the labels from the triangle vertices
    triangle.forEach(v => ctx.fillText(v.label, 1.2*v.x, 1.1*v.y));

Ответ 2

Мое решение поворачивает холст, а затем рисует текст.

ctx.scale(1,-1); // rotate the canvas
triangle.forEach(v => {
ctx.fillText(v.label, v.x, -v.y + 25); // draw with a bit adapt position
});

Надеюсь, что помогает:)

const size = 200;

const canvas = document.getElementsByTagName('canvas')[0]
canvas.width = canvas.height = size;
const ctx = canvas.getContext('2d');

ctx.setTransform(1, 0, 0, -1, size / 2, size / 2);

const triangle = [
  {x: -70, y: -70, label: 'A'},
  {x:  70, y: -70, label: 'B'},
  {x:   0, y:  70, label: 'C'},
];

// draw lines  

ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(triangle[2].x, triangle[2].y);
triangle.forEach(v => ctx.lineTo(v.x, v.y));
ctx.stroke();
ctx.closePath();

// draw labels
ctx.textAlign = 'center';
ctx.font = '24px Arial';
ctx.scale(1,-1);
triangle.forEach(v => {
ctx.fillText(v.label, v.x, -v.y + 25);
});
<canvas></canvas>

Ответ 3

Я бы пошел с подходом, который хранит "состояние" вашего чертежа без фактических пикселей и определяет метод draw, который может отображать это состояние в любой точке.

Вам нужно будет реализовать свои собственные методы scale и translate для ваших очков, но я думаю, что это стоит того, в конце концов.

Итак, в пулях:

  • Сохраните список "вещей для рисования" (точки с метками)
  • Expose scale и translate методы, которые изменяют эти "вещи"
  • Вывести метод draw, который отображает эти "вещи"

В качестве примера я создал класс под названием Figure, который показывает реализацию этих функций 1.0. Я создаю новый экземпляр, который ссылается на холст. Затем я добавляю к нему точки, передавая x, y и a label. scale и transform обновить свойства этих точек x и y. draw проходит через точки к a) нарисуйте "точку" и b) нарисуйте метку.

const Figure = function(canvas) {
  const ctx = canvas.getContext('2d');
  const origin = {
    x: canvas.width / 2,
    y: canvas.height / 2
  };
  const shift = p => Object.assign(p, {
    x: origin.x + p.x,
    y: origin.y - p.y
  });

  let points = [];

  this.addPoint = (x, y, label) => {
    points = points.concat({
      x,
      y,
      label
    });
  }

  this.translate = (tx, ty) => {
    points = points.map(
      p => Object.assign(p, {
        x: p.x + tx,
        y: p.y + ty
      })
    );
  };

  this.scale = (sx, sy) => {
    points = points.map(
      p => Object.assign(p, {
        x: p.x * sx,
        y: p.y * sy
      })
    );
  };

  this.draw = function() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();

    const sPoints = points.map(shift);

    sPoints.forEach(p => drawDot(ctx, 5, p.x, p.y));
    sPoints.forEach(p => drawLabel(ctx, p.label, p.x + 5, p.y));

    ctx.fill();
  }
}

const init = () => {
  const canvas = document.getElementById('canvas');
  const fig = new Figure(canvas);

  // Generate some test data
  for (let i = 0, labels = "ABCD"; i < labels.length; i += 1) {
    fig.addPoint(i * 3, (i + 1) * 10, labels[i]);
  }

  const sX = parseFloat(document.querySelector(".js-scaleX").value);
  const sY = parseFloat(document.querySelector(".js-scaleY").value);
  const tX = parseFloat(document.querySelector(".js-transX").value);
  const tY = parseFloat(document.querySelector(".js-transY").value);

  fig.scale(sX, sY);
  fig.translate(tX, tY);
  fig.draw();
}

Array
  .from(document.querySelectorAll("input"))
  .forEach(el => el.addEventListener("change", init));

init();



// Utilities for drawing
function drawDot(ctx, d, x, y) {
  ctx.arc(x, y, d / 2, 0, 2 * Math.PI);
}

function drawLabel(ctx, label, x, y) {
  ctx.fillText(label, x, y);
}
canvas {
  background: #efefef;
  margin: 1rem;
}

input {
  width: 50px;
}
<div>
  <p>
    Scales first, translates second (hard coded, can be changed)
  </p>
  <label>Scale x <input type="number" class="js-scaleX" value="1"></label>
  <label>Scale y <input type="number" class="js-scaleY" value="1"></label>
  <br/>
  <label>Translate x <input type="number" class="js-transX" value="0"></label>
  <label>translate y <input type="number" class="js-transY" value="0"></label>
</div>
<canvas id="canvas" width="250" height="250"></canvas>