Open Lorp opened 4 years ago
YES. Proper rotation would be huge!
Better support for rotation in variation (continuous variation of an angle θ) sounds useful, but it doesn't seem like burying it within the COLR table would be exactly what you want. I mean, it would be possible to have behaviour like a non-colour font—the glyph fill is only ever text foreground colour; but needing to add COLR and CPAL to get that seems kludgy.
What makes rotation as variation difficult now is that the basis function used to calculate variation scalar coefficients is strictly linear (in a single active segment—a "hockey stick" function—or in two segments going from 0 to 1 to 0—a "tooth" function). What if the format were extended to support non-linear basis functions?
Future-proofing this new format, with capabilities that are called for in ‘glyf’, is reasonable I think. COLRv1 in fact already does this to an extent by enabling proper handling of white-on-black shapes, which is nothing to do with “colour”. As @davelab6 wrote here https://github.com/googlefonts/colr-gradients-spec/issues/3#issuecomment-694888194 : “I don't see an issue with CPAL v2 [sic.] edging ahead, and then updating glyf to match the capability set”.
So I would propose rotation in turns or degrees. I would submit that rotation and reflection are the most common use of the affine matrix, and should have been in ‘glyf’ all along. Reflection is satisfactorily represented in Affine2x2, but for rotation the representation is poor. If rotation was recorded directly in turns or degrees, many of the most common rotations are represented exactly, without semantic loss and therefore with variation potential.
So, perhaps you have in mind something like the following?
PaintTransformed table: | Type | Field name | Description |
---|---|---|---|
uint8 | format | Set to 6. | |
uint8 | flags | See below for flag details. | |
Offset24 | paintOffset | Offset to a Paint subtable, from start of PaintTransformed table. | |
Affine2x3 or VarFixed |
transform | An Affine2x3 record (inline), or a rotation angle, depending on flags—see below. |
Flags: | Mask | Name | Description |
---|---|---|---|
0x01 | TRANSFORM_AFFINE_2X3 | transform is an Affine2x3 record | |
0x02 | TRANSFORM_ROTATION | transform is a VarFixed value for a rotation angle, in clockwise radians | |
0x04 | TRANSFORM_X_REFLECTION | mirror in x-axis—ignored if an Affine2x3 is used | |
0x08 | TRANSFORM_Y_REFLECTION | mirror in y-axis—ignored if an Affine2x3 is used | |
0xF0 | RESERVED | reserved flag bits: must not be set |
A down-side of this design is that the size of the PaintTransformed table varies according to the flags set. On the other hand, if rotations are, indeed, the most commonly used, there'd be some size savings: 9 bytes vs. 56 bytes.
A (dx,dy) translation is still needed for reflections and rotations. It may be a good idea to reproduce the option in glyf
composites for deciding whether translation occurs before or after transforms, to help glyf
-style composites migrate easily to this structure when adding colour.
I would vote against radians, since it leads to irrational numbers for common rotations, and would recommend instead degrees (first choice, because of the excellent divisibility of 360) or turns (second choice).
A revision: this would add a variable-sized record, comparable to the ValueRecord used in GPOS:
PaintTransformed table: | Type | Field name | Description |
---|---|---|---|
uint8 | format | Set to 6. | |
uint16 | flags | See below for flag details. | |
Offset24 | paintOffset | Offset to a Paint subtable, from start of PaintTransformed table. | |
Affine2x3 or TransformParam |
transform | An Affine2x3 or TransformParam record (inline), depending on flags—see below. |
Affine2x3 record, as currently spec'd.
TransformParam record:
Type | Field name | Description |
---|---|---|
VarFixed | scaleX | |
VarFixed | scaleY | |
VarFixed | rotation | rotation angle, in clockwise degrees |
VarFixed | dx | |
VarFixed | dy |
The size of a TransformParam record instance is variable, depending on flags that are set.
Flags: | Mask | Name | Description |
---|---|---|---|
0x0001 | TRANSFORM_AFFINE_2X3 | transform is an Affine2x3 record | |
0x0002 | TRANSFORM_SCALE_X | transform is TransformParam record, with scaleX field | |
0x0004 | TRANSFORM_SCALE_Y | transform is TransformParam record, with scaleY field | |
0x0008 | TRANSFORM_ROTATION | transform is TransformParam record, with rotation field | |
0x0010 | TRANSFORM_TRANSLATE_X | transform is TransformParam record, with dx field | |
0x0020 | TRANSFORM_TRANSLATE_Y | transform is TransformParam record, with dy field | |
0x0040 | TRANSFORM_X_REFLECTION | mirror in x-axis—ignored if an Affine2x3 is used | |
0x0080 | TRANSFORM_Y_REFLECTION | mirror in y-axis—ignored if an Affine2x3 is used | |
0xFF00 | RESERVED | reserved flag bits: must not be set |
Flag bit 0 (0x0001) is mutually exclusive with other bits: if bit 0 is set, other bits are ignored.
Thanks for the continued conversation! True, the COLR
table doesn’t really make sense as a place to deal with rotated components. Yes, using the glyf
table does seem reasonable, as it would seem to sit nicely beside existing components attributes like x
, y
, and scale
(as labeled in ttx – I’m not very familiar with the formal TTF labeling, and feel mostly out of my depth in this conversation).
I do agree that degrees would be (by far) the most direct & widely-understood way to specify this.
In a discussion with @jfkthame, Jonathan suggested that we might want to specify skew in degrees as well. This would require something like TRANFORM_SKEW_X_DEGREES, TRANFORM_SKEW_Y_DEGREES.
I would be in favour of reducing the flags to only Affine2x3 and separate ones for those where degrees are needed (TRANSFORM_ROTATE, plus the two above) as the linear transformations for scale, translate reflection could be expressed as nested PaintTransforms if they need to be varied separately and otherwise appear to be duplicating what Affine2x3 does.
@jfkthame good addition.
@drott sounds good, Affine2x3 respresents reflection, translate and scale without semantic loss.
IIUC the key thing we want to provide rotation with the angle as something you can directly vary, as opposed to indirectly by forming a PaintTransformed to rotate?
If so I think we should add a new Paint, not glue this onto PaintTransformed which cleanly represents a 2x3 transform.
I also vote for a new Paint format, with flags that select the meaning of the VarFixed field
PaintRotateSkew, with one VarFixed and flags?
It needs three values: angle of rotation, and x/y coords of the center of rotation.
(I'd lean toward separate PaintRotated and PaintSkewed; I don't see any benefit to merging them and requiring a flag to indicate which transform is desired.)
x/y coords of the center of rotation
good point 👍
separate PaintRotated and PaintSkewed
I'm also ok with two different paints.
Pardon my density [kids up all night...] but I'm not clear why skew isn't adequately represented by a PaintTransform? - a static skew at an angle should be fine so I suppose it's specifically to make variation of angle of skew easier?
+1 to PaintRotate {angle, center} and if we need it, to Paint At Angular Skew as it's own paint.
Sure, it can be represented by PaintTransform, but so can rotation. The point is that it's hard to control variation of the angles in this form.
Got it, ty for confirming. For whatever reason the issue with controlling rotation angle made perfect sense and the issue with skew angle didn't quite connect in my head :)
If we're going this way, IMO it'd make sense to also have PaintScaled (four values, for x/y scale factors and origin of scaling), and PaintTranslated (two values), for ease of authoring (and slightly more compact representation, in the case where just a single transform is required). It's fine to still have the general PaintTransformed that can express an arbitrary combination of the transform building blocks, and tools can generate that when it makes sense (e.g. for a complex but static transformation), but it'll often be more author-friendly to work with the individual components.
For rotation, expressing the angle in degrees is clearly more amenable to smooth variation. But what's the particular benefit for expressing skews using angle rather than the matrix?
@PeterConstable smooth and geometrically correct faux italics along the slnt
axis :)
For scale and translate my immediate reaction is to be less enthused; it's only a nuisance if you hand-write your transforms. A tool can present them however it wishes and I don't immediately perceive the same issues with representing smooth variation in the underlying format.
So, might PaintRotate and PaintAngularSkew [that's a fun name that might need a visit to a bike shed] suffice?
@Lorp slnt example much appreciated :)
Right, scale and translate don't have the issue with variations; they'd purely be a more compact representation of these particular types of the more general transform. So I don't feel strongly about them one way or the other.
new commit for #113
Why is the rotation specified in clockwise degrees?
For reference:
(BTW, the SVG and CSS specs really should specify rotation direction. @jfkthame, perhaps you can recommend the best way to add this to the specs?)
Arbitrarily chosen for initial draft. (At least it wasn't left unspecified.) Or, actually, I think maybe influenced by HTML Canvas 2D (probably the last thing I looked at that explicitly gave the angular direction).
But anti-clockwise is probably the better chose, particularly for consistency with slnt, but also because it makes better sense when considering the matrix representation (where the xform of the i basis vector is typically expressed as (cos(θ), sin(θ)).
+1 to anti-clockwise
The CSS spec does indicate that rotate()
is specified clockwise, but I agree it's well hidden (and expressed as an example, rather than directly stated in spec text). I'd suggest filing an issue at https://github.com/w3c/csswg-drafts/ asking for it to be made more explicit.
As for SVG, I don't immediately see any mention of clockwise vs anticlockwise, though the spec does include examples that let one infer that it's clockwise. I'm not sure how best to raise that as an issue; maybe mail to www-svg@w3.org?
It's unfortunate that we have this discrepancy between standards that are likely to be used together, but I guess we're stuck with it. Here, I'd agree that anti-clockwise is probably the right choice.
I suggest we use VarFixed for the rotation’s centerX and centerY, not VarFWord. It was persuasively argued that dx and dy in the transform matrix should be VarFixed. Even a simple 90° rotation needs a resolution of 0.5 font units for centerX and centerY.
@Lorp VarFWord has already been changed to VarFixed (I saw you comment on this earlier).
and effective limitation to 90°
I wanted to object to this but then realized that I couldn't find a good explanation as to why this wouldn't be the case. I started writing a comment here about how HOI works (or at least one way it can work), but it got a bit long. So I put my response at https://bungeman.github.io/hoi.html . This shows how to do smooth interpolation along arbitrary contours of bezier curves (and discusses a bit about introducing discontinuities and their limitations).
Note that I'm not against variable rigid rotation of components about a variable point, there are many cases where that is exactly what is wanted so it's probably a good idea to support it. Just pointing out that HOI isn't limited in quite this way and it does have the advantage of being able to exactly follow other bezier contours of the glyph.
I can't argue with lack of editor support though... I created my HOI example font by hand in ttx and even then had to hex edit to get everything right.
Thanks for this, @bungeman.
Indeed I have misrepresented HOI in terms of its rotation abilities. Apologies for that, Underware. And I’ll bet Underware’s own build processes are pretty slick too, so there’s no theoretical objection there either.
I have some concerns that this proposal, in particular since it implies changes in how future b/w glyph tables handle component transforms, has not had enough eyes from those who are not following the evolution of COLR. I wonder if a Zoom meeting should be proposed, advertised on the MPEG-OTSPEC and OTFontVar lists at least, where the proposals can be discussed.
I'm extremely uncomfortable with the PaintSkew addition. Use of degrees to represent skew has always been a mistake. It was wrongly carried to the slnt
axis of varfonts because the italic angle was represented in degrees. That was an unnecessary mistake. Now we have a situation that any font that has slnt
axis needs to provide a avar
table to approximate the non-linear behavior of skew-expressed-as-angle.
Now I really want to hear ONE legitimate use of the proposed PaintSkew. It cannot meaningfully be used slnt
axis, for two reasons:
avar
is needed to correct the slnt
axis value as to be used in other parts of the font. So the angle value won't be available in COLR
table before avar2
is rolled out.I'm also very skeptical for many other reasons. I think this was done without fully thinking through the uses. I think every time we deviate from the OT1.8 variation model, it should be for very good reason and well-considered. I don't think that's the case here.
That said, the way this proposal has been modified since its inception also shows a lack of clear criteria. For example, minimizing the number of paint types seems to be given higher priority than to reduce bytes. Which sounds wrong to me. Given that "translate" transformations are the most common use of PaintTransform, I suggest PaintTranslate be added in.
Finally, I highly recommend that it be spec'ed that a font with COLR
table but no CPAL
table is a valid non-color font; basically sanctioning COLR
table as glyf2 for component use.
I don't think we should conflate rotations or other transforms in COLR with rotations used for components in glyph outlines. Within the COLR table, the transforms will be processed in the context of general 2D graphics operations, not a TrueType rasterizer. Note, for instance, that the COLR transforms affect how gradient fills are painted, not just how outlines are rasterized.
Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.
Interesting.
Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.
Approved!
@behdad:
COLR table but no CPAL table
What would it mean to use a PaintComposite with a blending mode in the case of a monochromatic rendering? What should be the effect of a PaintLinearGradient or PaintRadialGradient? What should be the effect of ColorIndex.alpha < 1?
@Lorp you may be amused that the reason this is on github is specifically to make it more accessible than it would be as something like a document submitted directly to ISO. Why? - so we can get more input from the community. Apologies if we haven't adequately advertised that we're working on it.
Given that "translate" transformations are the most common use of PaintTransform, I suggest PaintTranslate be added in.
I fear I may have overindexed on the angles are tiresome to vary aspect. Agreed that smaller size in bytes matters! I can send a PR for PaintTranslate.
sanctioning COLR table as glyf2 for component use
If we want to pursue this my immediate thought is that we could have a set of requirements that guarantee the only possible paint output is the foreground color. For example, absent CPAL:
We proposed a similar mechanism for components in the variable-components specifications we have been working on: https://github.com/BlackFoundryCom/variable-components-spec#transformation.
As @Lorp pointed out, there should be a unique way of achieving these transformations for components, we don't need 2 ways for doing the same thing.
How would you suggest we gather efforts on this matter? Our proposal concerns only components in OT so, maybe it's a more appropriate place where this particular rotation feature should go?
Finally, I highly recommend that it be spec'ed that a font with COLR table but no CPAL table is a valid non-color font; basically sanctioning COLR table as glyf2 for component use.
Interesting indeed.
I wonder, is "use COLR" vs "use non-COLR fallback" ever meant to be a possible explicit end-user option? (Or even an implicit option based on the output medium.)
If it is, then how does that affect "sanctioning COLR [...] for component use"? As soon as the component features of COLR are used for non-color use, you can't have that non-color use be used as a non-color fallback for color use (hope this still makes sense).
Of the various paint formats for COLR v1, not all would be relevant for monochrome glyph compositing.
Not relevant:
Partially relevant:
Unclear relevance:
PaintColrLayers:
PaintColrGlyph: On its own, this wouldn't add additional components into a composite. It seems to me it would only be useful as a convenience for referencing another graph of paints, which could be done with PaintColrLayers. For color glyphs, PaintColrGlyph has a potential benefit that a runtime might render and cache the color glyph it references, but that wouldn't be relevant for variable components since the re-used paint graph would be subject to composite-specific variations.
@PeterConstable COLR v1 requires the DAG to assemble anything interesting so I think you want PaintColrLayers and PaintColrGlyph still. +1 that you likely don't want blend modes.
@JeremieHornus I took the liberty of editing your comment to enable the link, it was taking me to https://github.com/googlefonts/colr-gradients-spec/issues/url.
As soon as the component features of COLR are used for non-color use, you can't have that non-color use be used as a non-color fallback for color use
I think that's right. The glyph in the outline table (glyf or cff) that corresponds to the base glyph can contain a fallback but you couldn't use COLR capabilities to define it. It's not immediately obvious to me this presents a major problem, @justvanrossum do you forsee it does?
As soon as the component features of COLR are used for non-color use, you can't have that non-color use be used as a non-color fallback for color use
I'm inclined to think that use of COLR table itself for non-colour use would not be such a good idea: if you wanted a color font that also uses the COLR tables for non-colour composites, you'd be combining data for distinct purposes into the same table. That would make things harder for tooling, but would also introduce new validation issues—e.g., one more vector (via PaintGlyph) for introducing cycles into the graph.
But having some of the COLR paint formats be used in common with another table wouldn't be a problem.
I'm inclined to think that use of COLR table itself for non-colour use would not be such a good idea [ ... ]
Only if it is a requirement that one can have color and monochrome versions of glyphs. That is pretty much my question. If the answer to that is yes, then "sanctioning COLRv1 [without CPAL] [...] for component use" imposes a restriction.
@rsheeter: to use COLRv1 only to do more fancy compositing than glyf
is capable of, to me implies it makes little to no sense to have a fallback in glyf
or CFF
.
if you wanted a color font that also uses the COLR tables for non-colour composites
I read the suggestion to be to allow an exclusive choice between being a color font and and a non-color font based on presence of CPAL, not a hybrid.
to use COLRv1 only to do more fancy compositing than glyf is capable of, to me implies it makes little to no sense to have a fallback in glyf or CFF.
Fair point. Thinking "aloud" I think I would expect that if we developed a glyf2 it wouldn't have a fallback, it just wouldn't work if the client doesn't support it so ... perhaps that's OK?
Btw. my angle is from our Variable Components proposal, where we add more interpolation-friendly transformations (not unlike COLRv1) as well as the ability to specify the component's variation space location (not unlike Glyphs.app's Smart Components).
As @Lorp pointed out, at least the transformation part overlaps somewhat with COLRv1. But perhaps more importantly, the entire idea of "doing components better" has a lot in common, even if the approaches are quite different. I'm now thinking, what if COLRv1 would allow setting the component's variation space location? It could swallow our proposal whole...
It could swallow our proposal whole...
Would that be a desirable positive outcome from your pov?
I personally think it would be positive. The most important it that the fonctionality exists and is available.
I'm inclined to think that use of COLR table itself for non-colour use would not be such a good idea: if you wanted a color font that also uses the COLR tables for non-colour composites, you'd be combining data for distinct purposes into the same table.
The distinction between what will end up be a color glyph vs. what is a non-color composite is only relevant to an end user, based on the rendering results. The rendering engine itself will simply process the data encoded in COLR table, and the COLR table can be used as is to encode both color glyphs and non-color composites. If a font developer wants to do this (e.g. to have color glyphs and proper white on black glyphs) - there is nothing in the COLR table today that can stop this from happening.
Only if it is a requirement that one can have color and monochrome versions of glyphs.
Isn't it true for any font that combines regular glyphs with color emoji? One can have regular monochrome glyphs, white on black glyphs, and color emoji encoded in the same font, and both color and white-on-black glyphs can use COLR table to encode color glyphs and solve the contour overlap issues.
I read the suggestion to be to allow an exclusive choice between being a color font and and a non-color font based on presence of CPAL, not a hybrid.
My interpretation was different - to simply allow fonts with COLR table, but without CPAL, be considered as valid fonts. Obviously, this would only work for non-color composites and we'd need to define what the implementation should do if it encounters color glyph descriptions that cannot be rendered without CPAL, but as far as hybrid designs are concerned, it's really up to a designer - I don't see how we can prevent this.
Would that be a desirable positive outcome from your pov?
It could be positive, as it may be easier to get traction for one OT addition than for two. COLRv1 seems well on its way to be accepted, so if we can add "one more thing", that could be a route to get our desired functionality in, with possibly less friction than a completely separate OT addition.
On the other hand, it depends on the willingness of rasterizers to implement/activate COLRv1 even in a monochromatic environment. In other words, can it really catch on as "glyf2 for component use"?
We should probably first clarify whether there is an intention at all among the stakeholders that COLRv1 could legitimately used as "glyf2 for component use"?
In the context of COLRv1 being a superset of the features of composite glyphs, I wonder if it is too late to consider adding the possibility of rotation by a numeric value, rather than using the affine transformation matrix? Especially when combined with a variation axis, it would be useful not just for arrows, ornaments and clock faces, but also to enable chained rotations, so that mechanical objects, such as animal skeletons, can be represented efficiently. Such freedom of movement must currently be represented by multiple keyframes or by higher order interpolation (HOI). The former suffers from inaccuracy, the latter from an opaque build process, lack of support in UI, and effective limitation to 90°.