The canvas does not yet provide the same level of character manipulation and measuring functions as we might find in the Windows GDI or similar. But it does give us canvas.measureText() to measure character size. Combine that with path.getPointAtLength() and we can fit text to a path.
To have any chance to fit text to a path we will need to know the measurements of each character that we want to output.
As an aside, for character measuring, there is a potential but distant option to use the Houdini CSS interface – specifically the Font Metrics API, but sadly, according to https://ishoudinireadyyet.com/, there is nothing useful happening on that front (Jan 2021). Not surprising as it is complex work.
Another alternative could be opentype.js or font.js https://github.com/Pomax/lib-font but though both are very sophisticated and no doubt could provide the necessary character widths, they are daunting in their complexity and really more than we need for this little outing.
So – is there anything we can do to help ourselves, using what we’ve got ?
To get the character widths we could loop through all chars in a string and get measureText to measure them. However this ignores kerning pairs (see AYA image below).
A better solution is to measure the entire string, then measure the last character on its own. Subtract character width from full string width and you know the x-position for that characted in that string. Rinse and repeat for each character in the string.
Once completed you have an array of character widths and kerning adjustments for the string in hand. Outputting these in a relatively primitive char-by-char output to the canvas will give output matching the layout you achieve via running canvas.fillText() for the full string, meaning as an approach for gathering par-character information this algorithm works.
Fitting Text to a Path
To achieve this, we have to determine the 2D (x, y) points for the left corner and right corner of each character. Drawing the character then is simple.
On the face of it, this is complex because there is no way to predict the path. It could be a straight line, or a circle, or an ellipse, but you have to assume an unpredictable mad jagged loopy thing.
What this means is that we can’t easily compute the start and end points on the path. So we have to go fishing.
A quick discussion of the path.getPointAtLength() method is now required needed. The canvas knows the length of any path you ask it to draw, and you can ask it the (x, y) point for any distance along that path – the Konva source has code for this and it’s an interesting read! So, given a path of length of say 100, you can ask for the (x, y) point at length 42 with path.getPointAtLength(42).
You can probably see where this is going – we can use getPointAtLength() to go fishing for the character end point on the curve. I’ve tried to explain what we need to do in the little video below – take your time watching it and stop and restart it as each step appears. You need so understand the gist of how to fish for that magic point where the character cell hits the path.
It goes something like this:
- Given a character to draw and a known starting point on the path
- Advance along path the width of the character
- Get the (x, y) point at that extra distance along the path
- Compute the straight-line length between start point and this point
- But, except when the path segment is a perfectly straight line, this will be shorter than we need because of curvature of the path.
- We therefore need to jump forward along the path some distance, say +1 char width.
- Get point (x, y) at that length on path, compute straight line length, compare…
- Then we will need to jump back and forward until we find the path point that gives the straight line length matching the character width.
- When we find this point we have the (x, y) location for the end of the character cell.
- We can now calculate the angle of rotation from the line between these two points, given that the desired angle is perpendicular to the baseline.
- Finally we can draw the character, then repeat for the next.
We needed to find an approach to finding the drawing position for text on a path. We found a way to measure text, taking account of kerning pairs to ensure pleasing positioning, and we found how to fish for the point on the line where the text should end.
We ignored alignment of the text (centering, etc), and additional character spacing. Each can be handled with simple math once the basic algorithm is working.
Thanks for reading.