samizdatco/skia-canvas

Different baseline rendering positions are different to node-canvas

Closed this issue · 1 comments

When set different baseline, the rendering positions are different to node-canvas.

Code

Click to show the code
import { createCanvas } from "canvas";
import fs from "node:fs";
import { Canvas } from "skia-canvas";

// skia-canvas
let skiaCanvas = new Canvas(1000, 1000);

testDrawText(skiaCanvas, "skia-canvas");

await skiaCanvas.saveAs("skia-canvas.pdf");

// node-canvas
let nodeCanvas = new createCanvas(1000, 1000, "pdf");
nodeCanvas.getContext("2d").textDrawingMode = "glyph";

testDrawText(nodeCanvas, "node-canvas");

fs.writeFileSync("node-canvas.pdf", nodeCanvas.toBuffer());

function testDrawText(canvas, tip) {
  const crossLineHalfWidth = 25;
  const step = 60;
  const x = 50;
  let y = 60;
  let fontFamily = "zcool-gdh";
  let text = "The quick brown fox jumps over the lazy dog";

  let ctx = canvas.getContext("2d");

  ctx.strokeStyle = "purple";
  ctx.fillStyle = "black";
  ctx.lineWidth = 1;
  ctx.font = `normal 20pt '${fontFamily}'`;

  const baselines = ["top", "hanging", "middle", "alphabetic", "ideographic", "bottom"];

  drawTextWithTip(tip);

  baselines.forEach((baseline, index) => drawTextWithCrossLine(text, x, y + step * (index + 1), baseline));

  y = 600;
  fontFamily = "Times new Roman";
  ctx.font = `normal 12pt '${fontFamily}'`;
  text = "Abcdefg";

  drawTextWithLine(text, x + 100, y);

  function drawTextWithCrossLine(text, x, y, baseline) {
    ctx.textBaseline = baseline;

    ctx.fillText(`${text}(${baseline})`, x, y);

    ctx.moveTo(x - crossLineHalfWidth, y);
    ctx.lineTo(x + crossLineHalfWidth, y);
    ctx.stroke();

    ctx.moveTo(x, y - crossLineHalfWidth);
    ctx.lineTo(x, y + crossLineHalfWidth);
    ctx.stroke();
  }

  function drawTextWithLine(text, x, y) {
    ctx.moveTo(0, y);
    ctx.lineTo(x + 1000, y);
    ctx.stroke();

    baselines.forEach((baseline, index) => {
      const newX = x + step * 2 * index;

      ctx.textBaseline = "alphabetic";
      ctx.fillText(baseline, newX, y - step);

      ctx.textBaseline = baseline;
      ctx.fillText(text, newX, y);
    });
  }

  function drawTextWithTip(text) {
    ctx.textBaseline = "alphabetic";
    ctx.fillText(text, x, y);
  }
}

result

image
image

file

This is is the result PDF and font file: all-file.zip

Question

  1. Position is different to node-cnavas, maybe this is a issue like as #208
  2. Font metrics height very different, I use measureText api, the result is this:
skia-canvas metrics is:  TextMetrics {
  width: 26.670000076293945,
  actualBoundingBoxLeft: 0,
  actualBoundingBoxRight: 26.670000076293945,
  actualBoundingBoxAscent: 26.073354721069336,
  actualBoundingBoxDescent: 5.926646709442139,
  fontBoundingBoxAscent: 26.073354721069336,
  fontBoundingBoxDescent: 5.926646709442139,
  emHeightAscent: 26.073354721069336,
  emHeightDescent: 5.926646709442139,
  hangingBaseline: 16.53333282470703,
  alphabeticBaseline: 0,
  ideographicBaseline: -5.926646709442139,
  lines: [
    {
      x: 0,
      y: -26.073354721069336,
      width: 26.670000076293945,
      height: 32,
      baseline: 0,
      startIndex: 0,
      endIndex: 6
    }
  ]
}
node-canvas metrics is:  {
  width: 26.666015625,
  actualBoundingBoxLeft: 1.83984375,
  actualBoundingBoxRight: 24.826171875,
  actualBoundingBoxAscent: 17.759765625,
  actualBoundingBoxDescent: -1.4404296875,
  emHeightAscent: 22.90625,
  emHeightDescent: 3.759765625,
  alphabeticBaseline: -0.0263671875
}

And In chrome, metrics is:

web-canvas metrics is:  {
  actualBoundingBoxAscent: 17.75555992126465,
  actualBoundingBoxDescent: -1.4396400451660156,
  actualBoundingBoxLeft: -1.839539885520935,
  actualBoundingBoxRight: 24.820459365844727,
  alphabeticBaseline: -0,
  fontBoundingBoxAscent: 23,
  fontBoundingBoxDescent: 4,
  hangingBaseline: 18.399999618530273,
  ideographicBaseline: -4,
  width: 26.659988403320312,
}

See the result, the width is basically the same, but the height is very different(skia-canvas is 32 and node-cnavas is 26.67, web-canvas is 27). Is this caused by different calculation algorithms for font metrics? The difference in metrics between node-canvas and web-canvas is even smaller.

Version 2.0.2 fixes actualBoundingBox & line-height calculations (using the font metrics to find the default leading if not specified in ctx.font) and calculates baseline offsets to more closely match browser behavior. Note that no two browsers behave identically when it comes to type, so there's no true 'standard' behavior to emulate.