processing / p5.js

p5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. http://twitter.com/p5xjs —
http://p5js.org/
GNU Lesser General Public License v2.1
21.1k stars 3.22k forks source link

[p5.js 2.0 RFC Proposal]: Typography module revamp #7026

Open limzykenneth opened 1 month ago

limzykenneth commented 1 month ago

Increasing access

Typography module enables text based work to be done in the canvas, creating more possibilities of interaction between free drawing in pixels and text. There are also opportunity for more creative manipulation of text and type once typography module has been stabilized and made up to date.

Which types of changes would be made?

Most appropriate sub-area of p5.js?

What's the problem?

Currently the typography module feels a bit brittle with many of the functions around text alignment, width/height measurement, text wrapping, etc, all undergoing fixes at one point or another. These fixes increases the technical debt the module has and also may have hidden edge case bugs (perhaps when using the module with non-English text for example) that we have not found yet.

In addition, for the textToPoints() function and drawing text onto a WebGL canvas, p5.js is currently relying on opentype.js which does not seem to be actively maintained. opentype.js also adds a very significant file size footprint in the overall p5.js bundle.

What's the solution?

There are two main interconnected parts to this:

One possible way to start is to leverage browser text rendering API as much as possible, much of CSS and possibly the browser's own text rendering engine can potentially be leveraged to do much of the work (eg. loading fonts into CSS works as a way to load fonts in canvases). Some feature, especially those we rely heavily on opentype.js as mentioned above will likely not work out of the box with browsers so will need new solutions there.

The typography module should also be test to work across as many language scripts as possible without extra effort from the user.

Pros (updated based on community comments)

Cons (updated based on community comments)

Proposal status

Under review

davepagurek commented 1 month ago

For some more context, we looked into some alternatives here: https://github.com/processing/p5.js/issues/6391#issuecomment-1708718396

How much do we care about feature compatibility between renderers? Switching to using native canvas APIs is very clearly a win for 2D mode. I'm not sure there's a way to get this to work in WebGL very easily though, and it will probably lag behind for features like variable fonts. OpenType.js doesn't even support that, but it still seems like unfortunately maybe the best option out there. So one path forward could be to let 2D and WebGL diverge in their feature sets, and only include OpenType.js in builds that include WebGL.

limzykenneth commented 1 month ago

I just found gl-text, perhaps we can have a look at their approach?

davepagurek commented 1 month ago

gl-text uses font-atlas, which is kind of like if we rendered all the characters of a font to a 2D canvas and then used those as textures in WebGL. The main reason why we haven't been doing that in p5.js is that the resolution of texture that you need depends on how close the text is to the camera, which may vary from frame to frame as things move, possibly resulting in pixelated text. That tradeoff is potentially OK for some, but right now we've opted for a vector drawing approach which will be crisp regardless of the camera. The downside is that it needs the Bezier curve information to do that, which needs a font parser.

font-atlas-sdf is a step closer, because you can render an SDF at higher resolutions and it will still be crisp, but it still will result in a lack of accuracy once you've zoomed in farther than the original resolution. Personally I'm more OK with this tradeoff compared to a regular font texture atlas, as it also would let us add stroke to text in WebGL mode, but this is still a bit of a tradeoff in accuracy compared to the current implementation.

Both could potentially work with variable fonts, although probably more slowly than in 2D mode when you adjust font variables since it would have to recreate the atlas each time something changes.

limzykenneth commented 1 month ago

Yeah that made sense. I had a look into how Three.js does it and it seems ultimately they are also relying on opentype.js to preprocess font files into points then geometries first.

An alternative that may be easier or much harder is to fork opentype.js to bring it more up to date but we may not have enough expertise or availability amongst ourselves to get new features into it in the near term. The best I can think of for forking is to make it use latest JS features so that we can potentially tree shake the library during bundling to make it smaller, for variable fonts I may need to read up on the font file spec.

davepagurek commented 1 month ago

Actually, looks like in the past few weeks they added support for variable fonts! https://github.com/opentypejs/opentype.js/pull/699

limzykenneth commented 1 month ago

We're gonna need a new release on NPM for it.

davepagurek commented 1 month ago

haha hopefully by the time we get around to implementing it they'll have released a new version! Worst case, you can technically use a git dependency via a URL + commit hash in package.json, but my experience doing so has been kinda buggy when you update the hash (removing and re-adding the dependency seemed the most consistent way to get it to update), so it wouldn't be a long-term solution.

nickmcintyre commented 1 month ago

Could TinySDF be helpful?

If we were to use this library or a similar 2D canvas-based approach, then a (maybe too) simple fix for zooming could be to render the text much larger by default. Size/zoom could be adjusted by scaling the 2D canvas. There'd be a tradeoff with the additional memory used for the larger 2D canvas and the math used for scaling.

davepagurek commented 1 month ago

Could TinySDF be helpful?

definitely! Any SDF texture will be more useful to us than a regular texture because we can draw that at different sizes and still get a crisp line, and increasing the size of the texture will just give us a more accurate line at large zooms, which seems like something we could reasonably make a function for to let people set it higher if they need that. If we're looking to ditch OpenType then that's probably the best compromise approach for WebGL.

It probably will be slower at doing variable font changes than a 2D canvas though since it has the additional step of running the rendered character through a distance transform before it can be used for rendering, but I think that's also a pretty OK tradeoff. People can always render text to a 2D p5.Graphics and draw it into a 3D main canvas if they want better variable font speed at the cost of having to manually handle scaling/pixel density.

agrshch commented 1 month ago

This is amazing that there is a plan to remake the typography module, it's much needed! Thank you so much for bringing this up! As a designer, I would be incredibly happy to have within p5js direct access to opentype features, variable font axes, letter spacing, built-in kerning, etc. This will be a really huge leap forward!

davepagurek commented 1 month ago

The kerning/spacing thing is also something important we haven't discussed in depth here yet. For 2D mode we can rely on native tools, which is great. For other renderers, currently we'd need to be doing all of that manually, finding a library to do that (last I looked into it there weren't too many js options, although cross-compiling a native library is an option), or having a reduced feature set in non-2D renderers. Anyone have any thoughts on the time/consistency/filesize tradeoffs of those options?