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

[instancer] Setting a new default location seems to disrupt axis mapping in a "binary" axis #3198

Open
arrowtype opened this issue Jul 5, 2023 · 5 comments

Comments

@arrowtype
Copy link

arrowtype commented Jul 5, 2023

I have set up a “binary” on/off variable axis with axis mapping in a designspace. It seems to work well!

    <axis name="Italic" tag="ital" minimum="0" maximum="1" default="0">
      <labels>
          <label uservalue="0" name="Roman" elidable="true" />
          <label uservalue="1" name="Italic" />
      </labels>
      <map input="1" output="1"/>
      <map input="0.5" output="1"/>
      <map input="0.4999999" output="0"/>
      <map input="0" output="0"/>
    </axis>

However, if I then use the fontTools.instancer to add a new default location to this variable font, the ital axis suddenly goes back to being continuous. Here is a comparison:

Screen.Recording.2023-07-05.at.10.42.48.AM.mov

The bottom line is acting as expected, with the ital axis snapping from upright to italic at 0.5. The top line is instanced to have a default wght of 400, but its ital axis is acting as a continuous/smooth axis, which is not intended. I have replicated this result with https://wakamaifondue.com/beta/, as well.

This is especially strange because if I use TTX to look at the avar tables of each font (before/after the instancer), there is no change.

  <avar>

    <!-- (wght and opsz mapping are here) -->

    <segment axis="ital">
      <mapping from="-1.0" to="-1.0"/>
      <mapping from="0.0" to="0.0"/>
      <mapping from="0.5" to="1.0"/>
      <mapping from="1.0" to="1.0"/>
    </segment>
  </avar>

Here’s the code I’m using to set this instance:

import sys
from fontTools.ttLib import TTFont
from fontTools.varLib import instancer

# get fontpath passed in
fontpath = sys.argv[-1]

# open the font in memory
varfont = TTFont(fontpath)

# determine the min and max wght axis values
for axis in varfont["fvar"].axes:
    if axis.axisTag == "wght":
        minWght = axis.minValue
        maxWght = axis.maxValue
        oldDflt = axis.defaultValue

# create a new font with a default of 400 for the wght axis
newFont = instancer.instantiateVariableFont(varfont, {"wght": (minWght, 400, maxWght)})

## (some stuff to edit the name table)

# save the new font, with same path as input font
newFont.save(fontpath)

Version: fonttools==4.40.1.dev0. Once I file this issue, I’ll poke around and see if I can update to the latest development branch, and I’ll note if that makes a difference. Update: To confirm, I just pip installed the latest in the L4-fixes branch, and got the same result.

I’m happy to send along fonts in an email, if helpful! Let me know. Thanks!

@anthrotype
Copy link
Member

are you sure the avar tables before/after are the same? I would expect that for a discrete change to happen the original avar that you say is working should have a mapping for <map input="0.4999999" output="0"/> as in the input DS axis, e.g.:

   <avar>

     <!-- (wght and opsz mapping are here) -->

     <segment axis="ital">
       <mapping from="-1.0" to="-1.0"/>
       <mapping from="0.0" to="0.0"/>
+      <mapping from="0.4999" to="0.0"/>
       <mapping from="0.5" to="1.0"/>
       <mapping from="1.0" to="1.0"/>
     </segment>
   </avar>

maybe that gets lost somehow in the instancing. Have you tried to use fewer decimal places? Maybe your "0.4999999" gets rounded to "0.5" as it goes through float => Fixed2Dot14.
Indeed floatToFixed(0.5, 14) and floatToFixed(0.4999999, 14) return 8192, whereas you want the second to be just one unit apart, 8191. With 14 bits for the fractional part, the granularity is I believe up to 4 decimal places, 1/(2<<13) to be precise. So I believe 0.4999 should do the trick.

@anthrotype
Copy link
Member

0.49994 to be precise

from fontTools.misc.fixedTools import floatToFixed, fixedToStr

bits = 14
v = 0.5
fv = floatToFixed(v, bits)  # 8192
v2 = float(fixedToStr(fv - 1, bits))
print(v2)  # 0.49994

@arrowtype
Copy link
Author

Wow, thanks for figuring out and sharing the specifics on decimal accuracy!

And actually, yes, when I make that change to my mapping attributes, the instancer works out without any issues!

I feel a bit silly... I had actually figured out that the most accurate I could get in the designspace, and have the discrete axis work in the non-instanced output, is 6 decimal places. The code I copy-pasted was from a note to myself about what I was initially doing wrong. But, it’s great to know more specifically that I should probably go to .49994.

And, after making that adjustment, things work well even after the instancer step. Perfect!

Screen.Recording.2023-07-05.at.2.12.49.PM.mov

Thanks so much for the help here! I don’t see an issue here, so please feel free to close this unless you want to take any further action.

@Lorp
Copy link

Lorp commented Jul 5, 2023

Note that there is acceptance of the need to be able to express "immediately after N" or "immediately before N", meaning the value (in normalised coordinates) 1/16384 more or less than N. According to the spec, axis values in variable fonts are rounded to the nearest 1/16384 before being used in the variation algorithm. AFAIK Samsa is the only app where you can step through a designspace in 1/16384 units.

@behdad
Copy link
Member

behdad commented Jul 7, 2023

In theory we can extend the designspace format to allow for .5+epsilon / .5-epsilon syntax. It's a bit ugly to implement though.

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

No branches or pull requests

4 participants