mirror of
https://github.com/rsms/inter.git
synced 2024-12-12 05:35:49 +03:00
222 lines
6.5 KiB
Python
222 lines
6.5 KiB
Python
#MenuTitle: Adjust glyph box width
|
|
# -*- coding: utf-8 -*-
|
|
import GlyphsApp
|
|
from math import ceil, floor
|
|
from os.path import basename
|
|
import vanilla
|
|
from vanilla import FloatingWindow, TextBox, EditText, Button
|
|
|
|
prefsKey = "me.rsms.adjust-sidebearings"
|
|
|
|
|
|
# glyphs which should never be resized
|
|
excludedGlyphNames = set([
|
|
"emquad",
|
|
"emspace",
|
|
"enspace",
|
|
"thirdemspace",
|
|
"sixthemspace",
|
|
"quarteremspace",
|
|
"emdash", "emdash.case",
|
|
"endash", "endash.case",
|
|
])
|
|
|
|
class Script( object ):
|
|
def __init__(self):
|
|
self.valuesPrefsKey = prefsKey + ".delta." + basename(Glyphs.font.filepath)
|
|
# UI metrics
|
|
spacing = 8
|
|
height = 22
|
|
# calculate window height
|
|
minWinSize = (220, 120 + (len(Glyphs.font.masters) * (height + spacing)))
|
|
# create UI window
|
|
self.w = FloatingWindow(
|
|
minWinSize,
|
|
"Adjust sidebearings",
|
|
minSize=minWinSize,
|
|
maxSize=(500, 500),
|
|
autosaveName=prefsKey + ".win")
|
|
# layout UI controls
|
|
y = 16
|
|
self.w.label = TextBox((16, y, -16, height), "Sidebearing delta adjustment:")
|
|
y += height + spacing
|
|
|
|
inputWidth = 64
|
|
for master in Glyphs.font.masters:
|
|
setattr(self.w, "deltaLabel%s" % master.id,
|
|
TextBox((16, y, -16-inputWidth, height), master.name))
|
|
setattr(self.w, "deltaInput%s" % master.id,
|
|
EditText((-16-inputWidth, y, -16, height), "16"))
|
|
# print("self.w.deltaInputs[master.id]", getattr(self.w, "deltaInput%s" % master.id))
|
|
y += height + spacing
|
|
|
|
self.w.submitButton = Button(
|
|
(16, -16-height, -16, height),
|
|
"Adjust all sidebearings",
|
|
callback=self.onSubmit)
|
|
# finalize UI
|
|
self.w.setDefaultButton(self.w.submitButton)
|
|
self.loadPreferences()
|
|
self.w.bind("close", self.savePreferences)
|
|
|
|
# make sure window is large enough to show all masters
|
|
x, y, w, h = self.w.getPosSize()
|
|
if w < minWinSize[0] and h < minWinSize[1]:
|
|
self.w.setPosSize((x, y, minWinSize[0], minWinSize[1]), animate=False)
|
|
elif w < minWinSize[0]:
|
|
self.w.setPosSize((x, y, minWinSize[0], h), animate=False)
|
|
elif h < minWinSize[1]:
|
|
self.w.setPosSize((x, y, w, minWinSize[1]), animate=False)
|
|
|
|
self.w.open()
|
|
self.w.makeKey()
|
|
|
|
|
|
def getDeltaInputForMaster(self, masterId):
|
|
return getattr(self.w, "deltaInput%s" % masterId)
|
|
|
|
|
|
def loadPreferences(self):
|
|
try:
|
|
Glyphs.registerDefault(self.valuesPrefsKey, [])
|
|
for t in Glyphs.defaults[self.valuesPrefsKey]:
|
|
try:
|
|
masterId, value = t
|
|
self.getDeltaInputForMaster(masterId).set(value)
|
|
except:
|
|
pass
|
|
except:
|
|
print("failed to load preferences")
|
|
|
|
|
|
def savePreferences(self, sender):
|
|
try:
|
|
values = []
|
|
for master in Glyphs.font.masters:
|
|
values.append((master.id, self.getDeltaInputForMaster(master.id).get()))
|
|
Glyphs.defaults[self.valuesPrefsKey] = values
|
|
except:
|
|
print("failed to save preferences")
|
|
|
|
|
|
def onSubmit(self, sender):
|
|
try:
|
|
sender.enable(False)
|
|
if performFontChanges(self.action1):
|
|
self.w.close()
|
|
except Exception(e):
|
|
Glyphs.showMacroWindow()
|
|
print("error: %s" % e)
|
|
finally:
|
|
sender.enable(True)
|
|
|
|
|
|
def action1(self, font):
|
|
print(font)
|
|
|
|
deltas = {} # keyed on master.id
|
|
for master in Glyphs.font.masters:
|
|
deltas[master.id] = int(self.getDeltaInputForMaster(master.id).get())
|
|
syncLayers = set() # layers that need syncMetrics() to be called
|
|
|
|
# [debug] alterntive loop over a few select glyphs, for debugging.
|
|
# debugGlyphNames = set([
|
|
# ".null",
|
|
# "A", "Adieresis", "Lambda",
|
|
# "Bhook",
|
|
# "C", "Chook",
|
|
# "endash",
|
|
# "space",
|
|
# ])
|
|
# for g in [g for g in font.glyphs if g.name in debugGlyphNames]:
|
|
for g in font.glyphs:
|
|
# print(">> %s (%s)" % (g.name, g.productionName))
|
|
if g.name in excludedGlyphNames:
|
|
# glyph is exlcuded
|
|
print("ignoring excluded glyph %r" % g.name)
|
|
continue
|
|
g.beginUndo()
|
|
try:
|
|
for master in font.masters:
|
|
layer = g.layers[master.id]
|
|
delta = deltas[master.id]
|
|
if delta == 0:
|
|
# no adjustment
|
|
continue
|
|
|
|
if layer.width == 0:
|
|
# ignore glyphs with zero-width layers
|
|
print("ignoring zero-width glyph", g.name)
|
|
break
|
|
|
|
if layer.isAligned:
|
|
# ignore glyphs which are auto-aligned from components
|
|
print("ignoring auto-aligned glyph", g.name)
|
|
break
|
|
|
|
if len(layer.paths) == 0 and len(layer.components) == 0:
|
|
# adjust width instead of LSB & RSB of empty glyphs
|
|
if layer.widthMetricsKey is None:
|
|
layer.width = max(0, layer.width + (delta * 2))
|
|
else:
|
|
syncLayers.add(layer)
|
|
continue
|
|
|
|
# offset components by delta to counter-act effect of also applying delta
|
|
# to the component glyphs.
|
|
if layer.components is not None:
|
|
for cn in layer.components:
|
|
m = cn.transform
|
|
# auto-aligned or offset x or offset y or contains shapes
|
|
if not cn.automaticAlignment or m[4] != 0 or m[5] != 0 or len(layer.paths) > 0:
|
|
cn.transform = (
|
|
m[0], # x scale factor
|
|
m[1], # x skew factor
|
|
m[2], # y skew factor
|
|
m[3], # y scale factor
|
|
m[4] - delta, # x position (+ since transform is inverse)
|
|
m[5] # y position
|
|
)
|
|
|
|
# Note:
|
|
# layer metrics keys are expressions, e.g. "==H+10"
|
|
# glyph metrics keys are other glyphs, e.g. U+0041
|
|
|
|
if g.leftMetricsKey is None and layer.leftMetricsKey is None:
|
|
layer.LSB = layer.LSB + delta
|
|
else:
|
|
syncLayers.add(layer)
|
|
|
|
if g.rightMetricsKey is None and layer.rightMetricsKey is None:
|
|
layer.RSB = layer.RSB + delta
|
|
else:
|
|
syncLayers.add(layer)
|
|
|
|
finally:
|
|
g.endUndo()
|
|
|
|
# end for g in font
|
|
|
|
# sync layers that use metricsKey
|
|
if len(syncLayers) > 0:
|
|
print("Syncing LSB & RSB for %r layers..." % len(syncLayers))
|
|
for layer in syncLayers:
|
|
layer.syncMetrics()
|
|
print("Done")
|
|
|
|
|
|
|
|
def performFontChanges(f):
|
|
font = Glyphs.font
|
|
font.disableUpdateInterface()
|
|
try:
|
|
f(font)
|
|
return True # success (as a result, the ui window will be closed)
|
|
finally:
|
|
font.enableUpdateInterface()
|
|
return False # error
|
|
|
|
|
|
Glyphs.clearLog()
|
|
Script()
|