Enabling stylistic sets (e.g. ss02) with fonttools
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
-
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)
1 -
Adam's OTF Feature Freezer might do what you want.3
-
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.0
-
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?
0 -
@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)
3 -
It works! Thanks for being so helpful, @Connor Davenport!
+: Thanks for the explanations too – I learnt a lot.
0 -
@Casper de With no problem! always happy to help!!0
Categories
- All Categories
- 43 Introductions
- 3.7K Typeface Design
- 803 Font Technology
- 1K Technique and Theory
- 622 Type Business
- 444 Type Design Critiques
- 542 Type Design Software
- 30 Punchcutting
- 136 Lettering and Calligraphy
- 83 Technique and Theory
- 53 Lettering Critiques
- 485 Typography
- 303 History of Typography
- 114 Education
- 68 Resources
- 499 Announcements
- 80 Events
- 105 Job Postings
- 148 Type Releases
- 165 Miscellaneous News
- 270 About TypeDrawers
- 53 TypeDrawers Announcements
- 116 Suggestions and Bug Reports