Enabling stylistic sets (e.g. ss02) with fonttools

Casper de With
Casper de With Posts: 4
edited May 2022 in Font Technology

I want to create a static instance of a variable font that I have (Recursive.ttf), so I can use it in my video editor, which does not support variable fonts.

The font variations that I want to fix in place are: slnt, wght, MONO, CASL, CRSV, ss02, ss03, ss04, ss05, ss08 and ss11.

I’ve found this post that led me to discovering the program fonttools. So far, I have successfully instantiated the variation axes (slnt, wght, etc.), but now I want to enable the stylistic variants, such as a single-storey ‘g’, which is encoded as ss02.

I’ve spent about two hours in the documentation, and I tried a lot of stuff out in the terminal, but to no avail. I thought the section ‘subset’ would be useful, but the only thing it does, is strip the font of the other things – it still doesn’t enable the alternates. The ‘instancer’ section also doesn’t help much, it seems; only the axes can be fixed, not the stylistic sets. Maybe tweaking with ‘GSUB’ would work, but I had no idea what that was and I couldn’t figure out what I had to do.

So my question is: how do you instantiate stylistic alternates of a font in fonttools? Or any other program for that matter, but I think it must be doable within fonttools.

Comments

  • Connor Davenport
    Connor Davenport Posts: 36
    edited May 2022
    You can do this by tweaking the CMAP table in the font. Essentially you just have to set the unicode slot for the default glyph to be assigned to the alternate. 

    <map code="0x67" name="g.alt"/><!-- LATIN SMALL LETTER G -->

    Here is a simple example of how to do this in FontTools too. This is probably very convoluted and not the best way if you want the script to be flexible but it works for this specific example with Recursive. I didn't include it in this example but you can also reverse the dictionary from the ```Subtable.mapping``` to swap the GSUB.

    import os
    from fontTools.ttLib import TTFont
    
    feasToFreeze = "ss02 ss03 ss04 ss05 ss08 ss11".split(" ")
    path = 'path/to/font.ttf'
    
    ext = os.path.splitext(path)[1]
    savePath = path.replace(ext,f"-frozen.{ext}")
    
    f = TTFont(path)
    
    GSUB = f["GSUB"]
    cmap = f['cmap']
    
    def getUnicodeFromName(name, subtable):
        u = None
        for uni,CIDname in list(subtable.cmap.items()):
            if CIDname == name:
                u = uni
        if u:
            return u
            
    i = []
    for fea in feasToFreeze:
        for r in GSUB.table.FeatureList.FeatureRecord:
            if r.FeatureTag == fea:
                i.append(r.Feature.LookupListIndex[0])
    i = sorted(list(set(i)))
    
    mapping = {}
    if i:
        for LookupID in i:
            Lookup = GSUB.table.LookupList.Lookup[LookupID]
            for Subtable in Lookup.SubTable:
                if Subtable.LookupType == 1:
                    mapping.update(Subtable.mapping)
        if mapping:
            remap = list(mapping.keys())
            for table in cmap.tables: 
                for n in remap:
                    c = getUnicodeFromName(n, table)
                    if c:
                        table.cmap[c] = mapping[n]            
            f.save(savePath)


  • Simon Cozens
    Simon Cozens Posts: 740
    Adam's OTF Feature Freezer might do what you want.
  • Agreed^ I was just coming back to link that tool:) I originally figured out how to do this because of Adam's code but had tweaked stuff to fit my needs at the time.
  • Casper de With
    Casper de With Posts: 4
    edited May 2022

    Many thanks for your swift replies! It works!

    +: Oh, only partially, apparently. It doesn’t change ss04 for some weird reason, but it does do the rest. Am I doing something wrong? I tried isolating the problem:

    $ pyftfeatfreeze -f 'ss04' Recursive.ttf Recursive-ss04.ttf

    … but it doesn’t change the ‘i’, which it’s supposed to do.

    Connor’s script has the same issue. The problem can’t be the font itself, since I regularly use ss04 without problems in Inkscape. What could it be? Does anyone have a hunch?

  • Connor Davenport
    Connor Davenport Posts: 36
    edited May 2022
    @Casper de With I just took a look and it appears that Recursive's ss04 is a type-2 lookup which is a bit more tricky to make work with these tools because type-2 subs are "one for many" so what glyph would we end up swapping for? In the case of Recursive we can just open it and see that there is only one actual type-2 substitution in the feature, so you could just do this all manually and ignore the one that is type-2. 

    import os
    from fontTools.ttLib import TTFont
    
    path = 'path/to/font.ttf'
    ext = os.path.splitext(path)[1]
    savePath = path.replace(ext,f"-frozen.{ext}")
    
    f = TTFont(path)
    cmap = f['cmap']
    
    def getUnicodeFromName(name, subtable):
        u = None
        for uni,CIDname in list(subtable.cmap.items()):
            if CIDname == name:
                u = uni
        if u:
            return u
    
    sansI = "dotlessi i iacute ibreve icircumflex idieresis idieresisacute idotbelow igrave igravedbl ihook iinvertedbreve imacron iogonek itilde".split(" ")
    smplI = "dotlessi.italic i.simple iacute.simple ibreve.simple icircumflex.simple idieresis.simple idieresisacute.simple idotbelow.simple igrave.simple igravedbl.simple ihook.simple iinvertedbreve.simple imacron.simple iogonek.simple itilde.simple".split(" ")
    
    for i, glyph in enumerate(sansI):
        for table in cmap.tables:
            c = getUnicodeFromName(glyph, table)
            if c:
                table.cmap[c] = smplI[i]
    
    f.save(savePath)
    
    



  • Casper de With
    Casper de With Posts: 4
    edited May 2022

    It works! Thanks for being so helpful, @Connor Davenport!

    +: Thanks for the explanations too – I learnt a lot.

  • @Casper de With no problem! always happy to help!!