Python library for manipulating OpenType font features

simoncozens, updated ๐Ÿ•ฅ 2023-02-03 07:58:05

fontFeatures library

If you're looking for the FEE language, it has been renamed to FEZ and moved to its own library (fez).

OpenType fonts are "programmed" using features, which are normally authored in Adobe's feature file format. This like source code to a computer program: it's a user-friendly, but computer-unfriendly, way to represent the features.

Inside a font, the features are compiled in an efficient internal format. This is like the binary of a computer program: computers can use it, but they can't do else anything with it, and people can't read it.

The purpose of this library is to provide a middle ground for representing features in a machine-manipulable format, kind of like the abstract syntax tree of a computer programmer. This is so that:

  • features can be represented in a structured human-readable and machine-readable way, analogous to the XML files of the Unified Font Object format.
  • features can be more directly authored by programs (such as font editors), rather than them having to output AFDKO feature file format.
  • features can be easily manipulated by programs - for example, features from two files merged together, or lookups moved between languages.

How is this different from fontTool's feaLib? I'm glad you asked. feaLib translates between the Adobe feature file format and a abstract syntax tree representing elements of the feature file - not representing the feature data. The AST is still "source equivalent". For example, when you code an aalt feature in feature file format, you might include code like feature salt to include lookups from another feature. But what's actually meant by that is a set of lookups. fontFeatures allows you to manipulate meaning, not description.


fontFeatures consists of the following components:

  • fontFeatures itself, which is an abstract representation of the different layout operations inside a font.
  • fontFeatures.feaLib (included as a mixin) which translates between Adobe feature syntax and fontFeatures representation.
  • fontFeatures.ttLib, which translates between OpenType binary fonts and fontFeatures representation. (Currently only OTF -> fontFeatures is partially implemented; there is no fontFeatures -> OTF compiler yet.)
  • fontFeatures.fontDameLib which translate FontDame text files into fontFeatures objects.

And the following utilities:

  • otf2fea: translates an OTF file into Adobe features syntax.
  • txt2fea: translates a FontDame txt file into Adobe features syntax.


Fix failing test in test_anchors

opened on 2023-02-03 07:58:04 by ctrlcctrlv

As you may notice we still have a test fail. I'm not sure how to fix it as the results are identical, markClass names shouldn't matter to a roundtrip?

feaLib: languagesystem statement not respected

opened on 2022-04-27 16:58:18 by joshuakraemer

It seems that languagesystem statements in a feature file are not respected. Please check the following example:

``` import fontFeatures.feaLib

fea = """ languagesystem tml2 dflt;

feature haln { sub patamil uuvowelsigntamil by pauuvowelsigntamil; } haln; """

parser = fontFeatures.feaLib.FeaParser(fea) tree = parser.parse() print(tree.hasScriptSupport("tml2")) ```

If the same feature file is parsed with fontTools.feaLib.parser.Parser(), the resulting tree includes a fontTools.feaLib.ast.LanguageSystemStatement object as expected.

ff.buildBinaryFeatures() somehow affects language systems in exported FEA

opened on 2022-04-03 19:04:33 by lianghai

Build the same ff, apply one of them to a certain font using .buildBinaryFeatures(), then somehow the results of .asFea() arenโ€™t the same anymore. Is this an intended behavior? Can I provide the test font in private?

```python from fontFeatures import FontFeatures, Routine, Substitution from fontTools.ttLib import TTFont

language = "taml", "dflt" font = TTFont("foo.otf")

ff_1 = FontFeatures() ff_2 = FontFeatures()

ff_1.addFeature( "akhn", [ Routine( languages=[language], rules=[ Substitution([["kaTaml"]], [["kaTaml"]]), ], ), ], ) ff_2.addFeature( "akhn", [ Routine( languages=[language], rules=[ Substitution([["kaTaml"]], [["kaTaml"]]), ], ), ], )


fea_1 = ff_1.asFea() fea_2 = ff_2.asFea()


assert fea_1 == fea_2 ```

Incomplete FEA languagesystem statements

opened on 2021-12-20 09:55:44 by lianghai

Is this < 2 assuming a DFLT dflt is always present next to a non-default language system based on the commented code below?

Type note about Substitution.replacement is outdated?

opened on 2021-12-19 23:04:28 by lianghai

Seems itโ€™s now expected also to be list[list[str]].


opened on 2021-09-09 09:31:51 by m4rc1e

Still heavy wip.

This PR adds the ability to reverse a shaped buffer e.g

Buffer: ุงู„ู„ู‡ After deshaping: ุง ู„ ู„ ู‡

It basically does the reverse of the shaperLib/HB by applying the features/shaping steps in reverse order.

So far, I have a functioning Arabic deshaper. I plan to add a shaper each week so this may take a while. Whilst adding them, I'll definitely encounter edge cases I haven't considered. Once I have all niggles are sorted, I'll do a refactor so ignore the copy pasta for now.

My testing strategy is to take an unshaped buffer, shape it, deshape it and then check if the deshaped buffer resembles the original state.

This PR will deprecate #29

If anyone can think of a better name than deshaper, feel free. Perhaps simply calling it revert is better?

Simon Cozens
GitHub Repository