Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify whether STAT Format 2 Axis Value Ranges are inclusive or exclusive #466

Closed
punchcutter opened this issue Jul 22, 2020 · 15 comments
Closed

Comments

@punchcutter
Copy link

The spec does not clarify whether Format 2 Axis Value ranges are inclusive or exclusive. This affects the discussion of overlapping ranges because if we assume inclusive then, for example, ranges 400-500 and 500-600 overlap, but if we assume exclusive then these do not overlap. Also, if ranges are exclusive then a nominal value that matches the max value would be invalid. Some discussion related to this is at Lorp/samsa#46


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

@Lorp
Copy link

Lorp commented Jul 29, 2020

I think it’s clear from the spec that Format 2 is inclusive:

  • rangeMinValue : The minimum value…
  • rangeMaxValue : The maximum value…

This makes sense, otherwise a range intended to include the axis max would be inconvenient to define.

What seems to be missed by the spec is that contiguous ranges (which are of course common) have zero overlap in continuous maths but greater than zero overlap in the discrete maths of 16.16 numbers. As I mentioned in the other issue, the spec’s:

Two format 2 tables for a given axis should not have ranges with overlap greater than zero

… is frequently violated, but a careful reading of the “error handling” (where T1 and T2 are named confusingly in the non-intuitive order!):

If the range of T1 overlaps the higher end of the range of T2…

… leads to the higher range being used, thus 500 triggers the 500-600 range in the example in the original post.

I would recommend improving the STAT Format 2 spec to clarify how contiguous ranges are handled, and improve the phrasing of the general handling of overlapping ranges.

BTW I am surprised that axisValueTable records can be in any order. This would seem to make life harder for implementors.

@sairuspatel
Copy link

sairuspatel commented Aug 31, 2020

The spec’s wording for overlapping ranges is a bit confusing, since it isn’t clear whether touching ranges are considered overlapping. Also, if overlapping ranges aren’t allowed, why are there rules for what to do if the ranges do overlap? Perhaps consider replacing:

Two format 2 tables for a given axis should not have ranges with overlap greater than zero. If a font has two format 2 tables for a given axis, T1 and T2, with overlapping ranges, the following rules will apply:

  • If the range of T1 overlaps the higher end of the range of T2 with a greater max value than T2 (T1.rangeMaxValue > T2.rangeMaxValue and T1.rangeMinValue <= T2.rangeMaxValue), then T1 is used for all values within its range, including the portion that overlaps the range of T2.
  • If the range of T2 is contained entirely within the range of T1 (T2.rangeMinValue >= T1.rangeMinValue and T2.rangeMaxValue <= T1.rangeMaValue), then T2 is ignored.
    In the case of two tables with identical ranges for the same axis, it will be up to the implementation which is used and which is ignored.

by something like:

Two format 2 tables for a given axis may have ranges that touch (the rangeMaxValue of one range is the rangeMinValue of another), but the ranges must not overlap any more than that. In the case of ranges that touch:

  • At most one of the ranges may have nominalValue set to the axis value at which the ranges touch.
  • The higher range must be used for the axis value at the point at which the ranges touch, unless the lower range’s nominalValue is set to the value at which the ranges touch, in which case the lower range must be used.

For example, using the notation {nominalValue, rangeMinValue–rangeMaxValue}:

  • For {700, 650–750} and {800, 750–800}, the second range will be used for value 750.
  • For {800, 750–800} and {900, 800–900}, the first range will be used for value 800.

Question: Would a font designer/producer want a different string just for the point at which the ranges touch? That is, use one string for the first range, use another string for the touching range, but use yet another for just the point at which the ranges touch? Are there compelling examples for this?

Note: If we did want that kind of ability, one way we could do it would be to allow for a format 2 just for that one point, e.g. {750, 750–750} and rework the rules to allow for that. Or we could have a format 1 just for that point and rework things.

@Lorp
Copy link

Lorp commented Aug 31, 2020

Good rewrite. Forbidding overlaps apart from touching overlaps is a good move.

I can think of a compelling case where we would want want a different string just for the point at which the ranges touch. Consider a font with a slnt axis that has backward slant as well as forward slant. Then we can imagine its STAT setup as follows using your syntax:

{-12, -12–0} // name = "Oblique"
{0, 0–0} // name = "Upright"
{12, 0–12} // name = "Backslant"

As you imply, the Upright range would be better represented as a format 1. I would suggest that format 1 and format 3 records take precedence over format 2 records.

@PeterCon
Copy link
Collaborator

PeterCon commented Sep 3, 2020

If a font has two format 2 tables for a given axis, T1 and T2, with overlapping ranges...

(Who wrote that confusing text?)

I think the intent of that text might have been what to do in the event that a font failed to follow the recommendation and did have two tables with overlapping ranges.

(Something that's strange, though, is that the wording in the first bullet point seems to make it encompasses the case described in the second bullet point. Makes me wonder if the original intent for the first bullet point was something different.)

@PeterCon
Copy link
Collaborator

PeterCon commented Sep 3, 2020

I strongly suspect that, in this bullet point

If the range of T1 overlaps the higher end of the range of T2 with a greater max value than T2 (T1.rangeMaxValue > T2.rangeMaxValue and T1.rangeMinValue <= T2.rangeMaxValue), then T1 is used for all values within its range, including the portion that overlaps the range of T2.

that T1.rangeMinValue <= T2.rangeMaxValue was meant to read T1.rangeMinValue >= T2.rangeMaxValue. In other words, the ranges overlap, but one is not a sub-range of the other. (As it reads now, T2 is a sub-range of T1.)

@PeterCon
Copy link
Collaborator

PeterCon commented Sep 3, 2020

Here's my proposed changes for the next version:

The range specification of a format 2 table is inclusive: both the minimum and maximum values are included within the range. Two tables for a given axis can have ranges that touch (the rangeMaxValue of one range is the rangeMinValue of the other), but ranges should not overlap more than that. In the case of two ranges that touch:

  • At most one of the ranges should have the nominalValue set to the axis value at which the ranges touch.
  • When the requested axis value is the value at which the ranges touch, the higher range must be used unless the nominalValue for the lower range is set to the value at which the ranges touch, and the nominalValue for the higher range is greater than that value.

Two format 2 tables for a given axis should not have ranges with overlap greater than zero. If a font has two format 2 tables for a given axis, T1 and T2, with overlapping ranges, the following rules will apply:If two format 2 tables have ranges for the same axis with non-zero overlap, then the following guidance is recommended for applications:

  • If the range of T1 overlaps the higher end of the range of T2 with a greater max value than T2 (T1.rangeMaxValue > T2.rangeMaxValue and T1.rangeMinValue <= T2.rangeMaxValue), then T1 is used for all values within its range, including the portion that overlaps the range of T2.
  • If the range of T2 is contained entirely within the range of T1 (T2.rangeMinValue >= T1.rangeMinValue and T2.rangeMaxValue <= T1.rangeMaValue), then T2 is ignored.
  • If two tables have identical ranges, the application should consistently choose one and ignore the other, by its own criteria.
  • Else, if the range of one table is entirely contained within the range of other, then the table with the smaller range should be ignored.
  • Else, for axis values within the overlapping range, use the table with the higher range (either rangeMinValue or rangeMaxValue is higher).

In the case of two tables with identical ranges for the same axis, it will be up to the implementation which is used and which is ignored.

@PeterCon PeterCon closed this as completed Sep 3, 2020
@PeterCon
Copy link
Collaborator

PeterCon commented Sep 4, 2020

Btw, re this from Sairus' proposed text:

  • At most one of the ranges may have nominalValue set to the axis value at which the ranges touch.

I wasn't sure whether this was intended in the sense of "it MUST be that at most one..."

I went with "should" wording so as not to assert fonts that don't follow this are invalid, and then adjusted the following two bullets so that the guidance still covers the case of a font that doesn't follow the recommendation.

@sairuspatel
Copy link

Thanks, Peter,

  1. When my proposed text said “At most one of the ranges may have nominalValue set to the axis value at which the ranges touch”, it was indeed intended in the sense of “it must be that at most one...”

    Is the concern with this (your proposal changed it to “should”) that something overly drastic would happen – e.g. a font engine rejecting the entire table or the entire font – if STAT wasn’t constructed in that “must” way? I guess this gets at what conformance means, or how conformance is (or should be!) enforced, a long-standing open issue with OT.

    To me, “should” sounds like a recommendation and that it would still be valid for a font to have both ranges set their nominalValue to the point at which they abut, which doesn’t make sense when one considers what a nominalValue is supposed to be.

  2. The same goes for your softening my proposal prohibiting ranges from overlapping to a “should,” and even going further, by giving guidance as to what engines should do when the ranges do overlap (which has already been recommended against). That seems to be sending mixed messages.

    I do see “must” used in several places in STAT already. Or do you think that those are deeper requirements than the ones we’re talking about (some of them do seem to be), which is why you think these should be “should”s?

  3. An interesting case of “overlapping ranges” happens, as Laurence has suggested, when a format 1 axis value occurs withing the range of a format 2 of the same axis. Which should be preferred, or should we disallow it (my preference right now), and say for that situation the font producer has to break up the format 2 into two format 2s, with the format 1 value at the point of abutting?

    Your recommendation of what to do if ranges overlap includes:

    “Else, if the range of one table is entirely contained within the range of other, then the table with the smaller range should be ignored”

    Perhaps the smaller range was intended to be an “override” for the larger range, much as a format 1 could be an override within a larger range.

    And of course all the above has to include the possibility of format 4 being used as a superset of format 1!

Let me know what you think. I’m not totally against the “should” language, so (3) is probably the most important point here.

@PeterConstable
Copy link

And of course all the above has to include the possibility of format 4 being used as a superset of format 1!

The intent of format 4 was not as a (less efficient) format 1. So, the spec can be clear that it is not to be used that way.

Per review feedback, adding the following after the description of format 4:

The axisCount value must be greater than 1.

@Lorp
Copy link

Lorp commented Sep 29, 2020

The axisCount value must be greater than 1.

Why is this necessary?

Whatever the intent, when a flexible representation is a natural superset of a simpler one, why forbid it from acting as the simple one? In general, such a facility is useful and meaningful, and can lead to better UI.

@PeterConstable
Copy link

This reflects the design intent and that the definition of format 4 makes it a superset of format 1 (and only 1).

How is it useful to have two ways to express the same thing? How can it be more meaningful or lead to better UI?

@Lorp
Copy link

Lorp commented Sep 29, 2020

It probably makes little difference in this case, but in general it seems not to be useful to have such restrictions. One might prohibit many representations that have other supposedly more efficent or more normative forms (e.g. no collocated outline points, no lookups that are not used by features, no glyphs with uncompressed flags), which are at worst harmless and at best useful for performance or other reasons.

@PeterConstable
Copy link

Fair enough: relaxing "must" to "should".

@PeterConstable
Copy link

I'm reviewing to make sure Laurence's use case is accommodated involving two contiguous ranges but with a distinct name for the point at which the ranges meet. As a partial fix, adding this in the discussion of format 2 after the first two bullet points in the revision I gave 26 days ago:

Similar behavior is used if the value of a format 1 or format 3 table touches the range of a format 2 table:

  • If the value of a format 1 or format 3 table is equal to the rangeMaxValue of a format 2 table, the format 1 or format 3 table is used.
  • If the value of a format 1 or format 3 table is equal to the rangeMinValue of a format 2 table, the format 1 or format 3 table is used except if the nominalValue of the format 2 table is also equal to the rangeMinValue.

@PeterConstable
Copy link

And I've revised wording in an earlier portion that has been revised for issue #576: the first bullet in the proposed revision for that issue is split into two, as follows:

  • There should not be a format 1 table with axisIndex and value that match the axisIndex and value of a separate format 3 table, or that match the axisIndex and value in a format 4 table that has an axis count of 1.
  • There should not be a format 1 table with the same axisIndex as a separate format 2 table if the format 1 value is greater than the rangeMinValue and less than the rangeMaxValue of the format 2 table. The format 1 value may be the same as minimum or maximum of the format 2 range, but the format 2 nominalValue should be different.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants