colr-gradients-spec icon indicating copy to clipboard operation
colr-gradients-spec copied to clipboard

stroke: discussion about inclusion in COLRv1

Open Lorp opened this issue 3 years ago • 20 comments

I would like to kick off a discussion of pros and cons of adding stroke effects to COLRv1.

When COLRv1 fonts become a reality, inevitable comparisons will be drawn between OpenType-SVG and COLRv1 as colour vector font formats. COLRv1’s advantages in terms of file size, processing efficiency and variations will be weighed against OpenType-SVG’s installed base and its flexibility in handling most types of SVG source artwork. Notably, designers will need training on certain common features that may not be used when exporting artwork for use in COLRv1 fonts:

  • bitmaps (proposed in #272)
  • blur, useful for shadows (proposed in #270)
  • stroke (no discussion that I can see, so starting it here)

Of course, all the above can be simulated with outlines (e.g. picosvg) but with significant loss of efficiency in terms of file size and variability.

Native strokes with variable stroke-width would have several advantages which may be compelling in a font context:

  1. they require less data to represent than the equivalent outline
  2. they handle variation in stroke-width efficiently (useful for certain weight/grade adjustments, and optically sized emoji and other icons)
  3. they handle variation in shape efficiently
  4. they handle stroke effects such as dashed lines efficiently

I propose adding native strokes to the COLRv1 format. Stroke paths would be defined in glyf, CFF or CFF2 tables using the same data structures as outlines. Similarly, variations in the positions of strokes’ control points would be handled with gvar or CFF2.

As far as changes to the spec are concerned, I believe only a new PaintStrokeGlyph table format would be needed. This would clone PaintGlyph but add:

  • a VarFixed field for stroke-width
  • control over whether to self-close contours
  • perhaps (variable?) control of dashed lines

Comments welcome.

Lorp avatar Apr 20 '21 23:04 Lorp

Concern: none of the outline formats support open contours.

justvanrossum avatar Apr 21 '21 14:04 justvanrossum

Stroke functionality like this would be greatly appreciated!

Last year I made a variable COLR/CPAL font called Gimlet X-Ray (https://djr.com/gimlet-xray) that had a variable stroke width. Even though I was able to generate the necessary outline contours with my font editor, it ended up being quite labor-intensive to keep them compatible and make sure they didn’t self-intersect. And as Laurence alludes to above, all of the additional glyphs had lots of points and lots of deltas, which added quite a bit to the filesize.

Having the functionality that Laurence describes in COLRv1 would have made this process much easier and the result much more efficient. And while Gimlet X-Ray is admittedly a niche design, I think there are plenty of more practical use cases that would benefit from this as well, not least of which is my typeface Bungee (https://fonts.google.com/?query=bungee) which was produced as an early COLR/CPAL font with several outline variants.

And I’m not the only one thinking about it: https://twitter.com/swelltype/status/1382125370716065793

(Also...just saw Just’s post as I was typing mine...while building up skeleton fonts would be a cool possibility, fwiw this would still be useful to me even without open contours)

djrrb avatar Apr 21 '21 14:04 djrrb

Concern: none of the outline formats support open contours.

I think that’s covered by my line about “control over whether to self-close contours”. I imagine a bit or bit field in the PaintStrokeGlyph table that would decide whether to close contours that are not explicitly closed.

Lorp avatar Apr 21 '21 15:04 Lorp

Even without usable open contours, I believe this could still be quite helpful. Strokes would ease the design process and make stroke-based characters visually more coherent. The extra processing power required and diminished rendering quality of outlines (especially at very thin widths) have always been a weak point of layered and illustrative type. I especially ran into this while working on various styles of my Gimme family.

Strokes would also fix trapping errors, as inner and outer fills do not always align perfectly (allowing backgrounds to seep through along the edges, even more so when variations come into play such as with my Variable Initials). For this I usually add an excess of color background, but a stroke would be less destructive to the original paths. Only for tapered lines or other stroke effects an outline would be needed.

Combined with variables this would become even better: I'm currently working on a variable color version of Schijn and juggling the outlined strokes (making them disappear to zero and transforming their position to adjust the bevel) is quite something, especially keeping all the lines and fills coherent.

ArthurTypearture avatar Apr 21 '21 15:04 ArthurTypearture

control over whether to self-close contours”. I imagine a bit or bit field in the PaintStrokeGlyph table that would decide whether to close contours that are not explicitly closed.

Neither glyf not CFF nor CFF2 have any closing operation: all paths are always implicitly closed. For glyf a point flag could be added, to be set for the first point of each contour. A bit field may indeed be needed, esp for CFF/CFF2.

justvanrossum avatar Apr 21 '21 15:04 justvanrossum

To be clear, I am not proposing any changes to glyf or CFF(2). The flags I am talking about would be in the PaintStrokeGlyph subtable within the COLR table. A stroked square shape would be represented in glyf by either a contour of 5 points (points 0 and 4 having the same position) without looking at any control bits, or a contour of 4 points and a “self-close” bit determining that contour 0 (or maybe the whole glyph) is self-closing. Without that self-close bit, that 4-point contour, when layered via PaintStrokeGlyph, would render as a stroked square with one side missing.

Lorp avatar Apr 21 '21 16:04 Lorp

While I am a big enthusiast of the stroke-based features in font editors, I am not so sure about having it in the export format. Closed contours is one obvious problem, as Just pointed out.

Another problem is an implementation of the stroke operations in editors. "Standard" stroke (SVG-style thickness-3caps-3joins) has very limited use while working with fonts (we really need more), so conversion to export data will almost universally mean "rendering" of the stroke into the path.

yarmola avatar Apr 22 '21 07:04 yarmola

I would like to kick off a discussion of pros and cons of adding stroke effects to COLRv1.

I think this belongs in another forum, as I don't see it in scope for COLR v1. Perhaps COLR v2, but perhaps stroke fonts should be supported in a Strk table or glyf v2 or etc.

@skef I wonder that arc joins might be relevant to a stroke font effort :)

davelab6 avatar Apr 23 '21 16:04 davelab6

@davelab6 Given my own experiences with the new joins I would say it depends on what layer is going to be doing the rasterization with what input. If the hope is that the "source spline" can be handed off to the existing vector rasterizers (Cairo, Skia, etc.) then you're probably stuck with the old PostScript caps and joins unless you have a lot of corporate traction in your standards body. We're definitely in a corporate-driven standards phase right now.

If you're willing to expand the stroke into a path before handing it off then the sky's the limit.

The SVG 2 joins (at least) would be a good addition. This area has been static for too long.

For anyone curious about this area I have a handy html/js reference implementation: joins.zip. The spec is here.

skef avatar Apr 24 '21 00:04 skef

"Standard" stroke (SVG-style thickness-3caps-3joins) has very limited use while working with fonts (we really need more)

+1, and I suspect defining a version sufficiently powerful to please type designers to be non-trivial.

rsheeter avatar Apr 28 '21 17:04 rsheeter

For context, our COLR v1 compiler converts strokes to equivalent paths currently.

rsheeter avatar Apr 28 '21 19:04 rsheeter

I know it's déclassé but anyone interested in stroke algorithms should glance at the work I did to replace the previous system in FontForge last year. The current system supports the SVG joins as well as arbitrary convex nibs and generalizes the two systems: You can use a rectangular nib or any other convex nib and still choose any of the traditional or new cap and join styles. ("Round" joins become the least eccentric compatible elliptical arc.)

Even if you're allergic to FontForge generally it wouldn't be hard to round-trip through the Python API from other tools.

Documentation is here.

skef avatar Apr 28 '21 19:04 skef

The thought process is: we have a reasonably efficient “path store” (glyf/CFF2), and a table to control non-default rendering of those paths (COLR), so why not? Speccing strokes can all be done in COLR, starting with SVG style strokes + width variation (including cap & join styles and per-contour flags for open/closed). Indeed this is not all that type designers want from strokes, but this well-known subset does cover an awful lot of the use cases that SVG covers, is trivial to hand off to system rasterizers or indeed SVG, and is easy to create with graphics apps. The semantic loss in converting strokes to outlines seems significant, particularly considering stroke-width variation. I get that it’s a late suggestion to a proposal that is making its way to standardization!

Lorp avatar Apr 29 '21 17:04 Lorp

Stroking is very very expensive because the drawing borders are no longer cubic or quadratic Bezier curves. Even approximations are expensive. This is the reason why stroked fonts never gained any traction. Remember Type 3, or vector Windows FON, or metafont? I am against stroke in fonts. Mimicking SVG at all (rather large) costs is not a good idea.

apodtele avatar Aug 03 '21 22:08 apodtele

@apodtele On the other hand a stroked font spec could allow certain variations that would be intractable or at least very difficult with existing technology.

Right now one can stroke the masters of a variable font "pre-delivery" but then one has to make all the points match up, which using most technologies would require a great deal of post-processing. (I know of one system that preserves 1:1 points for the case of different weights (not the general case) but it does so at the expense of accuracy.)

If your rasterizer can stroke, however, it can interpolate the "source points" of the skeleton masters first (with "weight" presumably being a special-purpose axis controlling a width parameter) and only then stroke the geometry, so there are no match-up issues.

skef avatar Aug 03 '21 22:08 skef

Will complex strokes be allowed, like all the modes supported by MFEK/stroke?

It seems we only consider to support CWS mode (not VWS or PAP). I think all three are valuable.

Also, will custom caps be allowed? Very valuable for making arrows and serifs, and for CJK.

VWS is valuable for CJK, see https://atadistance.net/2021/07/18/stroke-fonts-forever/

PAP is just fun, and most PAP fonts are giant in practice, so that's where most savings can be seen.

cc @matthewblanchard

ctrlcctrlv avatar Oct 25 '21 15:10 ctrlcctrlv

Oh, also @skef discussed NIB mode of MFEK/stroke which I get by linking to libfontforge.so, so you don't even really need FontForge Python API for it. It might be possible to make a pared down libfontforge.so providing only what is needed for nib stroking, I call this non-existent but possibly existent library in my head libskef.so ;)

We haven't figured out how to do this in vector but it should be easier in raster, to combine NIB and VWS modes, as well. So, there are CWS, VWS, PAP, NIB, NIBVWS, five possible modes. I haven't thought of any other generally useful mode, and I've been thinking about this for years.

ctrlcctrlv avatar Oct 25 '21 15:10 ctrlcctrlv

Another issue with strokes (besides non-trivial curve polynomials) is the associated singularities of miter joints with uncertain drawing boundaries. At the very least the boundaries are not trivial to calculate. It is another complication for rasterization. All things considered... At least consider all complications. These are fonts after all. Even OT-SVG has limits.

apodtele avatar Oct 25 '21 16:10 apodtele

Just FYI: I'm likely going to have some time over the next two months and one project I'm considering is a port of the stroke code to a fork of the Skia path ops infrastructure (basically just reusing their spline classes and so on). Zero promises at this point but it could happen.

Most of the stroke logic is straightforward and poses no great porting challenge. It does use some questionable FontForge interfaces The two potential sticking points are what to do about the fitting code and how to sort out spline direction at the end -- although path ops itself might have a solution for the latter.

skef avatar Oct 25 '21 19:10 skef

@apodtele The SVG 2 miter-clip convention seems like a good means of providing boundaries when they're needed.

skef avatar Oct 25 '21 19:10 skef