This repository has been archived on 2025-10-02. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
inter-font/misc/pylib/robofab/tools/accentBuilder.py
Rasmus Andersson 8234b62ab7 Speeds up font compilation by around 200%
Cython is used to compile some hot paths into native Python extensions.
These hot paths were identified through running ufocompile with the hotshot
profiler and then converting file by file to Cython, starting with the "hottest"
paths and continuing until returns were deminishing. This means that only a few
Python files were converted to Cython.

Closes #23
Closes #20 (really this time)
2017-09-04 11:12:34 -04:00

348 lines
12 KiB
Python
Executable file

"""A simple set of tools for building accented glyphs.
# Hey look! A demonstration:
from robofab.accentBuilder import AccentTools, buildRelatedAccentList
font = CurrentFont
# a list of accented glyphs that you want to build
myList=['Aacute', 'aacute']
# search for glyphs related to glyphs in myList and add them to myList
myList=buildRelatedAccentList(font, myList)+myList
# start the class
at=AccentTools(font, myList)
# clear away any anchors that exist (this is optional)
at.clearAnchors()
# add necessary anchors if you want to
at.buildAnchors(ucXOffset=20, ucYOffset=40, lcXOffset=15, lcYOffset=30)
# print a report of any errors that occured
at.printAnchorErrors()
# build the accented glyphs if you want to
at.buildAccents()
# print a report of any errors that occured
at.printAccentErrors()
"""
#XXX! This is *very* experimental! I think it works, but you never know.
from robofab.gString import lowercase_plain, accents, uppercase_plain, splitAccent, findAccentBase
from robofab.tools.toolsAll import readGlyphConstructions
import robofab
from robofab.interface.all.dialogs import ProgressBar
from robofab.world import RFWorld
inFontLab = RFWorld().inFontLab
anchorColor=125
accentColor=75
def stripSuffix(glyphName):
"""strip away unnecessary suffixes from a glyph name"""
if glyphName.find('.') != -1:
baseName = glyphName.split('.')[0]
if glyphName.find('.sc') != -1:
baseName = '.'.join([baseName, 'sc'])
return baseName
else:
return glyphName
def buildRelatedAccentList(font, list):
"""build a list of related glyphs suitable for use with AccentTools"""
searchList = []
baseGlyphs = {}
foundList = []
for glyphName in list:
splitNames = splitAccent(glyphName)
baseName = splitNames[0]
accentNames = splitNames[1]
if baseName not in searchList:
searchList.append(baseName)
if baseName not in baseGlyphs.keys():
baseGlyphs[baseName] = [accentNames]
else:
baseGlyphs[baseName].append(accentNames)
foundGlyphs = findRelatedGlyphs(font, searchList, doAccents=0)
for baseGlyph in foundGlyphs.keys():
for foundGlyph in foundGlyphs[baseGlyph]:
for accentNames in baseGlyphs[baseGlyph]:
foundList.append(makeAccentName(foundGlyph, accentNames))
return foundList
def findRelatedGlyphs(font, searchItem, doAccents=True):
"""Gather up a bunch of related glyph names. Send it either a
single glyph name 'a', or a list of glyph names ['a', 'x'] and it
returns a dict like: {'a': ['atilde', 'a.alt', 'a.swash']}. if doAccents
is False it will skip accented glyph names.
This is a relatively slow operation!"""
relatedGlyphs = {}
for name in font.keys():
base = name.split('.')[0]
if name not in relatedGlyphs.keys():
relatedGlyphs[name] = []
if base not in relatedGlyphs.keys():
relatedGlyphs[base] = []
if doAccents:
accentBase = findAccentBase(name)
if accentBase not in relatedGlyphs.keys():
relatedGlyphs[accentBase] = []
baseAccentBase = findAccentBase(base)
if baseAccentBase not in relatedGlyphs.keys():
relatedGlyphs[baseAccentBase] = []
if base != name and name not in relatedGlyphs[base]:
relatedGlyphs[base].append(name)
if doAccents:
if accentBase != name and name not in relatedGlyphs[accentBase]:
relatedGlyphs[accentBase].append(name)
if baseAccentBase != name and name not in relatedGlyphs[baseAccentBase]:
relatedGlyphs[baseAccentBase].append(name)
foundGlyphs = {}
if isinstance(searchItem, str):
searchList = [searchItem]
else:
searchList = searchItem
for glyph in searchList:
foundGlyphs[glyph] = relatedGlyphs[glyph]
return foundGlyphs
def makeAccentName(baseName, accentNames):
"""make an accented glyph name"""
if isinstance(accentNames, str):
accentNames = [accentNames]
build = []
if baseName.find('.') != -1:
base = baseName.split('.')[0]
suffix = baseName.split('.')[1]
else:
base = baseName
suffix = ''
build.append(base)
for accent in accentNames:
build.append(accent)
buildJoin = ''.join(build)
name = '.'.join([buildJoin, suffix])
return name
def nameBuster(glyphName, glyphConstruct):
stripedSuffixName = stripSuffix(glyphName)
suffix = None
errors = []
accentNames = []
baseName = glyphName
if glyphName.find('.') != -1:
suffix = glyphName.split('.')[1]
if glyphName.find('.sc') != -1:
suffix = glyphName.split('.sc')[1]
if stripedSuffixName not in glyphConstruct.keys():
errors.append('%s: %s not in glyph construction database'%(glyphName, stripedSuffixName))
else:
if suffix is None:
baseName = glyphConstruct[stripedSuffixName][0]
else:
if glyphName.find('.sc') != -1:
baseName = ''.join([glyphConstruct[stripedSuffixName][0], suffix])
else:
baseName = '.'.join([glyphConstruct[stripedSuffixName][0], suffix])
accentNames = glyphConstruct[stripedSuffixName][1:]
return (baseName, stripedSuffixName, accentNames, errors)
class AccentTools:
def __init__(self, font, accentList):
"""several tools for working with anchors and building accents"""
self.glyphConstructions = readGlyphConstructions()
self.accentList = accentList
self.anchorErrors = ['ANCHOR ERRORS:']
self.accentErrors = ['ACCENT ERRORS:']
self.font = font
def clearAnchors(self, doProgress=True):
"""clear all anchors in the font"""
tickCount = len(self.font)
if doProgress:
bar = ProgressBar("Cleaning all anchors...", tickCount)
tick = 0
for glyphName in self.accentList:
if doProgress:
bar.label(glyphName)
baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
existError = False
if len(errors) > 0:
existError = True
if not existError:
toClear = [baseName]
for accent, position in accentNames:
toClear.append(accent)
for glyphName in toClear:
try:
self.font[glyphName].clearAnchors()
except IndexError: pass
if doProgress:
bar.tick(tick)
tick = tick+1
if doProgress:
bar.close()
def buildAnchors(self, ucXOffset=0, ucYOffset=0, lcXOffset=0, lcYOffset=0, markGlyph=True, doProgress=True):
"""add the necessary anchors to the glyphs if they don't exist
some flag definitions:
uc/lc/X/YOffset=20 offset values for the anchors
markGlyph=1 mark the glyph that is created
doProgress=1 show a progress bar"""
accentOffset = 10
tickCount = len(self.accentList)
if doProgress:
bar = ProgressBar('Adding anchors...', tickCount)
tick = 0
for glyphName in self.accentList:
if doProgress:
bar.label(glyphName)
previousPositions = {}
baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
existError = False
if len(errors) > 0:
existError = True
for anchorError in errors:
self.anchorErrors.append(anchorError)
if not existError:
existError = False
try:
self.font[baseName]
except IndexError:
self.anchorErrors.append(' '.join([glyphName, ':', baseName, 'does not exist.']))
existError = True
for accentName, accentPosition in accentNames:
try:
self.font[accentName]
except IndexError:
self.anchorErrors.append(' '.join([glyphName, ':', accentName, 'does not exist.']))
existError = True
if not existError:
#glyph = self.font.newGlyph(glyphName, clear=True)
for accentName, accentPosition in accentNames:
if baseName.split('.')[0] in lowercase_plain:
xOffset = lcXOffset-accentOffset
yOffset = lcYOffset-accentOffset
else:
xOffset = ucXOffset-accentOffset
yOffset = ucYOffset-accentOffset
# should I add a cedilla and ogonek yoffset override here?
if accentPosition not in previousPositions.keys():
self._dropAnchor(self.font[baseName], accentPosition, xOffset, yOffset)
if markGlyph:
self.font[baseName].mark = anchorColor
if inFontLab:
self.font[baseName].update()
else:
self._dropAnchor(self.font[previousPositions[accentPosition]], accentPosition, xOffset, yOffset)
self._dropAnchor(self.font[accentName], accentPosition, accentOffset, accentOffset, doAccentPosition=1)
previousPositions[accentPosition] = accentName
if markGlyph:
self.font[accentName].mark = anchorColor
if inFontLab:
self.font[accentName].update()
if inFontLab:
self.font.update()
if doProgress:
bar.tick(tick)
tick = tick+1
if doProgress:
bar.close()
def printAnchorErrors(self):
"""print errors encounted during buildAnchors"""
if len(self.anchorErrors) == 1:
print 'No anchor errors encountered'
else:
for i in self.anchorErrors:
print i
def _dropAnchor(self, glyph, positionName, xOffset=0, yOffset=0, doAccentPosition=False):
"""anchor adding method. for internal use only."""
existingAnchorNames = []
for anchor in glyph.getAnchors():
existingAnchorNames.append(anchor.name)
if doAccentPosition:
positionName = ''.join(['_', positionName])
if positionName not in existingAnchorNames:
glyphLeft, glyphBottom, glyphRight, glyphTop = glyph.box
glyphXCenter = glyph.width/2
if positionName == 'top':
glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset))
elif positionName == 'bottom':
glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset))
elif positionName == 'left':
glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop))
elif positionName == 'right':
glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop))
elif positionName == '_top':
glyph.appendAnchor(positionName, (glyphXCenter, glyphBottom-yOffset))
elif positionName == '_bottom':
glyph.appendAnchor(positionName, (glyphXCenter, glyphTop+yOffset))
elif positionName == '_left':
glyph.appendAnchor(positionName, (glyphRight+xOffset, glyphTop))
elif positionName == '_right':
glyph.appendAnchor(positionName, (glyphLeft-xOffset, glyphTop))
if inFontLab:
glyph.update()
def buildAccents(self, clear=True, adjustWidths=True, markGlyph=True, doProgress=True):
"""build accented glyphs. some flag definitions:
clear=1 clear the glyphs if they already exist
markGlyph=1 mark the glyph that is created
doProgress=1 show a progress bar
adjustWidths=1 will fix right and left margins when left or right accents are added"""
tickCount = len(self.accentList)
if doProgress:
bar = ProgressBar('Building accented glyphs...', tickCount)
tick = 0
for glyphName in self.accentList:
if doProgress:
bar.label(glyphName)
existError = False
anchorError = False
baseName, stripedSuffixName, accentNames, errors = nameBuster(glyphName, self.glyphConstructions)
if len(errors) > 0:
existError = True
for accentError in errors:
self.accentErrors.append(accentError)
if not existError:
baseAnchors = []
try:
self.font[baseName]
except IndexError:
self.accentErrors.append('%s: %s does not exist.'%(glyphName, baseName))
existError = True
else:
for anchor in self.font[baseName].anchors:
baseAnchors.append(anchor.name)
for accentName, accentPosition in accentNames:
accentAnchors = []
try:
self.font[accentName]
except IndexError:
self.accentErrors.append('%s: %s does not exist.'%(glyphName, accentName))
existError = True
else:
for anchor in self.font[accentName].getAnchors():
accentAnchors.append(anchor.name)
if accentPosition not in baseAnchors:
self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, accentPosition, baseName))
anchorError = True
if ''.join(['_', accentPosition]) not in accentAnchors:
self.accentErrors.append('%s: %s not in %s anchors.'%(glyphName, ''.join(['_', accentPosition]), accentName))
anchorError = True
if not existError and not anchorError:
destination = self.font.compileGlyph(glyphName, baseName, self.glyphConstructions[stripedSuffixName][1:], adjustWidths)
if markGlyph:
destination.mark = accentColor
if doProgress:
bar.tick(tick)
tick = tick+1
if doProgress:
bar.close()
def printAccentErrors(self):
"""print errors encounted during buildAccents"""
if len(self.accentErrors) == 1:
print 'No accent errors encountered'
else:
for i in self.accentErrors:
print i