konvajs / konva

Konva.js is an HTML5 Canvas JavaScript framework that extends the 2d context by enabling canvas interactivity for desktop and mobile applications.
http://konvajs.org/
Other
11.5k stars 920 forks source link

The height of the text when using react-konva and konva is not the same, causing the text to appear skewed #1798

Open htran844 opened 2 months ago

htran844 commented 2 months ago

I want to use react-konva to preview and drag text. Additionally, I plan to use konva in a Node.js server to render a PDF file with thousands of pages. However, when I render a test page, there is a problem: the height of some fonts is not consistent. As a result, the text "hg" with a custom font appears skewed.

Is there anything else you need help with?

image in react web: react

image in nodejs server: nodejs

My custom font I use is SVN-Pateglamt-Script, you can download at: https://drive.google.com/file/d/1YmEdMiq4H5ESV7503EN8ZDjHjMun9shO/view?usp=sharing This is my code in react version and nodejs version: React code:

import React, { useState } from "react";
import { Stage, Layer, Text } from "react-konva";
import "./App.css";
const widthGiay = 1240;
const heightGiay = 874;

function App() {
  const stageRef = React.useRef(null);
  return (
    <div>
      <div
        style={{
          width: widthGiay,
          height: heightGiay,
          border: "1px solid red"
        }}
      >
        <Stage width={widthGiay} height={heightGiay} ref={stageRef}>
          <Layer>
          <Text
              text="Hg"
              x={0}
              y={0}
              fontFamily="Calibri"
              fontSize={360}
              draggable
            />
            <Text
              text="Hg"
              x={500}
              y={0}
              fontFamily="SVN-Pateglamt-Script"
              fontSize={360}
              draggable
            />
          </Layer>
        </Stage>
      </div>
    </div>
  );
}

export default App;

nodejs code:

import { registerFont } from "canvas";
import Konva from "konva";

var stage = new Konva.Stage({
  width: 1240,
  height: 874,
});

// add canvas element
var layer = new Konva.Layer();
stage.add(layer);
registerFont("./fonts/SVN-Pateglamt-Script.ttf", {
  family: "SVN-Pateglamt-Script",
});
var simpleText = new Konva.Text({
  x: 0,
  y: 0,
  text: "Hg",
  fontSize: 360,
  fontFamily: "Calibri",
});
var simpleText2 = new Konva.Text({
  x: 500,
  y: 0,
  text: "Hg",
  fontSize: 360,
  fontFamily: "SVN-Pateglamt-Script",
  draggable: true,
});
layer.add(simpleText);
layer.add(simpleText2);
console.log("uri", stage.toDataURL());

I am using Nodejs 20.15.0 "konva": "^9.3.14" "react-konva": "^18.2.10"

lavrton commented 2 months ago

I don't think we can do much from Konva side. canvas library in Node.js renders fonts differently. Possible solutions I see:

  1. Use puppeteer. Slow, consumes a lot of CPU and memory. But will give you almost 100% the same result.
  2. Monkey patch konva in node to use skia-canvas instead of canvas.

Something like this:

const Konva = require('konva');
const { Canvas, DOMMatrix, Image } = require('skia-canvas');

global.DOMMatrix = DOMMatrix;
Konva.Util['createCanvasElement'] = () => {
  const node = new Canvas(300, 300);
  if (!node['style']) {
    node['style'] = {};
  }
  return node;
};
htran844 commented 2 months ago

I try the solutions 2 but can't fix :((

// import { registerFont } from "canvas";
import Konva from "konva";
import pkg from 'skia-canvas';
const { Canvas, DOMMatrix, Image, FontLibrary } = pkg
global.DOMMatrix = DOMMatrix;
Konva.Util['createCanvasElement'] = () => {
  const node = new Canvas(300, 300);
  if (!node['style']) {
    node['style'] = {};
  }
  return node;
};

var stage = new Konva.Stage({
  width: 1240,
  height: 874,
});

// add canvas element
var layer = new Konva.Layer();
stage.add(layer);

// registerFont("./fonts/PlaywriteBEVLG-VariableFont-wght.ttf", {
//   family: "PlaywriteBEVLG",
// });

// registerFont("./fonts/SVN-Pateglamt-Script.ttf", {
//   family: "SVN-Pateglamt-Script",
// });
FontLibrary.use('PlaywriteBEVLG','./fonts/PlaywriteBEVLG-VariableFont-wght.ttf');
FontLibrary.use('SVN-Pateglamt-Script','./fonts/SVN-Pateglamt-Script.ttf');
var box = new Konva.Rect({
    x: 0,
    y: 0,
    width: 1240,
    height: 874,
    fill: "lightgray"
})
var simpleText = new Konva.Text({
  x: 0,
  y: 0,
  text: "Hg",
  fontSize: 360,
  fontFamily: "PlaywriteBEVLG",
});
var simpleText2 = new Konva.Text({
  x: 500,
  y: 0,
  text: "Hg",
  fontSize: 360,
  fontFamily: "SVN-Pateglamt-Script",
  draggable: true,
});
layer.add(box);
layer.add(simpleText);
layer.add(simpleText2);
console.log("uri", stage.toDataURL().then(uri=>{
  console.log(uri)
}));
// console.log("uri", stage.toDataURL());
lavrton commented 2 months ago

Does it produce the result? Like you have an image output?

htran844 commented 2 months ago

this is result, font "SVN-Pateglamt-Script" not like react-konva when I use x=0 and y=0. (the second text) skia

lavrton commented 2 months ago

Well, I don't think there is much to do. Only the first option is left. Different render engines may produce different results. Like, even Chrome vs Firefox vs Safari sometimes render different output. Also, you can try to use Konva._fixTextRendering = true. It was made for a different issue, but it may change something for you.

If nothing works, I would go with puppeteer.

htran844 commented 2 months ago

Konva._fixTextRendering = true cannot fix. I think puppeteer will get bad perfomance. I will try export png in client or switch to use canvas in both side. thank you for your help!

lavrton commented 2 months ago

switch to use canvas in both side

What do you mean by that? You can't use canvas library directly on the client side. It will give you back just the native browser API, not its node.js implementation.

htran844 commented 2 months ago

I use the native canvas in HTML, and on the server, I use a canvaslibrary. The x and y positions render the same