Multiscript font file and export for single scripts - feature code management

I have a font file with script latin and script hebrew. Now I want to export one .otf for latin and hebrew together and one .otf for latin only.

But I have a lot of feature code and classes in this font file and managing the export with the custom parameters is very painful. (expecially as the font file will be in development for a longer time range…)
I think, it would be much easier, if feature code within script tags could be omitted on export. Or a mechanism like the conditional feature code for variables. Or if I could write special “versions” of feature tags for the scripts. (ss01#latin, ss01#hebrew e.g.)
Also classes, that are not needed for the not exported scripts, should be simply ignored.

Are there any possibilities yes, that I couldn’t find? Or would this be a feature request?

1 Like

Ralf has a good point; adding a feature similar to “conditional statements” to other OpenType features could be really beneficial. Instead of using scripts, which can be limiting, we could implement a tagging system. This way, we could tag certain parts of the code and then specify, through a Custom Parameter in the font’s settings, which tags to ignore during export.

Right now, exporting fonts that are subset to specific scripts is quite complicated. It requires manually removing classes and updating features, which is done by using a “Replace Features” CP.

Here’s how it works currently with an example of a stylistic set:

sub a by a.ss01
sub alpha by alpha.ss01
sub a-cy by a-cy.ss01

Exporting a font where Latin and Greek glyphs are removed using this setup would result in an error.

However, if we used conditional code, it would look something like this:

#if LAT_tag
sub a by a.ss01
#end LAT_tag

#if CYR_tag
sub a-cy by a-cy.ss01
#end CYR_tag

#if GRK_tag
sub alpha by alpha.ss01
#end GRK_tag

This way, when exporting a font with only Cyrillic, I could apply a Custom Parameter called “OpenType Ignore Tags” and specify GRK_tag and LAT_tag to be ignored.

Good point. I’ll have a look.

The ss01 feature is not a good example as you can just leave it on automatic and it will be regenerated on export. Same for the default classes.

1 Like

I would be infinitely grateful @GeorgSeifert
This would greatly simplify multiscript file management.

Hey @GeorgSeifert

I wrote a script to parse OpenType feature code, and keep only code between specific tags.

import re

def keep_code_between_tags(FEATURE_CODE, TAGS):
    results_str = ""
    for tag in TAGS:
        pattern = rf"(?<=#ifdef {tag}\n).*?(?=#endif)"
        match = re.search(pattern, FEATURE_CODE, flags=re.DOTALL)
        if match:
            updated_code += match.group(0) + "\n"
    if results_str == "":
        return FEATURE_CODE
    else:
        return updated_code.strip()

TAGS = ["LATIN", "CYRILLIC"]
for feature in Font.features:
	if feature.automatic:
		print(feature.name, "is autogenerated; no need to update the code manually.")
	else:
		print(feature.name)
		feature_code = feature.code
		updated_code = extract_texts_for_tags(feature_code, TAGS)
		print(updated_code)

I used .ss in my example (I know that’s not very relevant, since most of the time .ss are automatic, but)

Here is the code of my .ss01 :

#ifdef LATIN
featureNames {
	name "Latin ss01";
};
sub A by A.ss01;
#endif

#ifdef CYRILLIC
featureNames {
	name "Cyrillic ss01";
};
sub Be-cy by Be-cy.ss01;
#endif


#ifdef GREEK
featureNames {
	name "Greek ss01";
};
sub Delta by Delta.ss01;
#endif

Ideally, it should now work with Instance Custom Parameters Keep OTFeature Code for tags. By doing this, to generate a script subset, we will no longer encounter errors due to OTFeature with manual code.

Screenshot 2024-03-08 at 11.19.20

What do you thing ?

Sounds good. Make that script a Filter plugin and rename the custom parameter to something like “OpenType feature macros’ to make more generally useful.

1 Like

There is a kind of hack to make it work with Filter plugin ?

I guess that for usual Filter plugin, when some changes are applied to layer, theses changes are only applied in exported layer, not in the source layer.

How can I change the content of Feature codes, only during the export ?

class OpenTypeFeatureMacros(FilterWithoutDialog):

	@objc.python_method
	def settings(self):
		self.menuName = "OpenTypeFeatureMacros"
		self.instances_proc = []

	@objc.python_method
	def filter(self, layer, inEditView, customParameters):
		for instance in Glyphs.font.instances:
			if instance not in self.instances_pass:
				self.instances_pass.append(instance)
				Glyphs.font.features["ss01"].code = "sub a by b;"

	@objc.python_method
	def __file__(self):
		"""Please leave this method unchanged"""
		return __file__

Have a look at the core filter API: GlyphsFilter Protocol Reference
You need to implement

def processFont_withArguments_(self, font, arguments):

In your filter. And add the macros to the filter parameter (I’ll need to check how to get to the instance from within the filter).