Specify axis range (set limit) for variable font

My situation:
Weight axis:
2 to 216 (stem thickness)
Hairline (2) to ExtraBold (200)
216 is the boldest Master.

Now I want to export a variable font with the axis range from 2 to 200 and not from 2 to 216. But this is currently not possible.

In that example, I have to create a new master 200 from the instance and delete the 216 master. But actually I want to keep the master for future extensions.

Here are two idea:

  1. Either it’s possible to ‘hide’ the master, similar to what we can do with instances or
  2. we can specify the range of an axis (set a limit).

Olli

Hi, I am working on exactly this at the moment, but trying to solve it with a script which generates a file with the extra master that is needed in order to provide the variable font origin.
After exporting, you can use the app Slicer to create a subset variable font: https://slice-gui.netlify.app/

Here’s my code if you want to play around with it, it is extremely WIP. It tries to take care of glyphs with alternates as well, as these are not automatically added when running Instance as Master:

# MenuTitle: Create Variable Subset File
# -*- coding: utf-8 -*-

__doc__ = """
Creates a file to export a subset variable font.
"""

import vanilla


class CreateSubset:
	def __init__(self):
		if Font is None:
			Message("No font selected", "Select a font project!")
			return

		self.font = Font

		self.axisRanges = {axis.name: sorted(set(master.axes[i] for master in self.font.masters))
		                   for i, axis in enumerate(self.font.axes)}

		for axis in self.axisRanges:
			del self.axisRanges[axis][1:-1]

		self.variable_font_origin_master = self.font.masters[0]
		for master in Font.masters:
			if master.id == self.font.customParameters["Variable Font Origin"]:
				self.variable_font_origin_master = master

		self.subsetValues = {}

		self.w = vanilla.FloatingWindow((0, 0), "Create Variable Subset File")

		self.ypos = 10

		self.w.newNameTitle = vanilla.TextBox((10, self.ypos, -10, 14), "Family name", sizeStyle="small")

		self.w.newNameEntry = vanilla.EditText((90, self.ypos, -10, 19), text=self.font.familyName,
		                                       sizeStyle="small", callback=self.check_name)

		self.ypos += 30

		self.w.divider = vanilla.HorizontalLine((10, self.ypos, -10, 1))

		self.ypos += 10

		self.w.MinTitle = vanilla.TextBox((60, self.ypos, 40, 14), "Min", alignment="center", sizeStyle="small")
		self.w.toTitle = vanilla.TextBox((110, self.ypos, 40, 14), "Max", alignment="center", sizeStyle="small")
		self.w.defaultTitle = vanilla.TextBox((150, self.ypos, 60, 14), "Default", alignment="center",
		                                      sizeStyle="small")

		self.ypos += 20

		for i, axis in enumerate(self.font.axes):
			axis_title = vanilla.TextBox((10, self.ypos, -10, 17), axis.name, sizeStyle="small")
			axis_min = vanilla.EditText((60, self.ypos, 40, 19), text=self.axisRanges[axis.name][0], sizeStyle="small",
			                             callback=self.define_axis_ranges)
			axis_from = vanilla.EditText((110, self.ypos, 40, 19), text=self.axisRanges[axis.name][1], sizeStyle="small",
			                           callback=self.define_axis_ranges)
			separator = vanilla.TextBox((95, self.ypos, 20, 17), ":", alignment="center")
			bracketleft = vanilla.TextBox((145, self.ypos, 20, 17), "[", alignment="center")
			axis_default = vanilla.EditText((160, self.ypos, 40, 19),
			                                text=self.variable_font_origin_master.axes[i], sizeStyle="small",
			                                callback=self.define_axis_ranges)
			bracketright = vanilla.TextBox((195, self.ypos, 20, 17), "]", alignment="center")
			reset_button = vanilla.SquareButton((210, self.ypos + 2, 17, 17), u"↺", sizeStyle="small",
			                              callback=self.reset_value)

			setattr(self.w, axis.axisTag, axis_title)
			setattr(self.w, axis.axisTag + "Min", axis_min)
			setattr(self.w, axis.axisTag + "Max", axis_from)
			setattr(self.w, axis.axisTag + "Separator", separator)
			setattr(self.w, axis.axisTag + "Bracketleft", bracketleft)
			setattr(self.w, axis.axisTag + "Default", axis_default)
			setattr(self.w, axis.axisTag + "Bracketright", bracketright)
			setattr(self.w, axis.axisTag + "Button", reset_button)

			self.subsetValues[axis.name] = [int(axis_min.get()),
			                                int(axis_from.get()),
			                                int(axis_default.get())]

			self.ypos += 24

		self.ypos += 10

		self.w.makeButton = vanilla.Button((10, self.ypos, -10, 17), "Make file", callback=self.make_subset_file)
		self.w.makeButton.enable(self.font.familyName != self.w.newNameEntry.get())

		self.ypos += 27

		self.w.setDefaultButton(self.w.makeButton)
		self.w.resize(237, self.ypos)
		self.w.open()
		self.w.makeKey()

	def check_name(self, sender):
		self.w.makeButton.enable(self.font.familyName != self.w.newNameEntry.get())

	def define_axis_ranges(self, sender):
		for axis in self.font.axes:
			self.subsetValues[axis.name] = [int(getattr(self.w, axis.axisTag + "Min").get() or 0),
			                                int(getattr(self.w, axis.axisTag + "Max").get() or 0),
			                                int(getattr(self.w, axis.axisTag + "Default").get() or 0)]

	def reset_value(self, sender):
		for i, axis in enumerate(self.font.axes):
			if sender is getattr(self.w, axis.axisTag + "Button"):
				getattr(self.w, axis.axisTag + "Min").set(self.axisRanges[axis.name][0])
				getattr(self.w, axis.axisTag + "Max").set(self.axisRanges[axis.name][1])
				getattr(self.w, axis.axisTag + "Default").set(self.variable_font_origin_master.axes[i])

	def master_manager(self):
		for master in self.font.masters:
			match_count = 0
			for i, axis in enumerate(self.font.axes):
				if self.subsetValues[axis.name][2] == master.axes[i]:
					match_count += 1
			if match_count == len(self.font.axes):
				found_origin_master = master
				break

		try:
			print("origin", found_origin_master)
		except:
			origin_instance = GSInstance()
			origin_instance.name = "Variable Font Origin"
			self.font.instances.append(origin_instance)
			self.font.instances[-1].axes = [self.subsetValues[axis][2] for axis in self.subsetValues]
			self.font.instances[-1].addAsMaster()
			self.font.masters[-1].axes = [self.subsetValues[axis][2] for axis in self.subsetValues]


		# removeList = []
		# try:
		# 	for master in self.font.masters:
		# 		for i, axisValue in enumerate(master.axes):
		# 			if axisValue < self.subsetValues[self.font.axes[i].name][0] or axisValue > self.subsetValues[
		# 				self.font.axes[i].name][1]:
		# 				removeList.append(master)
		# 				continue
		# 	for removeMaster in removeList:
		# 		# self.font.masters.remove(removeMaster)
		# 		print("remove", removeMaster)
		# except Exception as e:
		# 	print(e)

		for instance in self.font.instances:
			if instance.type:
				for fontInfo in instance.properties:
					if fontInfo.key == "familyNames":
						for value in fontInfo.values:
							if value.languageTag == "dflt":
								value.value = self.w.newNameEntry.get()
				instance.customParameters["fileName"] = self.w.newNameEntry.get().replace(" ", "-")

	def fix_special_layers(self):
		self.font.disableUpdateInterface()
		for glyph in Font.glyphs:
			if glyph.mastersCompatible:
				continue
			print(glyph)
			backup_glyph = glyph.duplicate(name=glyph.name + ".specialLayers")
			for layer in backup_glyph.layers:
				if layer.isSpecialLayer:
					copy_layer = layer.copy()
					del copy_layer.attributes["axisRules"]
					backup_glyph.layers[layer.associatedMasterId] = copy_layer
			for layer in backup_glyph.layers:
				if layer.name == "Variable Font Origin":
					layer.reinterpolate()
					final_layer = layer
					final_layer.attributes["axisRules"] = glyph.layers[-1].attributes["axisRules"]
			glyph.layers.append(final_layer.copy())
			del Font.glyphs[glyph.name + ".specialLayers"]
		self.font.enableUpdateInterface()

	def make_subset_file(self, sender):
		self.master_manager()
		self.font.save(path=self.font.filepath.replace(self.font.filepath.split("/")[-1],
		                                              self.w.newNameEntry.get() + ".glyphs"))
		self.fix_special_layers()
		self.font.save()


CreateSubset()

Not perhaps for the same reason you want it but I agree, an option to “deactivate” any master would be amazing.

Have you tried the “Disable Masters” CP?

@GeorgSeifert The custom parameter works well, at least for what I was looking for. Thanks.