summaryrefslogtreecommitdiff
path: root/misc/pylib/robofab/tools/accentBuilder.py
blob: d122222fd1c22803c9a066a5ab8840d75d5bbdee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
"""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