Legacy Konva text
Konva was initially written a few years ago now, when HTML canvas was the new kid on the block. Getting any text onto a canvas was amazing, and adding justify, line-wrap, line-height, letter-spacing, underline and strikeout must have been fantastic for the early adopters.
However, if you compare legacy Konva text to the same output for MS Word or Google Docs you will quickly notice that the line height for the Konva output is more cramped than the output from the word processors.
In short, Konva's approach to text layout was functional but missed something important for those line height calculations.
The gist of the Konva text layout approach was to expect line height to match the font size it was given and to position the text via it's 'middle' in the vertical middle of the line. That probably worked for labels that were one-line of text only. But for multi-line text it produces the cramped results.
A better approach is to understand that the font size only gets you to a starting point. From there you ask the canvas for the text metrics of the letter you want to print. And from those metrics you need the fontBoundingBoxAscent and fontBoundingBoxDescent values which when added give the line height you need. This will result in a close match to the output from MS Word than the legacy approach - it's not pixel perfect down the length of a page but it is more representative of higher quality text layout.
Here's some sample html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Text layout demo</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./demo.js"></script>
</body>
</html>
And the matching JavaScript
//demo.js
// Get a handle to the 2D contxt from the HTML5 canvas element
const canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
// Set some text to draw
const text = "Yielding",
charArray = text.split('');
// Set the context properties of interest
ctx.save()
ctx.letterSpacing = 0
ctx.textBaseline = 'alphabetic'
ctx.fillStyle = 'black'
ctx.font = "32px Arial"
// Measure one letter to get the correct line height and baseline.
const fixedMetric = ctx.measureText("M"),
yAdvance = fixedMetric.fontBoundingBoxAscent + fixedMetric.fontBoundingBoxDescent;
console.log('Note event though the font is set as ' + ctx.font + ' the correct line height is ' + yAdvance + 'px')
// Prepare some variables to manage the output position
let xPos = 10,
yPos = 40;
// And now to output 3 lines repeating the given text
for (let i = 0; i < 3; i++){
// reset x at start of line
xPos = 0
for (const char of charArray){
// get the measurements of each char because char widths differ
const variableMetric = ctx.measureText(char),
xAdvance = variableMetric.width,
fontAscent = variableMetric.fontBoundingBoxDescent;
// Output the char at the calculated position
ctx.fillText(char, xPos, yPos + fontAscent)
// advance the x position by the char width
xPos = xPos + xAdvance
}
// advance the y position by the calculated line height
yPos = yPos + yAdvance
}
ctx.restore()