Search code examples
javascripthtml5-canvaswebgltriangulationopentype

Triangulate path data from OpenType.js using Earcut


I have a use case where I need to render a significant amount (~50,000 glyphs) of crisp, scalable text strings on a canvas element. The best solution I've tried so far involves triangulating text drawn on a canvas element (Text was drawn using fillText method), uploading matrix uniforms and the Float32Array of triangles representing that string to the GPU via WebGL. Using this method, I was able to render 100,000 glyphs at about 30fps. Glyphs become blocky at very high zoom levels, but that is fine for my use case.

However, this method has overhead of about ~250ms per string, since I first draw the string to a canvas element in memory, read pixel data, turn the bitmap image into a vector and then triangulate the vector data. Searching the web for solutions, I came across two interesting open-source projects:

So now I want to re-write my initial proof of concept to use OpenType and Earcut. OpenType for feeding curve data into Earcut, and Earcut for triangulating that data and returning an array representing the point for each triangle.

My problem is, I can't figure out how to get the data OpenType provides and convert it into the format that Earcut accepts. Can anyone provide assistance for this?

More Info:

This StackOverflow question had some great information, but lacks some of the implementation details: Better Quality Text in WebGL. I suppose what I am trying to accomplish is the "Font as Geometry" approach described in the first answer.


Solution

  • You can create a path using Font.getPath. Path consists of move-to, line-to, curve-to, quad-to and close instructions, accessed via path.commands. You will need to convert bezier curve instructions into small segments first, of course.

    Once you have a set of closed paths, you need to determine which ones are holes. Inner outlines will be oriented in an opposite direction to outer ones, and you can assign them to the smallest outer outline containing them. Once you have groups of <outer outline and a set of holes> you should be able to feed it to earcut library.

    This is a simple implementation that assumes there are no intersections. For me it worked very well for most fonts, except for very few "fancy" fonts that have intersecting paths.

    Here's a working example: https://jsbin.com/gecakub/edit?html,js,output

    Instead of creating meshes for each string, you could also create them for individual characters, and then position them yourself using kerning data from the library.

    Edit: this solution will only work for TTF fonts, though it can be easily adjusted for CCF (.otf) by ignoring path orientation and using a better "path A is inside path B" check, unless the font has intersecting paths.