Created
May 10, 2022 19:45
-
-
Save typesupply/247b3f59143e7dc94d2c3e4bbcdb9165 to your computer and use it in GitHub Desktop.
Start tabular figures.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Create tabular figures. Version 0.1""" | |
""" | |
This script gives you a UI and some options for building tabular | |
figures. The generated glyphs contain duplicates of the source | |
glyphs, but their widths and sidebearings have been changed. | |
This does not attempt to squeeze or stretch the outlines, rather | |
it simply shifts them around. | |
----------------------------------------------------------------- | |
Groups List: | |
Groups of glyphs that should all have the same width. | |
The groups can have a fixed width, or the width for the group | |
can be created by averaging the width of all source glyphs in | |
the group. If you elect for a fixed width, you can enter it as | |
a set number of units (ie 250) or as a percentage (ie 50%). | |
Percentage values will be calculated as a percentage of the | |
overall tabular width. | |
Glyphs List: | |
List of glyphs in the selected group. The glyphs in this list | |
are the sources that will be used to generate the tabular glyphs. | |
----------------------------------------------------------------- | |
Use glyphs with suffix: | |
An optional suffix that will be appended to the source glyph | |
names when searching for source glyphs. For example, if you want | |
to generate old style tabular and your old style glyphs have a | |
".oldstyle" suffix, enter ".oldstyle" in this field. | |
Add suffix: | |
The sufix to be added to the tabular glyph names. | |
----------------------------------------------------------------- | |
Overwrite existing glyphs: | |
Enabling this will cause any glyphs with the same name as the | |
new tabular figures to be overwritten. | |
----------------------------------------------------------------- | |
Width: | |
The width for the tabular glyphs. | |
----------------------------------------------------------------- | |
Apply to all open fonts: | |
Enabling tis will add the tabular glyphs to all open fonts. | |
Apply to current font: | |
Enabling this will add the tabular glyphs only to the current font. | |
----------------------------------------------------------------- | |
Calculator | |
This is a simple dialog that averages the widths of selected | |
glyphs in specified fonts. | |
""" | |
from fontParts.ui import AskString, Message, ProgressBar | |
from dialogKit import * | |
defaultGroups = { | |
"figures" : {"glyphs" : "zero one two three four five six seven eight nine".split(" "), | |
"width" : "100%", | |
"useAverage" : False, | |
}, | |
"math" : {"glyphs" : "plus minus plusminus multiply divide equal notequal approxequal greater less greaterequal lessequal numbersign".split(" "), | |
"width" : "100%", | |
"useAverage" : False, | |
}, | |
"currency": {"glyphs" : "dollar cent sterling section yen florin Euro won".split(" "), | |
"width" : "100%", | |
"useAverage" : False, | |
}, | |
"half" : {"glyphs" : "space colon semicolon comma period".split(" "), | |
"width" : "50%", | |
"useAverage" : False, | |
}, | |
"containers" : {"glyphs" : "parenleft parenright bracketleft bracketright braceleft braceright".split(" "), | |
"width" : "70%", | |
"useAverage" : False, | |
}, | |
"percent" : {"glyphs" : "percent".split(" "), | |
"width" : "100%", | |
"useAverage" : False, | |
}, | |
"perthousand" : {"glyphs" : "perthousand".split(" "), | |
"width" : "100%", | |
"useAverage" : False, | |
} | |
} | |
defaultGroup = {"glyphs" : [], | |
"width" : "100%", | |
"useAverage" : False | |
} | |
def interpretGroupWidth(widthString, tabularWidth): | |
widthString = widthString.strip() | |
try: | |
if widthString.endswith("%"): | |
widthString = widthString.replace("%", "") | |
percentage = int(widthString) * .01 | |
width = tabularWidth * percentage | |
else: | |
width = int(widthString) | |
return width | |
except ValueError: | |
return None | |
def findAverageWidth(fonts, glyphNames): | |
widths = [] | |
for font in fonts: | |
for glyphName in glyphNames: | |
if font.has_key(glyphName): | |
widths.append(font[glyphName].width) | |
return sum(widths) / len(widths) | |
def makeTabular(fonts, glyphNames, width, inputSuffix, outputSuffix, overwriteExisting): | |
for font in fonts: | |
for glyphName in glyphNames: | |
glyph = None | |
inputName = glyphName + inputSuffix | |
outputName = glyphName + outputSuffix | |
if not font.has_key(inputName): | |
print("skipping %s because %s does not exists" % (outputName, inputName)) | |
continue | |
inputGlyph = font[inputName] | |
if font.has_key(outputName) and not overwriteExisting: | |
print("skipping %s because the glyph already exists" % outputName) | |
continue | |
diff = width - inputGlyph.width | |
offset = int(round(diff / 2.0)) | |
outputGlyph = font.newGlyph(outputName, clear=True) | |
outputGlyph.appendComponent(inputName, offset=(offset, 0)) | |
outputGlyph.width = width | |
if width < inputGlyph.width: | |
outputGlyph.mark = (1, 0, 0, 0.5) | |
outputGlyph.note = "non-tabular width: %s\ndifference: %s" % (str(inputGlyph.width), str(diff)) | |
outputGlyph.update() | |
class TabularInterface(object): | |
def __init__(self): | |
self.groups = defaultGroups | |
# | |
self.w = ModalDialog((339, 515), okCallback=self.callback_ok) | |
# | |
self.w.groupTitle = TextBox((12, 15, 50, 17), "Groups") | |
self.w.groupAddButton = Button((99, 10, 28, 20), "+", callback=self.callback_addGroup) | |
self.w.groupRemoveButton = Button((134, 10, 28, 20), "-", callback=self.callback_removeGroup) | |
self.w.groupList = List((12, 40, 150, 100), [], callback=self.callback_groupSelection) | |
self.w.groupUseWidth = CheckBox((12, 145, 100, 27), "Fixed width:", value=True, callback=self.callback_groupWidthToggle) | |
self.w.groupWidthEntry = EditText((115, 145, 47, 27), "100%", callback=self.callback_groupWidthEntry) | |
self.w.groupUseAverage = CheckBox((12, 170, 150, 27), "Group average width", callback=self.callback_groupWidthToggle) | |
# | |
self.w.glyphsTitle = TextBox((177, 15, 50, 17), "Glyphs") | |
self.w.glyphsAddButton = Button((264, 10, 28, 20), "+", callback=self.callback_addGlyph) | |
self.w.glyphsRemoveButton = Button((299, 10, 28, 20), "-", callback=self.callback_removeGlyph) | |
self.w.glyphList = List((177, 40, 150, 155), []) | |
# | |
self.w.divider1 = HorizontalLine((12, 210, 315, 1)) | |
# | |
self.w.inputSuffixTitle = TextBox((12, 225, 150, 17), "Use glyphs with suffix:") | |
self.w.inputSuffixEntry = EditText((12, 245, 150, 27)) | |
self.w.outputSuffixTitle = TextBox((177, 225, 150, 17), "Add suffix:") | |
self.w.outputSuffixEntry = EditText((177, 245, 150, 27), ".tab") | |
# | |
self.w.divider2 = HorizontalLine((12, 285, 315, 1)) | |
# | |
self.w.overwriteExisting = CheckBox((12, 295, 315, 27), "Overwrite existing glyphs", value=True) | |
# | |
self.w.divider3 = HorizontalLine((12, 330, 315, 1)) | |
# | |
self.w.widthTitle = TextBox((12, 350, 60, 17), "Width:") | |
self.w.widthEntry = EditText((55, 345, 107, 27)) | |
# | |
self.w.divider4 = HorizontalLine((12, 385, 315, 1)) | |
# | |
self.w.applyToAll = CheckBox((12, 400, 315, 27), "Apply to all open fonts", callback=self.callback_applyToToggle) | |
self.w.applyToCurrent = CheckBox((12, 425, 315, 27), "Apply to current font", value=True, callback=self.callback_applyToToggle) | |
# | |
self.w.calculatorButton = Button((12, -35, 100, 20), "Calculator", callback=self.callback_showCalculator) | |
# | |
self.populateGroupList() | |
# | |
self.w.open() | |
def populateGroupList(self): | |
groupNames = list(self.groups.keys()) | |
groupNames.sort() | |
self.w.groupList.set(groupNames) | |
def getSelectedGroup(self): | |
selection = self.w.groupList.getSelection() | |
if len(selection) != 1: | |
return None | |
index = selection[0] | |
title = self.w.groupList[index] | |
return self.groups[title] | |
def callback_addGroup(self, sender): | |
from copy import deepcopy | |
title = AskString("Group title:") | |
if title: | |
if title in self.groups: | |
return | |
self.groups[title] = deepcopy(defaultGroup) | |
self.populateGroupList() | |
def callback_removeGroup(self, sender): | |
selection = self.w.groupList.getSelection() | |
if selection: | |
for index in selection: | |
title = self.w.groupList[index] | |
del self.groups[title] | |
self.populateGroupList() | |
def callback_groupSelection(self, sender): | |
group = self.getSelectedGroup() | |
if group is None: | |
self.w.glyphList.set([]) | |
return | |
self.w.glyphList.set(group["glyphs"]) | |
self.w.groupWidthEntry.set(group["width"]) | |
if group["useAverage"]: | |
self.w.groupUseWidth.set(False) | |
self.w.groupWidthEntry.enable(False) | |
self.w.groupUseAverage.set(True) | |
else: | |
self.w.groupUseWidth.set(True) | |
self.w.groupWidthEntry.enable(True) | |
self.w.groupUseAverage.set(False) | |
def packGroupWidthData(self): | |
group = self.getSelectedGroup() | |
if not group: | |
return | |
group["width"] = self.w.groupWidthEntry.get() | |
group["useAverage"] = self.w.groupUseAverage.get() | |
def callback_groupWidthEntry(self, sender): | |
# validate the entry | |
widthString = sender.get() | |
if interpretGroupWidth(widthString, 100) is None: | |
Message("Invalid entry. The value must be a percentage (example: 50%) or a fixed value (example 100).") | |
else: | |
self.packGroupWidthData() | |
def callback_groupWidthToggle(self, sender): | |
opposite = not sender.get() | |
if sender == self.w.groupUseWidth: | |
self.w.groupUseAverage.set(opposite) | |
else: | |
self.w.groupUseWidth.set(opposite) | |
self.w.groupWidthEntry.enable(not self.w.groupUseAverage.get()) | |
self.packGroupWidthData() | |
def callback_addGlyph(self, sender): | |
group = self.getSelectedGroup() | |
if group is None: | |
return | |
glyphName = AskString("Glyph name:") | |
if glyphName: | |
if glyphName in group["glyphs"]: | |
return | |
group["glyphs"].append(glyphName) | |
self.callback_groupSelection(self.w.groupList) | |
def callback_removeGlyph(self, sender): | |
group = self.getSelectedGroup() | |
if group is None: | |
return | |
selection = self.w.glyphList.getSelection() | |
if selection: | |
glyphNames = [self.w.glyphList[index] for index in selection] | |
for glyphName in glyphNames: | |
group["glyphs"].remove(glyphName) | |
self.callback_groupSelection(self.w.groupList) | |
def callback_applyToToggle(self, sender): | |
opposite = not sender.get() | |
if sender == self.w.applyToAll: | |
self.w.applyToCurrent.set(opposite) | |
else: | |
self.w.applyToAll.set(opposite) | |
def callback_showCalculator(self, sender): | |
CalculatorInterface() | |
def callback_ok(self, sender): | |
incompleteData = False | |
# | |
width = self.w.widthEntry.get() | |
try: | |
width = int(width) | |
except NameError: | |
incompleteData = True | |
# | |
inputSuffix = self.w.inputSuffixEntry.get() | |
outputSuffix = self.w.outputSuffixEntry.get() | |
if not inputSuffix and not outputSuffix: | |
incompleteData = True | |
# | |
overwriteExisting = self.w.overwriteExisting.get() | |
# | |
if not self.groups: | |
incompleteData = True | |
# | |
if incompleteData: | |
Message("Can not proceed: Incomplete data.") | |
else: | |
if self.w.applyToAll.get(): | |
fonts = AllFonts() | |
else: | |
fonts = [CurrentFont()] | |
tickCount = len(self.groups) | |
progressBar = ProgressBar(title="Processing...", ticks=tickCount) | |
for group in self.groups.values(): | |
glyphs = group["glyphs"] | |
groupWidth = interpretGroupWidth(group["width"], width) | |
useAverage = group["useAverage"] | |
if useAverage: | |
groupWidth = findAverageWidth(fonts, glyphs) | |
makeTabular(fonts, glyphs, groupWidth, inputSuffix, outputSuffix, overwriteExisting) | |
progressBar.tick() | |
for font in fonts: | |
font.update() | |
progressBar.close() | |
class CalculatorInterface(object): | |
def __init__(self): | |
self.allFonts = AllFonts() | |
self.currentFont = CurrentFont() | |
# | |
self.w = ModalDialog((174, 300)) | |
# | |
self.w.glyphTitle = TextBox((12, 15, 50, 17), "Glyphs") | |
self.w.glyphAddButton = Button((99, 10, 28, 20), "+", callback=self.callback_addGlyph) | |
self.w.glyphRemoveButton = Button((134, 10, 28, 20), "-", callback=self.callback_removeGlyph) | |
self.w.glyphList = List((12, 40, 150, 100), []) | |
# | |
self.w.allFonts = CheckBox((12, 155, 150, 27), "All open fonts", callback=self.callback_fontToggle) | |
self.w.currentFont = CheckBox((12, 180, 150, 27), "Current font", value=True, callback=self.callback_fontToggle) | |
# | |
self.w.outputField = TextBox((12, 220, 150, 17), "Average: 0") | |
# | |
self.w.open() | |
def calculate(self): | |
if self.w.allFonts.get(): | |
fonts = self.allFonts | |
else: | |
fonts = [self.currentFont] | |
widths = [] | |
for glyphName in self.w.glyphList.get(): | |
for font in fonts: | |
if font.has_key(glyphName): | |
widths.append(font[glyphName].width) | |
if not len(widths): | |
self.w.outputField.set("Average: 0") | |
else: | |
average = sum(widths) / len(widths) | |
self.w.outputField.set("Average: %d" % average) | |
def callback_addGlyph(self, sender): | |
glyphName = AskString("Glyph name:") | |
if not glyphName: | |
return | |
if glyphName not in self.w.glyphList: | |
self.w.glyphList.append(glyphName) | |
self.calculate() | |
def callback_removeGlyph(self, sender): | |
selection = self.w.glyphList.getSelection() | |
if selection: | |
selection.sort() | |
selection.reverse() | |
for index in selection: | |
del self.w.glyphList[index] | |
self.calculate() | |
def callback_fontToggle(self, sender): | |
opposite = not sender.get() | |
if sender == self.w.allFonts: | |
self.w.currentFont.set(opposite) | |
else: | |
self.w.allFonts.set(opposite) | |
self.calculate() | |
TabularInterface() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I think Frederik removed dialogKit from the latest RF beta. It was a very old UI toolkit that worked in FontLab that I made a vanilla version of so that old scripts could work in RF. Here's the old vanilla wrapper:
https://github.com/typesupply/dialogKit/blob/master/Lib/dialogKit/_dkVanilla.py
You should be able to convert everything to vanilla except ModalDialog. Just make that a Window.
Ideally this tool would be updated to ezui. :)