1
1
mirror of https://github.com/rsms/inter.git synced 2024-11-23 11:43:47 +03:00
inter/misc/glyphs-scripts/adjust-box-width.py

223 lines
6.5 KiB
Python

#MenuTitle: Adjust glyph box width
# -*- coding: utf-8 -*-
from __future__ import print_function
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()