arrowtype / recursive

Recursive Mono & Sans is a variable font family for code & UI
https://recursive.design
SIL Open Font License 1.1
3.25k stars 51 forks source link

macOS/Sketch bug: variable axes sometimes have to be offset by tiny amount (e.g. 0.01) to work as expected #317

Closed arrowtype closed 3 years ago

arrowtype commented 4 years ago

TL;DR: if an axis isn't working in Sketch or elsewhere on macOS, try changing it by 0.01.

Problem description

Sometimes, a slider won't have the expected effect in Sketch, if it is at its full min or max value. As a user, this can be handled by changing the value up or down by a very small amount, like 0.1 or 0.01. This small tweak doesn't change the visual appearance of the font, except that it somehow makes mac render the styles as expected.

Example: Recursive with a wght of 300 and slnt of -7 will appear to have a weight more like 400, until I adjust it to 300.1:

Kapture 2020-02-18 at 12 09 39

Another example: Recursive with a CASL value of 0 should be "Linear" (normal-looking), but when slnt is -5, the font presents with a CASL value of 1 ("Casual" or brushy-looking) until I adjust CASL to 0.01:

Kapture 2020-02-18 at 12 08 28

I believe this is a Core Text issue because:

Expected behavior

I expect the rendered text to have the values I have entered, even if these are integers at min or max values.

To Reproduce Steps to reproduce the behavior:

  1. This has happened with many different versions of Recursive, but you can try the latest. Download and install Recursive v1.039
  2. Open Sketch
  3. Try setting text in Recursive at:
    • MONO 0
    • CASL 1
    • Weight 300
    • Slant 0
    • Italic 0.5
  4. Make another text element and change the axes to:
    • MONO 0
    • CASL 1
    • Weight 300
    • Slant -7 (change this one)
    • Italic 0.5
  5. Notice that the weight is wrong, until you change Weight to 300.1, at which point it looks fine again.

Environment (please complete the following information):

Additional context

This is not an issue specific to Recursive. It also occurs in Fraunces

image

arrowtype commented 4 years ago

Interestingly, there used to be issues like this in DrawBot (https://github.com/typemytype/drawbot/issues/305), but I can no longer trigger them macOS 10.15.

Here's similar code to my first example from Sketch, working fine in Drawbot: image

code used above (Click to expand) Dropdown details ``` import platform print(platform.mac_ver()[0]) path = '/Users/stephennixon/Library/Fonts/recursive-fonts_1.038/Recursive_VF_1.039.ttf' font(path) for axis, data in listFontVariations().items(): print((axis,data)) fontSize(180) fontVariations(wght=300, slnt=0, CASL=1) text('Test', (100,734)) fontSize(180) fontVariations(wght=300, slnt=-7, CASL=1) text('Test', (100,534)) ```

Fraunces is also working well in DrawBot:

image

randomsequence commented 4 years ago

Hello, I'm looking at this from the Sketch side. I think the problem lays in the default value for the axis. In the case of weight the default is 300, which seems to be specified here:

https://github.com/arrowtype/recursive/blob/master/src/masters/recursive-MONO_CASL_wght_slnt_ital--full_gsub.designspace#L10

When we request a font from the system with a value which matches the default for an axis, the returned font's descriptor does not have a value for variation on that axis - the returned font is as though we didn't specify that axis at all.

I believe the answer to this is to change the default value to 400, or whatever the source glyph was designed at, though I don't know enough about how the fonts are made to be sure.

I also checked this in Fraunces and found the same thing. I believe their default weight should be 300, but is specified as 100.

arrowtype commented 4 years ago

Hey @randomsequence, thanks so much for commenting with insights from the mac developer side! That's helpful to hear that the returned font is as though we didn't specify that axis at all. Are you working directly with Sketch, or on other mac software? Either way, I appreciate you commenting with perspective.

In Recursive, the default value for wght must be either 300, 800, or 1000, as these values align with sources, and define the base set of glyf data that will show if a system doesn't understand variable fonts at all. For Recursive, I have this set at 300, as it's closest to a Regular (400) weight.

In Fraunces (designspace here), sources have wght values of either 100 or 900, and it looks like they figured 100 would be the logical thing to make the default.

In short, if I tried to change the default to 400 or Fraunces tried to change their default to 300, neither font would build at all.

However, my issue isn't really about the initial font style I get – the problem I'm experiencing is that values can be displayed inaccurately when I have moved some sliders around. For instance, in Fraunces if Goofy != 0, then wght=100 won't display as a 100, but rather snaps to 400.

Perhaps this snapping-to-400 occurs because Sketch "stops requesting" the weight axis when wght is at a font's default, but then Mac assumes that wght should be set to 400, the value that people generally expect to be the default. And so, maybe mac is setting the value to 400 without Sketch asking for it to be.

Then again, I don't quite know why this specific circumstance might be triggered by non-0 values of other axes such as Goofy or Slant. I'd like to do more testing, but right now, I can't access the variable sliders at all ... :/

image

jjgod commented 4 years ago

It is perfectly fine to have a variation axis default matching the minimum (or maximum).

When we request a font from the system with a value which matches the default for an axis, the returned font's descriptor does not have a value for variation on that axis - the returned font is as though we didn't specify that axis at all.

It is indeed true that when you request a font with variation axis value matching the default, the matched font descriptor from the font will not have that particular variation axis value. But it doesn't mean the information is lost, because the font file alone is enough to identify the value of that axis.

I believe what we observe here ("snapping-to-400") is not done by the system, but instead coming from Sketch confusing one instance with another in the same variable font. There is nothing indicating the system that it should pick 400 unless it has been explicitly asked to pick that.

Again, if @randomsequence can provide a snippet of code demonstrating how Sketch selects variable font, and we can reproduce the same problem with Recursive/Fraunces there, I will be happy to take a look.

arrowtype commented 4 years ago

Thanks for the helpful insights, @jjgod! I feel lucky to have both of you looking into this. :)

I believe what we observe here ("snapping-to-400") is not done by the system, but instead coming from Sketch confusing one instance with another in the same variable font

I suppose potentially, I could build a version of recursive with no instances, to test whether instance-guessing might be a potential cause of this...

randomsequence commented 4 years ago

@jjgod here's what we're doing, which you can run in a swift playground, assuming you have the "recursive" font installed:

import Cocoa

// get the default version of this font
let font = NSFont(name: "Recursive", size: 20) ?? NSFont()
print(font)
print(CTFontCopyVariation(font as CTFont) ?? "")

// the available variations
let axes = CTFontCopyVariationAxes(font)
print(axes ?? "")

// create a descriptor changing the weight to its default value
let weightID = NSNumber(integerLiteral: 2003265652) as CFNumber
var descriptor = CTFontDescriptorCreateCopyWithVariation(font.fontDescriptor as CTFontDescriptor, weightID, 300)

// the new font
let font2 = NSFont(descriptor: descriptor, size: font.pointSize)
print(font2 ?? "")
print(CTFontCopyVariation(font2!) ?? "")

Using Xcode 11.31. on macOS 10.14.6, this prints:

"RecursiveSansCsl-Regular 20.00 pt. P [] (0x7fe00bc3fbd0) fobj=0x7fe00bc56250, spc=6.00"
{
    1128354636 = 1;
    2003265652 = 400;
}
(
        {
        NSCTVariationAxisDefaultValue = 0;
        NSCTVariationAxisIdentifier = 1297043023;
        NSCTVariationAxisMaximumValue = 1;
        NSCTVariationAxisMinimumValue = 0;
        NSCTVariationAxisName = Monospace;
    },
        {
        NSCTVariationAxisDefaultValue = 0;
        NSCTVariationAxisIdentifier = 1128354636;
        NSCTVariationAxisMaximumValue = 1;
        NSCTVariationAxisMinimumValue = 0;
        NSCTVariationAxisName = Casual;
    },
        {
        NSCTVariationAxisDefaultValue = 300;
        NSCTVariationAxisIdentifier = 2003265652;
        NSCTVariationAxisMaximumValue = 1000;
        NSCTVariationAxisMinimumValue = 300;
        NSCTVariationAxisName = Weight;
    },
        {
        NSCTVariationAxisDefaultValue = 0;
        NSCTVariationAxisIdentifier = 1936486004;
        NSCTVariationAxisMaximumValue = 0;
        NSCTVariationAxisMinimumValue = "-15";
        NSCTVariationAxisName = Slant;
    },
        {
        NSCTVariationAxisDefaultValue = "0.5";
        NSCTVariationAxisIdentifier = 1769234796;
        NSCTVariationAxisMaximumValue = 1;
        NSCTVariationAxisMinimumValue = 0;
        NSCTVariationAxisName = Italic;
    }
)
"RecursiveSansCsl-Med 20.00 pt. P [] (0x7fe01bd77b70) fobj=0x7fe01bd6b760, spc=6.00"
{
    1128354636 = 1;
}

Note that by setting the weight to the default, we now have a different postscript name - what was RecursiveSansCsl-Regular is now RecursiveSansCsl-Med. Comparing these two visually (In FontBook), the latter appears heavier. The same thing happens in Sketch:

RecursiveSansCsl-Regular

Screenshot 2020-02-27 at 12 05 37

RecursiveSansCsl-Med

Screenshot 2020-02-27 at 12 05 40
jjgod commented 4 years ago

@randomsequence Interesting! Thanks for trying this.

This looks like an old OS bug that has been addressed in macOS 10.15 (at least 10.15.4 should work), it might have something to do with the present of the avar.

I just tried the same code as you shown above and I got RecursiveMonoCsl-Light 20.00 pt. in return instead of RecursiveSansCsl-Med 20.00 pt. as you shown. So I believe this particular bug has been addressed.

However, even with latest macOS 10.15.4, the Sketch bug as shown by @arrowtype still exists, so I think there is something else at play here. Care to take another look? Do you happen to have access to a 10.15 system to try?

randomsequence commented 4 years ago

@jjgod I tried the same Playground on 10.15.3 and got the same result as you - RecursiveMonoCsl-Light. I also found that I couldn't reproduce the problem in Sketch on that system, so I guess this is a bug in macOS 10.14.

Thanks for your help.

jjgod commented 4 years ago

Interesting, I believe @arrowtype said that he still experiences the same issue on 10.15.4 with Sketch: https://mobile.twitter.com/ArrowType/status/1230150358799876096

Can you check the video to see if that matches what you have tried? Could a specific Sketch version matter here?

I can reproduce the same issue on the same OS and latest Sketch I downloaded recently from sketch.com, too.

randomsequence commented 4 years ago

I'll keep looking @jjgod. I've found a bug in Sketch where we sometimes mistakenly mark variable fonts as unavailable, which causes them to render using a different code path.

arrowtype commented 4 years ago

Thanks to both of you for digging further into this!

I just tried the same code as you shown above and I got RecursiveMonoCsl-Light 20.00 pt. in return instead of RecursiveSansCsl-Med 20.00 pt. as you shown. So I believe this particular bug has been addressed.

This is almost correct, but actually, when requesting just CASL=1, wght=300, I would expect the result to be RecursiveSansCsl-Light (Sans, not Mono, as the default of the MONO axis is 0, or "Sans"). If it's not, that seems to be a bug somewhere along the line. Based on a ttx inspection of Recursive (both in v1.030 or the latest, v1.043), it seems that it is correctly hooked up:

From the fvar table:

    <!-- Sans Casual Light -->
    <!-- PostScript: RecursiveSansCsl-Light -->
    <NamedInstance flags="0x0" postscriptNameID="342" subfamilyNameID="341">
      <coord axis="MONO" value="0.0"/>
      <coord axis="CASL" value="1.0"/>
      <coord axis="wght" value="300.0"/>
      <coord axis="slnt" value="0.0"/>
      <coord axis="ital" value="0.5"/>
    </NamedInstance>

From the name table, showing corresponding 342 name ID:

    <namerecord nameID="342" platformID="1" platEncID="0" langID="0x0" unicode="True">
      RecursiveSansCsl-Light
    </namerecord>

@jjgod & @randomsequence, are you sure you are getting RecursiveMonoCsl-Light from that playground? Or, is it possible that it's RecursiveSansCsl-Light?

jjgod commented 4 years ago

That is a good point. Let me take another look.

jjgod commented 4 years ago

Oh, looks like we picked RecursiveMonoCsl-Regular to begin with when we request for font with name Recursive -- since it is not a PostScript name match we had to make a guess from the family name, then with RecursiveMonoCsl-Regular when requesting for weight 300 we got RecursiveMonoCsl-Light which is indeed expected.

If we do start with RecursiveSansCsl-Regular then applying weight 300 we will get RecursiveSansCsl-Light as expected. So I don't see any system problem here.

randomsequence commented 4 years ago

@jjgod I'm still finding problems with Recursive-SansLinearLight face in particular. Again in a playground:

import Cocoa

for descriptor in NSFontCollection.withAllAvailableDescriptors.matchingDescriptors(forFamily: "Recursive")! {

    let query = NSFontDescriptor(fontAttributes: descriptor.fontAttributes)
    if let matched = query.matchingFontDescriptor(withMandatoryKeys: nil) {
        if (descriptor != matched) {
            print("font attributes: \(descriptor) do not match \(matched)")
        }
    } else {
        print("no matching font descriptor for \(descriptor)")
    }
}

Expected: no output Actual:

font attributes: NSCTFontDescriptor <0x7fbaf7e5f440> = {
    NSFontNameAttribute = "Recursive-SansLinearLight";
} do not match NSCTFontDescriptor <0x7fbaf7d671d0> = {
    NSFontNameAttribute = "RecursiveSansCsl-Med";
}

I thought this was the same matching bug you mentioned earlier, but I get the same output on 10.14 & 10.15.

arrowtype commented 4 years ago

Sorry, but it’s hard to follow. Do you have any speculation as to whether this bug might be in the font, or in the OS?

jjgod commented 4 years ago

I plan to take another look, just didn’t get to it yet.

jjgod commented 4 years ago

This should be addressed in macOS 11 developer seed now. Please give this a try. @randomsequence

arrowtype commented 4 years ago

I have confirmed that at least in macOS 11.0 Beta (20A4299v), this is still an issue in Sketch Version 66.1 (97080), and still working in Drawbot.

Drawbot:

image

Sketch:

image

But, macOS 11 does fix a ton of font bugs, so I am optimistic this one might be possible to solve!

@randomsequence I know you probably have a ton to work on to update for the new OS, but please let me know if there’s anything you need from my end on this. Hopefully this can get resolved soon! Thanks for all your help.

arrowtype commented 3 years ago

Closing this issue, as it is out of the control of the Recursive project. I think it is likely fixed in macOS 11, but I haven’t installed that yet, so I can’t confirm.