| 1 |
""" |
|---|
| 2 |
Base classes for the Unified Font Objects (UFO), |
|---|
| 3 |
a series of classes that deal with fonts, glyphs, |
|---|
| 4 |
contours and related things. |
|---|
| 5 |
|
|---|
| 6 |
Unified Font Objects are: |
|---|
| 7 |
- platform independent |
|---|
| 8 |
- application independent |
|---|
| 9 |
|
|---|
| 10 |
About Object Inheritance: |
|---|
| 11 |
objectsFL and objectsRF objects inherit |
|---|
| 12 |
methods and attributes from these objects. |
|---|
| 13 |
In other words, if it is in here, you can |
|---|
| 14 |
do it with the objectsFL and objectsRF. |
|---|
| 15 |
""" |
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
from __future__ import generators |
|---|
| 19 |
|
|---|
| 20 |
|
|---|
| 21 |
from robofab import RoboFabError |
|---|
| 22 |
from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect |
|---|
| 23 |
from fontTools.pens.basePen import AbstractPen |
|---|
| 24 |
import math |
|---|
| 25 |
import copy |
|---|
| 26 |
|
|---|
| 27 |
|
|---|
| 28 |
MOVE = 'move' |
|---|
| 29 |
LINE = 'line' |
|---|
| 30 |
CORNER = 'corner' |
|---|
| 31 |
CURVE = 'curve' |
|---|
| 32 |
QCURVE = 'qcurve' |
|---|
| 33 |
OFFCURVE = 'offcurve' |
|---|
| 34 |
|
|---|
| 35 |
DEGREE = 180 / math.pi |
|---|
| 36 |
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 |
|
|---|
| 40 |
postScriptHintDataLibKey = "org.robofab.postScriptHintData" |
|---|
| 41 |
|
|---|
| 42 |
|
|---|
| 43 |
class BasePostScriptFontHintValues(object): |
|---|
| 44 |
""" Base class for font-level postscript hinting information. |
|---|
| 45 |
Blues values, stem values. |
|---|
| 46 |
""" |
|---|
| 47 |
|
|---|
| 48 |
_attrs = { |
|---|
| 49 |
|
|---|
| 50 |
'blueFuzz': {'default': None, 'max':1}, |
|---|
| 51 |
'blueScale': {'default': None, 'max':1}, |
|---|
| 52 |
'blueShift': {'default': None, 'max':1}, |
|---|
| 53 |
'forceBold': {'default': None, 'max':1}, |
|---|
| 54 |
'blueValues': {'default': None, 'max':13}, |
|---|
| 55 |
'otherBlues': {'default': None, 'max':9}, |
|---|
| 56 |
'familyBlues': {'default': None, 'max':13}, |
|---|
| 57 |
'familyOtherBlues': {'default': None, 'max':9}, |
|---|
| 58 |
'vStems': {'default': None, 'max':11}, |
|---|
| 59 |
'hStems': {'default': None, 'max':11}, |
|---|
| 60 |
} |
|---|
| 61 |
|
|---|
| 62 |
def __init__(self): |
|---|
| 63 |
for name in self._attrs.keys(): |
|---|
| 64 |
setattr(self, name, self._attrs[name]) |
|---|
| 65 |
|
|---|
| 66 |
def getParent(self): |
|---|
| 67 |
"""this method will be overwritten with a weakref if there is a parent.""" |
|---|
| 68 |
return None |
|---|
| 69 |
|
|---|
| 70 |
def setParent(self, parent): |
|---|
| 71 |
import weakref |
|---|
| 72 |
self.getParent = weakref.ref(parent) |
|---|
| 73 |
|
|---|
| 74 |
def fromDict(self, data): |
|---|
| 75 |
for name in self._attrs: |
|---|
| 76 |
if name in data: |
|---|
| 77 |
setattr(self, name, data[name]) |
|---|
| 78 |
|
|---|
| 79 |
def asDict(self): |
|---|
| 80 |
d = {} |
|---|
| 81 |
for name in self._attrs: |
|---|
| 82 |
try: |
|---|
| 83 |
value = getattr(self, name) |
|---|
| 84 |
except AttributeError: |
|---|
| 85 |
print "%s attribute not supported"%name |
|---|
| 86 |
continue |
|---|
| 87 |
if value is not None or not value: |
|---|
| 88 |
d[name] = getattr(self, name) |
|---|
| 89 |
return d |
|---|
| 90 |
|
|---|
| 91 |
def __repr__(self): |
|---|
| 92 |
return "<PostScript Font Hints Values>" |
|---|
| 93 |
|
|---|
| 94 |
|
|---|
| 95 |
class RoboFabInterpolationError(Exception): pass |
|---|
| 96 |
|
|---|
| 97 |
|
|---|
| 98 |
def _interpolate(a,b,v): |
|---|
| 99 |
"""interpolate values by factor v""" |
|---|
| 100 |
return a + (b-a) * v |
|---|
| 101 |
|
|---|
| 102 |
def _interpolatePt((xa, ya),(xb, yb),v): |
|---|
| 103 |
"""interpolate point by factor v""" |
|---|
| 104 |
if not isinstance(v, tuple): |
|---|
| 105 |
xv = v |
|---|
| 106 |
yv = v |
|---|
| 107 |
else: |
|---|
| 108 |
xv, yv = v |
|---|
| 109 |
return xa + (xb-xa) * xv, ya + (yb-ya) * yv |
|---|
| 110 |
|
|---|
| 111 |
def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)): |
|---|
| 112 |
"""scale a point from a center point""" |
|---|
| 113 |
ogCenter = (centerX, centerY) |
|---|
| 114 |
scaledCenter = (centerX * scaleX, centerY * scaleY) |
|---|
| 115 |
shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) |
|---|
| 116 |
scaledPointX = (pointX * scaleX) - shiftVal[0] |
|---|
| 117 |
scaledPointY = (pointY * scaleY) - shiftVal[1] |
|---|
| 118 |
return (scaledPointX, scaledPointY) |
|---|
| 119 |
|
|---|
| 120 |
def _box(objectToMeasure, fontObject=None): |
|---|
| 121 |
"""calculate the bounds of the object and return it as a (xMin, yMin, xMax, yMax)""" |
|---|
| 122 |
|
|---|
| 123 |
from robofab.pens.boundsPen import BoundsPen |
|---|
| 124 |
boundsPen = BoundsPen(glyphSet=fontObject) |
|---|
| 125 |
objectToMeasure.draw(boundsPen) |
|---|
| 126 |
bounds = boundsPen.bounds |
|---|
| 127 |
if bounds is None: |
|---|
| 128 |
bounds = (0, 0, 0, 0) |
|---|
| 129 |
return bounds |
|---|
| 130 |
|
|---|
| 131 |
def roundPt((x, y)): |
|---|
| 132 |
"""Round a vector""" |
|---|
| 133 |
return int(round(x)), int(round(y)) |
|---|
| 134 |
|
|---|
| 135 |
def addPt(ptA, ptB): |
|---|
| 136 |
"""Add two vectors""" |
|---|
| 137 |
return ptA[0] + ptB[0], ptA[1] + ptB[1] |
|---|
| 138 |
|
|---|
| 139 |
def subPt(ptA, ptB): |
|---|
| 140 |
"""Substract two vectors""" |
|---|
| 141 |
return ptA[0] - ptB[0], ptA[1] - ptB[1] |
|---|
| 142 |
|
|---|
| 143 |
def mulPt(ptA, scalar): |
|---|
| 144 |
"""Multiply a vector with scalar""" |
|---|
| 145 |
if not isinstance(scalar, tuple): |
|---|
| 146 |
f1 = scalar |
|---|
| 147 |
f2 = scalar |
|---|
| 148 |
else: |
|---|
| 149 |
f1, f2 = scalar |
|---|
| 150 |
return ptA[0]*f1, ptA[1]*f2 |
|---|
| 151 |
|
|---|
| 152 |
def relativeBCPIn(anchor, BCPIn): |
|---|
| 153 |
"""convert absolute incoming bcp value to a relative value""" |
|---|
| 154 |
return (BCPIn[0] - anchor[0], BCPIn[1] - anchor[1]) |
|---|
| 155 |
|
|---|
| 156 |
def absoluteBCPIn(anchor, BCPIn): |
|---|
| 157 |
"""convert relative incoming bcp value to an absolute value""" |
|---|
| 158 |
return (BCPIn[0] + anchor[0], BCPIn[1] + anchor[1]) |
|---|
| 159 |
|
|---|
| 160 |
def relativeBCPOut(anchor, BCPOut): |
|---|
| 161 |
"""convert absolute outgoing bcp value to a relative value""" |
|---|
| 162 |
return (BCPOut[0] - anchor[0], BCPOut[1] - anchor[1]) |
|---|
| 163 |
|
|---|
| 164 |
def absoluteBCPOut(anchor, BCPOut): |
|---|
| 165 |
"""convert relative outgoing bcp value to an absolute value""" |
|---|
| 166 |
return (BCPOut[0] + anchor[0], BCPOut[1] + anchor[1]) |
|---|
| 167 |
|
|---|
| 168 |
class FuzzyNumber(object): |
|---|
| 169 |
|
|---|
| 170 |
def __init__(self, value, threshold): |
|---|
| 171 |
self.value = value |
|---|
| 172 |
self.threshold = threshold |
|---|
| 173 |
|
|---|
| 174 |
def __cmp__(self, other): |
|---|
| 175 |
if abs(self.value - other.value) < self.threshold: |
|---|
| 176 |
return 0 |
|---|
| 177 |
else: |
|---|
| 178 |
return cmp(self.value, other.value) |
|---|
| 179 |
|
|---|
| 180 |
|
|---|
| 181 |
class RBaseObject(object): |
|---|
| 182 |
|
|---|
| 183 |
"""Base class for wrapper objects""" |
|---|
| 184 |
|
|---|
| 185 |
attrMap= {} |
|---|
| 186 |
_title = "RoboFab Wrapper" |
|---|
| 187 |
|
|---|
| 188 |
def __init__(self): |
|---|
| 189 |
self._object = {} |
|---|
| 190 |
self.changed = False |
|---|
| 191 |
self.selected = False |
|---|
| 192 |
|
|---|
| 193 |
def __len__(self): |
|---|
| 194 |
return len(self._object) |
|---|
| 195 |
|
|---|
| 196 |
def __repr__(self): |
|---|
| 197 |
try: |
|---|
| 198 |
name = `self._object` |
|---|
| 199 |
except: |
|---|
| 200 |
name = "None" |
|---|
| 201 |
return "<%s for %s>" %(self._title, name) |
|---|
| 202 |
|
|---|
| 203 |
def copy(self, aParent=None): |
|---|
| 204 |
"""Duplicate this object. Pass an object for parenting if you want.""" |
|---|
| 205 |
n = self.__class__() |
|---|
| 206 |
if aParent is not None: |
|---|
| 207 |
n.setParent(aParent) |
|---|
| 208 |
elif self.getParent() is not None: |
|---|
| 209 |
n.setParent(self.getParent()) |
|---|
| 210 |
dont = ['getParent'] |
|---|
| 211 |
for k in self.__dict__.keys(): |
|---|
| 212 |
if k in dont: |
|---|
| 213 |
continue |
|---|
| 214 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 215 |
dup = self.__dict__[k].copy(n) |
|---|
| 216 |
else: |
|---|
| 217 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 218 |
setattr(n, k, dup) |
|---|
| 219 |
return n |
|---|
| 220 |
|
|---|
| 221 |
def round(self): |
|---|
| 222 |
pass |
|---|
| 223 |
|
|---|
| 224 |
def isRobofab(self): |
|---|
| 225 |
"""Presence of this method indicates a Robofab object""" |
|---|
| 226 |
return 1 |
|---|
| 227 |
|
|---|
| 228 |
def naked(self): |
|---|
| 229 |
"""Return the wrapped object itself, in case it is needed for direct access.""" |
|---|
| 230 |
return self._object |
|---|
| 231 |
|
|---|
| 232 |
def setChanged(self, state=True): |
|---|
| 233 |
self.changed = state |
|---|
| 234 |
|
|---|
| 235 |
def getParent(self): |
|---|
| 236 |
"""this method will be overwritten with a weakref if there is a parent.""" |
|---|
| 237 |
return None |
|---|
| 238 |
|
|---|
| 239 |
def setParent(self, parent): |
|---|
| 240 |
import weakref |
|---|
| 241 |
self.getParent = weakref.ref(parent) |
|---|
| 242 |
|
|---|
| 243 |
def _writeXML(self, writer): |
|---|
| 244 |
pass |
|---|
| 245 |
|
|---|
| 246 |
def dump(self, private=False): |
|---|
| 247 |
"""Print a dump of this object to the std out.""" |
|---|
| 248 |
from robofab.tools.objectDumper import dumpObject |
|---|
| 249 |
dumpObject(self, private) |
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 |
|
|---|
| 253 |
class BaseFont(RBaseObject): |
|---|
| 254 |
|
|---|
| 255 |
"""Base class for all font objects.""" |
|---|
| 256 |
|
|---|
| 257 |
_allFonts = [] |
|---|
| 258 |
|
|---|
| 259 |
def __init__(self): |
|---|
| 260 |
import weakref |
|---|
| 261 |
RBaseObject.__init__(self) |
|---|
| 262 |
self.changed = False |
|---|
| 263 |
self._allFonts.append(weakref.ref(self)) |
|---|
| 264 |
self._supportHints = False |
|---|
| 265 |
|
|---|
| 266 |
def __repr__(self): |
|---|
| 267 |
try: |
|---|
| 268 |
name = self.info.fullName |
|---|
| 269 |
except AttributeError: |
|---|
| 270 |
name = "unnamed_font" |
|---|
| 271 |
return "<RFont font for %s>" %(name) |
|---|
| 272 |
|
|---|
| 273 |
def __eq__(self, other): |
|---|
| 274 |
|
|---|
| 275 |
return self._compare(other) |
|---|
| 276 |
|
|---|
| 277 |
def _compare(self, other): |
|---|
| 278 |
"""Compare this font to other. RF and FL UFO implementations need |
|---|
| 279 |
slightly different ways of comparing fonts. This method does the |
|---|
| 280 |
basic stuff. Start with simple and quick comparisons, then move into |
|---|
| 281 |
detailed comparisons of glyphs.""" |
|---|
| 282 |
if not hasattr(other, "fileName"): |
|---|
| 283 |
return False |
|---|
| 284 |
if self.fileName is not None and self.fileName == other.fileName: |
|---|
| 285 |
return True |
|---|
| 286 |
if self.fileName <> other.fileName: |
|---|
| 287 |
return False |
|---|
| 288 |
|
|---|
| 289 |
|
|---|
| 290 |
|
|---|
| 291 |
try: |
|---|
| 292 |
if len(self) <> len(other): |
|---|
| 293 |
return False |
|---|
| 294 |
except TypeError: |
|---|
| 295 |
return False |
|---|
| 296 |
|
|---|
| 297 |
namesSelf = self.keys() |
|---|
| 298 |
namesOther = other.keys() |
|---|
| 299 |
namesSelf.sort() |
|---|
| 300 |
namesOther.sort() |
|---|
| 301 |
for i in range(len(namesSelf)): |
|---|
| 302 |
if namesSelf[i] <> namesOther[i]: |
|---|
| 303 |
return False |
|---|
| 304 |
for c in self: |
|---|
| 305 |
if not c == other[c.name]: |
|---|
| 306 |
return False |
|---|
| 307 |
return True |
|---|
| 308 |
|
|---|
| 309 |
def keys(self): |
|---|
| 310 |
|
|---|
| 311 |
raise NotImplementedError |
|---|
| 312 |
|
|---|
| 313 |
def __iter__(self): |
|---|
| 314 |
for glyphName in self.keys(): |
|---|
| 315 |
yield self.getGlyph(glyphName) |
|---|
| 316 |
|
|---|
| 317 |
def __getitem__(self, glyphName): |
|---|
| 318 |
return self.getGlyph(glyphName) |
|---|
| 319 |
|
|---|
| 320 |
def _hasChanged(self): |
|---|
| 321 |
|
|---|
| 322 |
self.setChanged(True) |
|---|
| 323 |
|
|---|
| 324 |
def update(self): |
|---|
| 325 |
"""update the font""" |
|---|
| 326 |
pass |
|---|
| 327 |
|
|---|
| 328 |
def close(self, save=1): |
|---|
| 329 |
"""Close the font, saving is optional.""" |
|---|
| 330 |
pass |
|---|
| 331 |
|
|---|
| 332 |
def round(self): |
|---|
| 333 |
"""round all of the points in all of the glyphs""" |
|---|
| 334 |
for glyph in self.glyphs: |
|---|
| 335 |
glyph.round() |
|---|
| 336 |
|
|---|
| 337 |
def autoUnicodes(self): |
|---|
| 338 |
"""Using fontTools.agl, assign Unicode lists to all glyphs in the font""" |
|---|
| 339 |
for glyph in self: |
|---|
| 340 |
glyph.autoUnicodes() |
|---|
| 341 |
|
|---|
| 342 |
def getCharacterMapping(self): |
|---|
| 343 |
"""Create a dictionary of unicode -> [glyphname, ...] mappings. |
|---|
| 344 |
Note that this dict is created each time this method is called, |
|---|
| 345 |
which can make it expensive for larger fonts. All glyphs are loaded. |
|---|
| 346 |
Note that one glyph can have multiple unicode values, |
|---|
| 347 |
and a unicode value can have multiple glyphs pointing to it.""" |
|---|
| 348 |
map = {} |
|---|
| 349 |
for glyph in self: |
|---|
| 350 |
for u in glyph.unicodes: |
|---|
| 351 |
if not map.has_key(u): |
|---|
| 352 |
map[u] = [] |
|---|
| 353 |
map[u].append(glyph.name) |
|---|
| 354 |
return map |
|---|
| 355 |
|
|---|
| 356 |
def getReverseComponentMapping(self): |
|---|
| 357 |
""" |
|---|
| 358 |
Get a reversed map of component references in the font. |
|---|
| 359 |
{ |
|---|
| 360 |
'A' : ['Aacute', 'Aring'] |
|---|
| 361 |
'acute' : ['Aacute'] |
|---|
| 362 |
'ring' : ['Aring'] |
|---|
| 363 |
etc. |
|---|
| 364 |
} |
|---|
| 365 |
""" |
|---|
| 366 |
map = {} |
|---|
| 367 |
for glyph in self: |
|---|
| 368 |
glyphName = glyph.name |
|---|
| 369 |
for component in glyph.components: |
|---|
| 370 |
baseGlyphName = component.baseGlyph |
|---|
| 371 |
if not map.has_key(baseGlyphName): |
|---|
| 372 |
map[baseGlyphName] = [] |
|---|
| 373 |
map[baseGlyphName].append(glyphName) |
|---|
| 374 |
return map |
|---|
| 375 |
|
|---|
| 376 |
def compileGlyph(self, glyphName, baseName, accentNames, \ |
|---|
| 377 |
adjustWidth=False, preflight=False, printErrors=True): |
|---|
| 378 |
"""Compile components into a new glyph using components and anchorpoints. |
|---|
| 379 |
font: the font |
|---|
| 380 |
glyphName: the name of the glyph where it all needs to go |
|---|
| 381 |
baseName: the name of the base glyph |
|---|
| 382 |
accentNames: a list of accentName, anchorName tuples, [('acute', 'top'), etc] |
|---|
| 383 |
""" |
|---|
| 384 |
baseAnchors = ['left', 'right', 'top', 'bottom'] |
|---|
| 385 |
accentAnchors = ['_left', '_right', '_top', '_bottom'] |
|---|
| 386 |
anchors = {} |
|---|
| 387 |
errors = {} |
|---|
| 388 |
|
|---|
| 389 |
baseGlyph = self[baseName] |
|---|
| 390 |
|
|---|
| 391 |
for anchor in baseGlyph.getAnchors(): |
|---|
| 392 |
if anchor.name in baseAnchors: |
|---|
| 393 |
anchors[anchor.name] = anchor.position |
|---|
| 394 |
|
|---|
| 395 |
destGlyph = self.newGlyph(glyphName, clear=True) |
|---|
| 396 |
if not preflight: |
|---|
| 397 |
|
|---|
| 398 |
destGlyph.appendComponent(baseName) |
|---|
| 399 |
destGlyph.width = baseGlyph.width |
|---|
| 400 |
|
|---|
| 401 |
for accentName, accentPosition in accentNames: |
|---|
| 402 |
|
|---|
| 403 |
try: |
|---|
| 404 |
accent = self[accentName] |
|---|
| 405 |
except IndexError: |
|---|
| 406 |
errors["glyph '%s' is missing in font %s"%(accentName, self.fullName)] = 1 |
|---|
| 407 |
continue |
|---|
| 408 |
localAnchors = {} |
|---|
| 409 |
foundAnchor = None |
|---|
| 410 |
accX = accent.getAnchors() |
|---|
| 411 |
|
|---|
| 412 |
for anchor in accX: |
|---|
| 413 |
localAnchors[anchor.name] = anchor.position |
|---|
| 414 |
|
|---|
| 415 |
for anchorName in accentAnchors: |
|---|
| 416 |
|
|---|
| 417 |
if anchorName in localAnchors: |
|---|
| 418 |
|
|---|
| 419 |
if ''.join(['_', accentPosition]) == anchorName: |
|---|
| 420 |
foundAnchor = anchorName |
|---|
| 421 |
break |
|---|
| 422 |
if foundAnchor: |
|---|
| 423 |
|
|---|
| 424 |
accentAnchorX, accentAnchorY = localAnchors[foundAnchor] |
|---|
| 425 |
|
|---|
| 426 |
try: |
|---|
| 427 |
baseX, baseY = anchors[foundAnchor[1:]] |
|---|
| 428 |
except KeyError: |
|---|
| 429 |
errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info.fullName)]=1 |
|---|
| 430 |
continue |
|---|
| 431 |
|
|---|
| 432 |
xShift = baseX - accentAnchorX |
|---|
| 433 |
yShift = baseY - accentAnchorY |
|---|
| 434 |
|
|---|
| 435 |
if not preflight: |
|---|
| 436 |
destGlyph.appendComponent(accentName, offset=(xShift, yShift)) |
|---|
| 437 |
|
|---|
| 438 |
if foundAnchor[1:] in localAnchors: |
|---|
| 439 |
newX, newY = localAnchors[foundAnchor[1:]] |
|---|
| 440 |
newX = newX+xShift |
|---|
| 441 |
newY = newY+yShift |
|---|
| 442 |
anchors[foundAnchor[1:]] = (newX, newY) |
|---|
| 443 |
|
|---|
| 444 |
if adjustWidth and not preflight: |
|---|
| 445 |
for accentName, accentPosition in accentNames: |
|---|
| 446 |
|
|---|
| 447 |
try: |
|---|
| 448 |
accent = self[accentName] |
|---|
| 449 |
except IndexError: |
|---|
| 450 |
continue |
|---|
| 451 |
if accent is None: continue |
|---|
| 452 |
|
|---|
| 453 |
|
|---|
| 454 |
|
|---|
| 455 |
|
|---|
| 456 |
|
|---|
| 457 |
|
|---|
| 458 |
if accentPosition == 'right': |
|---|
| 459 |
destGlyph.rightMargin = self[accentName].rightMargin |
|---|
| 460 |
elif accentPosition == 'left': |
|---|
| 461 |
destGlyph.leftMargin = self[accentName].leftMargin |
|---|
| 462 |
if preflight: |
|---|
| 463 |
return errors.keys() |
|---|
| 464 |
if printErrors: |
|---|
| 465 |
for px in errors.keys(): |
|---|
| 466 |
print px |
|---|
| 467 |
return destGlyph |
|---|
| 468 |
|
|---|
| 469 |
def generateGlyph(self, glyphName, replace=1, preflight=False, printErrors=True): |
|---|
| 470 |
"""Generate a glyph and return it. Assembled from GlyphConstruction.txt""" |
|---|
| 471 |
from robofab.tools.toolsAll import readGlyphConstructions |
|---|
| 472 |
con = readGlyphConstructions() |
|---|
| 473 |
entry = con.get(glyphName, None) |
|---|
| 474 |
if not entry: |
|---|
| 475 |
print "glyph '%s' is not listed in the robofab/Data/GlyphConstruction.txt"%(glyphName) |
|---|
| 476 |
return |
|---|
| 477 |
baseName = con[glyphName][0] |
|---|
| 478 |
parts = con[glyphName][1:] |
|---|
| 479 |
return self.compileGlyph(glyphName, baseName, parts, adjustWidth=1, preflight=preflight, printErrors=printErrors) |
|---|
| 480 |
|
|---|
| 481 |
def interpolate(self, factor, minFont, maxFont, suppressError=True, analyzeOnly=False, doProgress=False): |
|---|
| 482 |
"""Traditional interpolation method. Interpolates by factor between minFont and maxFont. |
|---|
| 483 |
suppressError will supress all tracebacks and analyze only will not perform the interpolation |
|---|
| 484 |
but it will analyze all glyphs and return a dict of problems.""" |
|---|
| 485 |
from sets import Set |
|---|
| 486 |
errors = {} |
|---|
| 487 |
if not isinstance(factor, tuple): |
|---|
| 488 |
factor = factor, factor |
|---|
| 489 |
minGlyphNames = minFont.keys() |
|---|
| 490 |
maxGlyphNames = maxFont.keys() |
|---|
| 491 |
allGlyphNames = list(Set(minGlyphNames) | Set(maxGlyphNames)) |
|---|
| 492 |
if doProgress: |
|---|
| 493 |
from robofab.interface.all.dialogs import ProgressBar |
|---|
| 494 |
progress = ProgressBar('Interpolating...', len(allGlyphNames)) |
|---|
| 495 |
tickCount = 0 |
|---|
| 496 |
|
|---|
| 497 |
self.info.ascender = _interpolate(minFont.info.ascender, maxFont.info.ascender, factor[1]) |
|---|
| 498 |
self.info.descender = _interpolate(minFont.info.descender, maxFont.info.descender, factor[1]) |
|---|
| 499 |
|
|---|
| 500 |
for glyphName in allGlyphNames: |
|---|
| 501 |
if doProgress: |
|---|
| 502 |
progress.label(glyphName) |
|---|
| 503 |
fatalError = False |
|---|
| 504 |
if glyphName not in minGlyphNames: |
|---|
| 505 |
fatalError = True |
|---|
| 506 |
if not errors.has_key('Missing Glyphs'): |
|---|
| 507 |
errors['Missing Glyphs'] = [] |
|---|
| 508 |
errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.fullName)) |
|---|
| 509 |
if glyphName not in maxGlyphNames: |
|---|
| 510 |
fatalError = True |
|---|
| 511 |
if not errors.has_key('Missing Glyphs'): |
|---|
| 512 |
errors['Missing Glyphs'] = [] |
|---|
| 513 |
errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.fullName)) |
|---|
| 514 |
|
|---|
| 515 |
if not fatalError: |
|---|
| 516 |
|
|---|
| 517 |
|
|---|
| 518 |
|
|---|
| 519 |
oldLib = {} |
|---|
| 520 |
oldMark = None |
|---|
| 521 |
oldNote = None |
|---|
| 522 |
if self.has_key(glyphName): |
|---|
| 523 |
glyph = self[glyphName] |
|---|
| 524 |
oldLib = dict(glyph.lib) |
|---|
| 525 |
oldMark = glyph.mark |
|---|
| 526 |
oldNote = glyph.note |
|---|
| 527 |
self.removeGlyph(glyphName) |
|---|
| 528 |
selfGlyph = self.newGlyph(glyphName) |
|---|
| 529 |
selfGlyph.lib.update(oldLib) |
|---|
| 530 |
if oldMark != None: |
|---|
| 531 |
selfGlyph.mark = oldMark |
|---|
| 532 |
selfGlyph.note = oldNote |
|---|
| 533 |
min = minFont[glyphName] |
|---|
| 534 |
max = maxFont[glyphName] |
|---|
| 535 |
ok, glyphErrors = selfGlyph.interpolate(factor, min, max, suppressError=suppressError, analyzeOnly=analyzeOnly) |
|---|
| 536 |
if not errors.has_key('Glyph Errors'): |
|---|
| 537 |
errors['Glyph Errors'] = {} |
|---|
| 538 |
errors['Glyph Errors'][glyphName] = glyphErrors |
|---|
| 539 |
if doProgress: |
|---|
| 540 |
progress.tick(tickCount) |
|---|
| 541 |
tickCount = tickCount + 1 |
|---|
| 542 |
if doProgress: |
|---|
| 543 |
progress.close() |
|---|
| 544 |
return errors |
|---|
| 545 |
|
|---|
| 546 |
def getGlyphNameToFileNameFunc(self): |
|---|
| 547 |
funcName = self.lib.get("org.robofab.glyphNameToFileNameFuncName") |
|---|
| 548 |
if funcName is None: |
|---|
| 549 |
return None |
|---|
| 550 |
parts = funcName.split(".") |
|---|
| 551 |
module = ".".join(parts[:-1]) |
|---|
| 552 |
try: |
|---|
| 553 |
item = __import__(module) |
|---|
| 554 |
for sub in parts[1:]: |
|---|
| 555 |
item = getattr(item, sub) |
|---|
| 556 |
except (ImportError, AttributeError): |
|---|
| 557 |
from warnings import warn |
|---|
| 558 |
warn("Can't find glyph name to file name converter function, " |
|---|
| 559 |
"falling back to default scheme (%s)" % funcName, RoboFabWarning) |
|---|
| 560 |
return None |
|---|
| 561 |
else: |
|---|
| 562 |
return item |
|---|
| 563 |
|
|---|
| 564 |
|
|---|
| 565 |
class BaseGlyph(RBaseObject): |
|---|
| 566 |
|
|---|
| 567 |
"""Base class for all glyph objects.""" |
|---|
| 568 |
|
|---|
| 569 |
def __init__(self): |
|---|
| 570 |
RBaseObject.__init__(self) |
|---|
| 571 |
|
|---|
| 572 |
|
|---|
| 573 |
|
|---|
| 574 |
|
|---|
| 575 |
|
|---|
| 576 |
|
|---|
| 577 |
|
|---|
| 578 |
self.changed = False |
|---|
| 579 |
|
|---|
| 580 |
def __repr__(self): |
|---|
| 581 |
font = "unnamed_font" |
|---|
| 582 |
glyph = "unnamed_glyph" |
|---|
| 583 |
fontParent = self.getParent() |
|---|
| 584 |
if fontParent is not None: |
|---|
| 585 |
try: |
|---|
| 586 |
font = fontParent.info.fullName |
|---|
| 587 |
except AttributeError: |
|---|
| 588 |
pass |
|---|
| 589 |
try: |
|---|
| 590 |
glyph = self.name |
|---|
| 591 |
except AttributeError: |
|---|
| 592 |
pass |
|---|
| 593 |
return "<RGlyph for %s.%s>" %(font, glyph) |
|---|
| 594 |
|
|---|
| 595 |
|
|---|
| 596 |
|
|---|
| 597 |
|
|---|
| 598 |
|
|---|
| 599 |
def _getMathData(self): |
|---|
| 600 |
from robofab.pens.mathPens import GetMathDataPointPen |
|---|
| 601 |
pen = GetMathDataPointPen() |
|---|
| 602 |
self.drawPoints(pen) |
|---|
| 603 |
data = pen.getData() |
|---|
| 604 |
return data |
|---|
| 605 |
|
|---|
| 606 |
def _setMathData(self, data, destination=None): |
|---|
| 607 |
from robofab.pens.mathPens import CurveSegmentFilterPointPen |
|---|
| 608 |
if destination is None: |
|---|
| 609 |
newGlyph = self._mathCopy() |
|---|
| 610 |
else: |
|---|
| 611 |
newGlyph = destination |
|---|
| 612 |
newGlyph.clear() |
|---|
| 613 |
|
|---|
| 614 |
|
|---|
| 615 |
pointPen = newGlyph.getPointPen() |
|---|
| 616 |
filterPen = CurveSegmentFilterPointPen(pointPen) |
|---|
| 617 |
for contour in data['contours']: |
|---|
| 618 |
filterPen.beginPath() |
|---|
| 619 |
for segmentType, pt, smooth, name in contour: |
|---|
| 620 |
filterPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name) |
|---|
| 621 |
filterPen.endPath() |
|---|
| 622 |
for baseName, transformation in data['components']: |
|---|
| 623 |
filterPen.addComponent(baseName, transformation) |
|---|
| 624 |
for pt, name in data['anchors']: |
|---|
| 625 |
filterPen.beginPath() |
|---|
| 626 |
filterPen.addPoint(pt=pt, segmentType="move", smooth=False, name=name) |
|---|
| 627 |
filterPen.endPath() |
|---|
| 628 |
newGlyph.width = data['width'] |
|---|
| 629 |
|
|---|
| 630 |
return newGlyph |
|---|
| 631 |
|
|---|
| 632 |
def _getMathDestination(self): |
|---|
| 633 |
|
|---|
| 634 |
return self.__class__() |
|---|
| 635 |
|
|---|
| 636 |
def _mathCopy(self): |
|---|
| 637 |
|
|---|
| 638 |
glyph = self._getMathDestination() |
|---|
| 639 |
glyph.name = self.name |
|---|
| 640 |
glyph.unicodes = list(self.unicodes) |
|---|
| 641 |
glyph.width = self.width |
|---|
| 642 |
glyph.note = self.note |
|---|
| 643 |
glyph.lib = dict(self.lib) |
|---|
| 644 |
return glyph |
|---|
| 645 |
|
|---|
| 646 |
def _processMathOne(self, otherGlyph, funct): |
|---|
| 647 |
|
|---|
| 648 |
|
|---|
| 649 |
newData = { |
|---|
| 650 |
'contours':[], |
|---|
| 651 |
'components':[], |
|---|
| 652 |
'anchors':[], |
|---|
| 653 |
'width':None |
|---|
| 654 |
} |
|---|
| 655 |
selfData = self._getMathData() |
|---|
| 656 |
otherData = otherGlyph._getMathData() |
|---|
| 657 |
|
|---|
| 658 |
|
|---|
| 659 |
selfContours = selfData['contours'] |
|---|
| 660 |
otherContours = otherData['contours'] |
|---|
| 661 |
newContours = newData['contours'] |
|---|
| 662 |
if len(selfContours) > 0: |
|---|
| 663 |
for contourIndex in xrange(len(selfContours)): |
|---|
| 664 |
newContours.append([]) |
|---|
| 665 |
selfContour = selfContours[contourIndex] |
|---|
| 666 |
otherContour = otherContours[contourIndex] |
|---|
| 667 |
for pointIndex in xrange(len(selfContour)): |
|---|
| 668 |
segType, pt, smooth, name = selfContour[pointIndex] |
|---|
| 669 |
newX, newY = funct(selfContour[pointIndex][1], otherContour[pointIndex][1]) |
|---|
| 670 |
newContours[-1].append((segType, (newX, newY), smooth, name)) |
|---|
| 671 |
|
|---|
| 672 |
selfAnchors = selfData['anchors'] |
|---|
| 673 |
otherAnchors = otherData['anchors'] |
|---|
| 674 |
newAnchors = newData['anchors'] |
|---|
| 675 |
if len(selfAnchors) > 0: |
|---|
| 676 |
selfAnchors, otherAnchors = self._mathAnchorCompare(selfAnchors, otherAnchors) |
|---|
| 677 |
anchorNames = selfAnchors.keys() |
|---|
| 678 |
for anchorName in anchorNames: |
|---|
| 679 |
selfAnchorList = selfAnchors[anchorName] |
|---|
| 680 |
otherAnchorList = otherAnchors[anchorName] |
|---|
| 681 |
for i in range(len(selfAnchorList)): |
|---|
| 682 |
selfAnchor = selfAnchorList[i] |
|---|
| 683 |
otherAnchor = otherAnchorList[i] |
|---|
| 684 |
newAnchor = funct(selfAnchor, otherAnchor) |
|---|
| 685 |
newAnchors.append((newAnchor, anchorName)) |
|---|
| 686 |
|
|---|
| 687 |
selfComponents = selfData['components'] |
|---|
| 688 |
otherComponents = otherData['components'] |
|---|
| 689 |
newComponents = newData['components'] |
|---|
| 690 |
if len(selfComponents) > 0: |
|---|
| 691 |
selfComponents, otherComponents = self._mathComponentCompare(selfComponents, otherComponents) |
|---|
| 692 |
componentNames = selfComponents.keys() |
|---|
| 693 |
for componentName in componentNames: |
|---|
| 694 |
selfComponentList = selfComponents[componentName] |
|---|
| 695 |
otherComponentList = otherComponents[componentName] |
|---|
| 696 |
for i in range(len(selfComponentList)): |
|---|
| 697 |
|
|---|
| 698 |
selfXScale, selfXYScale, selfYXScale, selfYScale, selfXOffset, selfYOffset = selfComponentList[i] |
|---|
| 699 |
otherXScale, otherXYScale, otherYXScale, otherYScale, otherXOffset, otherYOffset = otherComponentList[i] |
|---|
| 700 |
newXScale, newXYScale = funct((selfXScale, selfXYScale), (otherXScale, otherXYScale)) |
|---|
| 701 |
newYXScale, newYScale = funct((selfYXScale, selfYScale), (otherYXScale, otherYScale)) |
|---|
| 702 |
newXOffset, newYOffset = funct((selfXOffset, selfYOffset), (otherXOffset, otherYOffset)) |
|---|
| 703 |
newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) |
|---|
| 704 |
return newData |
|---|
| 705 |
|
|---|
| 706 |
def _processMathTwo(self, factor, funct): |
|---|
| 707 |
|
|---|
| 708 |
|
|---|
| 709 |
newData = { |
|---|
| 710 |
'contours':[], |
|---|
| 711 |
'components':[], |
|---|
| 712 |
'anchors':[], |
|---|
| 713 |
'width':None |
|---|
| 714 |
} |
|---|
| 715 |
selfData = self._getMathData() |
|---|
| 716 |
|
|---|
| 717 |
selfContours = selfData['contours'] |
|---|
| 718 |
newContours = newData['contours'] |
|---|
| 719 |
for selfContour in selfContours: |
|---|
| 720 |
newContours.append([]) |
|---|
| 721 |
for segType, pt, smooth, name in selfContour: |
|---|
| 722 |
newX, newY = funct(pt, factor) |
|---|
| 723 |
newContours[-1].append((segType, (newX, newY), smooth, name)) |
|---|
| 724 |
|
|---|
| 725 |
selfAnchors = selfData['anchors'] |
|---|
| 726 |
newAnchors = newData['anchors'] |
|---|
| 727 |
for pt, anchorName in selfAnchors: |
|---|
| 728 |
newPt = funct(pt, factor) |
|---|
| 729 |
newAnchors.append((newPt, anchorName)) |
|---|
| 730 |
|
|---|
| 731 |
selfComponents = selfData['components'] |
|---|
| 732 |
newComponents = newData['components'] |
|---|
| 733 |
for baseName, transformation in selfComponents: |
|---|
| 734 |
xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation |
|---|
| 735 |
newXOffset, newYOffset = funct((xOffset, yOffset), factor) |
|---|
| 736 |
newXScale, newYScale = funct((xScale, yScale), factor) |
|---|
| 737 |
newXYScale, newYXScale = funct((xyScale, yxScale), factor) |
|---|
| 738 |
newComponents.append((baseName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) |
|---|
| 739 |
|
|---|
| 740 |
return newData |
|---|
| 741 |
|
|---|
| 742 |
def _mathAnchorCompare(self, selfMathAnchors, otherMathAnchors): |
|---|
| 743 |
|
|---|
| 744 |
from sets import Set |
|---|
| 745 |
selfAnchors = {} |
|---|
| 746 |
for pt, name in selfMathAnchors: |
|---|
| 747 |
if not selfAnchors.has_key(name): |
|---|
| 748 |
selfAnchors[name] = [] |
|---|
| 749 |
selfAnchors[name].append(pt) |
|---|
| 750 |
otherAnchors = {} |
|---|
| 751 |
for pt, name in otherMathAnchors: |
|---|
| 752 |
if not otherAnchors.has_key(name): |
|---|
| 753 |
otherAnchors[name] = [] |
|---|
| 754 |
otherAnchors[name].append(pt) |
|---|
| 755 |
compatAnchors = Set(selfAnchors.keys()) & Set(otherAnchors.keys()) |
|---|
| 756 |
finalSelfAnchors = {} |
|---|
| 757 |
finalOtherAnchors = {} |
|---|
| 758 |
for name in compatAnchors: |
|---|
| 759 |
if not finalSelfAnchors.has_key(name): |
|---|
| 760 |
finalSelfAnchors[name] = [] |
|---|
| 761 |
if not finalOtherAnchors.has_key(name): |
|---|
| 762 |
finalOtherAnchors[name] = [] |
|---|
| 763 |
selfList = selfAnchors[name] |
|---|
| 764 |
otherList = otherAnchors[name] |
|---|
| 765 |
selfCount = len(selfList) |
|---|
| 766 |
otherCount = len(otherList) |
|---|
| 767 |
if selfCount != otherCount: |
|---|
| 768 |
r = range(min(selfCount, otherCount)) |
|---|
| 769 |
else: |
|---|
| 770 |
r = range(selfCount) |
|---|
| 771 |
for i in r: |
|---|
| 772 |
finalSelfAnchors[name].append(selfList[i]) |
|---|
| 773 |
finalOtherAnchors[name].append(otherList[i]) |
|---|
| 774 |
return finalSelfAnchors, finalOtherAnchors |
|---|
| 775 |
|
|---|
| 776 |
def _mathComponentCompare(self, selfMathComponents, otherMathComponents): |
|---|
| 777 |
|
|---|
| 778 |
from sets import Set |
|---|
| 779 |
selfComponents = {} |
|---|
| 780 |
for baseName, transformation in selfMathComponents: |
|---|
| 781 |
if not selfComponents.has_key(baseName): |
|---|
| 782 |
selfComponents[baseName] = [] |
|---|
| 783 |
selfComponents[baseName].append(transformation) |
|---|
| 784 |
otherComponents = {} |
|---|
| 785 |
for baseName, transformation in otherMathComponents: |
|---|
| 786 |
if not otherComponents.has_key(baseName): |
|---|
| 787 |
otherComponents[baseName] = [] |
|---|
| 788 |
otherComponents[baseName].append(transformation) |
|---|
| 789 |
compatComponents = Set(selfComponents.keys()) & Set(otherComponents.keys()) |
|---|
| 790 |
finalSelfComponents = {} |
|---|
| 791 |
finalOtherComponents = {} |
|---|
| 792 |
for baseName in compatComponents: |
|---|
| 793 |
if not finalSelfComponents.has_key(baseName): |
|---|
| 794 |
finalSelfComponents[baseName] = [] |
|---|
| 795 |
if not finalOtherComponents.has_key(baseName): |
|---|
| 796 |
finalOtherComponents[baseName] = [] |
|---|
| 797 |
selfList = selfComponents[baseName] |
|---|
| 798 |
otherList = otherComponents[baseName] |
|---|
| 799 |
selfCount = len(selfList) |
|---|
| 800 |
otherCount = len(otherList) |
|---|
| 801 |
if selfCount != otherCount: |
|---|
| 802 |
r = range(min(selfCount, otherCount)) |
|---|
| 803 |
else: |
|---|
| 804 |
r = range(selfCount) |
|---|
| 805 |
for i in r: |
|---|
| 806 |
finalSelfComponents[baseName].append(selfList[i]) |
|---|
| 807 |
finalOtherComponents[baseName].append(otherList[i]) |
|---|
| 808 |
return finalSelfComponents, finalOtherComponents |
|---|
| 809 |
|
|---|
| 810 |
def __mul__(self, factor): |
|---|
| 811 |
assert isinstance(factor, (int, float, tuple)), "Glyphs can only be multiplied by int, float or a 2-tuple." |
|---|
| 812 |
if not isinstance(factor, tuple): |
|---|
| 813 |
factor = (factor, factor) |
|---|
| 814 |
data = self._processMathTwo(factor, mulPt) |
|---|
| 815 |
data['width'] = self.width * factor[0] |
|---|
| 816 |
return self._setMathData(data) |
|---|
| 817 |
|
|---|
| 818 |
__rmul__ = __mul__ |
|---|
| 819 |
|
|---|
| 820 |
def __div__(self, factor): |
|---|
| 821 |
assert isinstance(factor, (int, float, tuple)), "Glyphs can only be divided by int, float or a 2-tuple." |
|---|
| 822 |
|
|---|
| 823 |
if isinstance(factor, tuple): |
|---|
| 824 |
reverse = 1.0/factor[0], 1.0/factor[1] |
|---|
| 825 |
else: |
|---|
| 826 |
reverse = 1.0/factor |
|---|
| 827 |
return self.__mul__(reverse) |
|---|
| 828 |
|
|---|
| 829 |
def __add__(self, other): |
|---|
| 830 |
assert isinstance(other, BaseGlyph), "Glyphs can only be added to other glyphs." |
|---|
| 831 |
data = self._processMathOne(other, addPt) |
|---|
| 832 |
data['width'] = self.width + other.width |
|---|
| 833 |
return self._setMathData(data) |
|---|
| 834 |
|
|---|
| 835 |
def __sub__(self, other): |
|---|
| 836 |
assert isinstance(other, BaseGlyph), "Glyphs can only be substracted from other glyphs." |
|---|
| 837 |
data = self._processMathOne(other, subPt) |
|---|
| 838 |
data['width'] = self.width + other.width |
|---|
| 839 |
return self._setMathData(data) |
|---|
| 840 |
|
|---|
| 841 |
|
|---|
| 842 |
|
|---|
| 843 |
|
|---|
| 844 |
|
|---|
| 845 |
def interpolate(self, factor, minGlyph, maxGlyph, suppressError=True, analyzeOnly=False): |
|---|
| 846 |
"""Traditional interpolation method. Interpolates by factor between minGlyph and maxGlyph. |
|---|
| 847 |
suppressError will supress all tracebacks and analyze only will not perform the interpolation |
|---|
| 848 |
but it will analyze all glyphs and return a dict of problems.""" |
|---|
| 849 |
if not isinstance(factor, tuple): |
|---|
| 850 |
factor = factor, factor |
|---|
| 851 |
fatalError = False |
|---|
| 852 |
if analyzeOnly: |
|---|
| 853 |
ok, errors = minGlyph.isCompatible(maxGlyph) |
|---|
| 854 |
return ok, errors |
|---|
| 855 |
minData = None |
|---|
| 856 |
maxData = None |
|---|
| 857 |
minName = minGlyph.name |
|---|
| 858 |
maxName = maxGlyph.name |
|---|
| 859 |
try: |
|---|
| 860 |
minData = minGlyph._getMathData() |
|---|
| 861 |
maxData = maxGlyph._getMathData() |
|---|
| 862 |
newContours = self._interpolateContours(factor, minData['contours'], maxData['contours']) |
|---|
| 863 |
newComponents = self._interpolateComponents(factor, minData['components'], maxData['components']) |
|---|
| 864 |
newAnchors = self._interpolateAnchors(factor, minData['anchors'], maxData['anchors']) |
|---|
| 865 |
newWidth = _interpolate(minGlyph.width, maxGlyph.width, factor[0]) |
|---|
| 866 |
newData = { |
|---|
| 867 |
'contours':newContours, |
|---|
| 868 |
'components':newComponents, |
|---|
| 869 |
'anchors':newAnchors, |
|---|
| 870 |
'width':newWidth |
|---|
| 871 |
} |
|---|
| 872 |
self._setMathData(newData, self) |
|---|
| 873 |
except IndexError: |
|---|
| 874 |
if not suppressError: |
|---|
| 875 |
ok, errors = minGlyph.isCompatible(maxGlyph) |
|---|
| 876 |
ok = not ok |
|---|
| 877 |
return ok, errors |
|---|
| 878 |
self.update() |
|---|
| 879 |
return False, [] |
|---|
| 880 |
|
|---|
| 881 |
def isCompatible(self, otherGlyph, report=True): |
|---|
| 882 |
"""Return a bool value if the glyph is compatible with otherGlyph. |
|---|
| 883 |
With report = True, isCompatible will return a report of what's wrong. |
|---|
| 884 |
The interpolate method requires absolute equality between contour data. |
|---|
| 885 |
Absolute equality is preferred among component and anchor data, but |
|---|
| 886 |
it is NOT required. Interpolation between components and anchors |
|---|
| 887 |
will only deal with compatible data and incompatible data will be |
|---|
| 888 |
ignored. This method reflects this system.""" |
|---|
| 889 |
selfName = self.name |
|---|
| 890 |
selfData = self._getMathData() |
|---|
| 891 |
otherName = otherGlyph.name |
|---|
| 892 |
otherData = otherGlyph._getMathData() |
|---|
| 893 |
compatible, errors = self._isCompatibleInternal(selfName, otherName, selfData, otherData) |
|---|
| 894 |
if report: |
|---|
| 895 |
return compatible, errors |
|---|
| 896 |
return compatible |
|---|
| 897 |
|
|---|
| 898 |
def _isCompatibleInternal(self, selfName, otherName, selfData, otherData): |
|---|
| 899 |
fatalError = False |
|---|
| 900 |
errors = [] |
|---|
| 901 |
|
|---|
| 902 |
|
|---|
| 903 |
|
|---|
| 904 |
selfContours = selfData['contours'] |
|---|
| 905 |
otherContours = otherData['contours'] |
|---|
| 906 |
if len(selfContours) != len(otherContours): |
|---|
| 907 |
fatalError = True |
|---|
| 908 |
errors.append("Fatal error: glyph %s and glyph %s don't have the same number of contours." %(selfName, otherName)) |
|---|
| 909 |
else: |
|---|
| 910 |
for contourIndex in xrange(len(selfContours)): |
|---|
| 911 |
selfContour = selfContours[contourIndex] |
|---|
| 912 |
otherContour = otherContours[contourIndex] |
|---|
| 913 |
if len(selfContour) != len(otherContour): |
|---|
| 914 |
fatalError = True |
|---|
| 915 |
errors.append("Fatal error: contour %d in glyph %s and glyph %s don't have the same number of segments." %(contourIndex, selfName, otherName)) |
|---|
| 916 |
|
|---|
| 917 |
|
|---|
| 918 |
|
|---|
| 919 |
selfComponents = selfData['components'] |
|---|
| 920 |
otherComponents = otherData['components'] |
|---|
| 921 |
if len(selfComponents) != len(otherComponents): |
|---|
| 922 |
errors.append("Error: glyph %s and glyph %s don't have the same number of components." %(selfName, otherName)) |
|---|
| 923 |
for componentIndex in xrange(min(len(selfComponents), len(otherComponents))): |
|---|
| 924 |
selfBaseName, selfTransformation = selfComponents[componentIndex] |
|---|
| 925 |
otherBaseName, otherTransformation = otherComponents[componentIndex] |
|---|
| 926 |
if selfBaseName != otherBaseName: |
|---|
| 927 |
errors.append("Error: component %d in glyph %s and glyph %s don't have the same base glyph." %(componentIndex, selfName, otherName)) |
|---|
| 928 |
|
|---|
| 929 |
|
|---|
| 930 |
|
|---|
| 931 |
selfAnchors = selfData['anchors'] |
|---|
| 932 |
otherAnchors = otherData['anchors'] |
|---|
| 933 |
if len(selfAnchors) != len(otherAnchors): |
|---|
| 934 |
errors.append("Error: glyph %s and glyph %s don't have the same number of anchors." %(selfName, otherName)) |
|---|
| 935 |
for anchorIndex in xrange(min(len(selfAnchors), len(otherAnchors))): |
|---|
| 936 |
selfPt, selfAnchorName = selfAnchors[anchorIndex] |
|---|
| 937 |
otherPt, otherAnchorName = otherAnchors[anchorIndex] |
|---|
| 938 |
if selfAnchorName != otherAnchorName: |
|---|
| 939 |
errors.append("Error: anchor %d in glyph %s and glyph %s don't have the same name." %(anchorIndex, selfName, otherName)) |
|---|
| 940 |
return not fatalError, errors |
|---|
| 941 |
|
|---|
| 942 |
def _interpolateContours(self, factor, minContours, maxContours): |
|---|
| 943 |
newContours = [] |
|---|
| 944 |
for contourIndex in xrange(len(minContours)): |
|---|
| 945 |
minContour = minContours[contourIndex] |
|---|
| 946 |
maxContour = maxContours[contourIndex] |
|---|
| 947 |
newContours.append([]) |
|---|
| 948 |
for pointIndex in xrange(len(minContour)): |
|---|
| 949 |
segType, pt, smooth, name = minContour[pointIndex] |
|---|
| 950 |
minPoint = minContour[pointIndex][1] |
|---|
| 951 |
maxPoint = maxContour[pointIndex][1] |
|---|
| 952 |
newX, newY = _interpolatePt(minPoint, maxPoint, factor) |
|---|
| 953 |
newContours[-1].append((segType, (newX, newY), smooth, name)) |
|---|
| 954 |
return newContours |
|---|
| 955 |
|
|---|
| 956 |
def _interpolateComponents(self, factor, minComponents, maxComponents): |
|---|
| 957 |
newComponents = [] |
|---|
| 958 |
minComponents, maxComponents = self._mathComponentCompare(minComponents, maxComponents) |
|---|
| 959 |
componentNames = minComponents.keys() |
|---|
| 960 |
for componentName in componentNames: |
|---|
| 961 |
minComponentList = minComponents[componentName] |
|---|
| 962 |
maxComponentList = maxComponents[componentName] |
|---|
| 963 |
for i in xrange(len(minComponentList)): |
|---|
| 964 |
|
|---|
| 965 |
minXScale, minXYScale, minYXScale, minYScale, minXOffset, minYOffset = minComponentList[i] |
|---|
| 966 |
maxXScale, maxXYScale, maxYXScale, maxYScale, maxXOffset, maxYOffset = maxComponentList[i] |
|---|
| 967 |
newXScale, newXYScale = _interpolatePt((minXScale, minXYScale), (maxXScale, maxXYScale), factor) |
|---|
| 968 |
newYXScale, newYScale = _interpolatePt((minYXScale, minYScale), (maxYXScale, maxYScale), factor) |
|---|
| 969 |
newXOffset, newYOffset = _interpolatePt((minXOffset, minYOffset), (maxXOffset, maxYOffset), factor) |
|---|
| 970 |
newComponents.append((componentName, (newXScale, newXYScale, newYXScale, newYScale, newXOffset, newYOffset))) |
|---|
| 971 |
return newComponents |
|---|
| 972 |
|
|---|
| 973 |
def _interpolateAnchors(self, factor, minAnchors, maxAnchors): |
|---|
| 974 |
newAnchors = [] |
|---|
| 975 |
minAnchors, maxAnchors = self._mathAnchorCompare(minAnchors, maxAnchors) |
|---|
| 976 |
anchorNames = minAnchors.keys() |
|---|
| 977 |
for anchorName in anchorNames: |
|---|
| 978 |
minAnchorList = minAnchors[anchorName] |
|---|
| 979 |
maxAnchorList = maxAnchors[anchorName] |
|---|
| 980 |
for i in range(len(minAnchorList)): |
|---|
| 981 |
minAnchor = minAnchorList[i] |
|---|
| 982 |
maxAnchor = maxAnchorList[i] |
|---|
| 983 |
newAnchor = _interpolatePt(minAnchor, maxAnchor, factor) |
|---|
| 984 |
newAnchors.append((newAnchor, anchorName)) |
|---|
| 985 |
return newAnchors |
|---|
| 986 |
|
|---|
| 987 |
|
|---|
| 988 |
|
|---|
| 989 |
|
|---|
| 990 |
|
|---|
| 991 |
def __eq__(self, other): |
|---|
| 992 |
if isinstance(other, BaseGlyph): |
|---|
| 993 |
return self._getDigest() == other._getDigest() |
|---|
| 994 |
return False |
|---|
| 995 |
|
|---|
| 996 |
def _getDigest(self, pointsOnly=False): |
|---|
| 997 |
"""Calculate a digest of coordinates, points, things in this glyph. |
|---|
| 998 |
With pointsOnly == True the digest consists of a flat tuple of all |
|---|
| 999 |
coordinate pairs in the glyph, without the order of contours. |
|---|
| 1000 |
""" |
|---|
| 1001 |
from robofab.pens.digestPen import DigestPointPen |
|---|
| 1002 |
mp = DigestPointPen() |
|---|
| 1003 |
self.drawPoints(mp) |
|---|
| 1004 |
if pointsOnly: |
|---|
| 1005 |
return "%s|%d|%s"%(mp.getDigestPointsOnly(), self.width, self.unicode) |
|---|
| 1006 |
else: |
|---|
| 1007 |
return "%s|%d|%s"%(mp.getDigest(), self.width, self.unicode) |
|---|
| 1008 |
|
|---|
| 1009 |
def _getStructure(self): |
|---|
| 1010 |
"""Calculate a digest of points, things in this glyph, but NOT coordinates.""" |
|---|
| 1011 |
from robofab.pens.digestPen import DigestPointStructurePen |
|---|
| 1012 |
mp = DigestPointStructurePen() |
|---|
| 1013 |
self.drawPoints(mp) |
|---|
| 1014 |
return mp.getDigest() |
|---|
| 1015 |
|
|---|
| 1016 |
def _hasChanged(self): |
|---|
| 1017 |
"""mark the object and it's parent as changed""" |
|---|
| 1018 |
self.setChanged(True) |
|---|
| 1019 |
if self.getParent() is not None: |
|---|
| 1020 |
self.getParent()._hasChanged() |
|---|
| 1021 |
|
|---|
| 1022 |
def _get_box(self): |
|---|
| 1023 |
bounds = _box(self, fontObject=self.getParent()) |
|---|
| 1024 |
return bounds |
|---|
| 1025 |
|
|---|
| 1026 |
box = property(_get_box, doc="the bounding box of the glyph: (xMin, yMin, xMax, yMax)") |
|---|
| 1027 |
|
|---|
| 1028 |
def _get_leftMargin(self): |
|---|
| 1029 |
if self.isEmpty(): |
|---|
| 1030 |
return 0 |
|---|
| 1031 |
xMin, yMin, xMax, yMax = self.box |
|---|
| 1032 |
return xMin |
|---|
| 1033 |
|
|---|
| 1034 |
def _set_leftMargin(self, value): |
|---|
| 1035 |
if self.isEmpty(): |
|---|
| 1036 |
self.width = self.width + value |
|---|
| 1037 |
else: |
|---|
| 1038 |
diff = value - self.leftMargin |
|---|
| 1039 |
self.move((diff, 0)) |
|---|
| 1040 |
self.width = self.width + diff |
|---|
| 1041 |
|
|---|
| 1042 |
leftMargin = property(_get_leftMargin, _set_leftMargin, doc="the left margin") |
|---|
| 1043 |
|
|---|
| 1044 |
def _get_rightMargin(self): |
|---|
| 1045 |
if self.isEmpty(): |
|---|
| 1046 |
return self.width |
|---|
| 1047 |
xMin, yMin, xMax, yMax = self.box |
|---|
| 1048 |
return self.width - xMax |
|---|
| 1049 |
|
|---|
| 1050 |
def _set_rightMargin(self, value): |
|---|
| 1051 |
if self.isEmpty(): |
|---|
| 1052 |
self.width = value |
|---|
| 1053 |
else: |
|---|
| 1054 |
xMin, yMin, xMax, yMax = self.box |
|---|
| 1055 |
self.width = xMax + value |
|---|
| 1056 |
|
|---|
| 1057 |
rightMargin = property(_get_rightMargin, _set_rightMargin, doc="the right margin") |
|---|
| 1058 |
|
|---|
| 1059 |
def copy(self, aParent=None): |
|---|
| 1060 |
"""Duplicate this glyph""" |
|---|
| 1061 |
n = self.__class__() |
|---|
| 1062 |
if aParent is not None: |
|---|
| 1063 |
n.setParent(aParent) |
|---|
| 1064 |
dont = ['_object', 'getParent'] |
|---|
| 1065 |
for k in self.__dict__.keys(): |
|---|
| 1066 |
ok = True |
|---|
| 1067 |
if k in dont: |
|---|
| 1068 |
continue |
|---|
| 1069 |
elif k == "contours": |
|---|
| 1070 |
dup = [] |
|---|
| 1071 |
for i in self.contours: |
|---|
| 1072 |
dup.append(i.copy(n)) |
|---|
| 1073 |
elif k == "components": |
|---|
| 1074 |
dup = [] |
|---|
| 1075 |
for i in self.components: |
|---|
| 1076 |
dup.append(i.copy(n)) |
|---|
| 1077 |
elif k == "anchors": |
|---|
| 1078 |
dup = [] |
|---|
| 1079 |
for i in self.anchors: |
|---|
| 1080 |
dup.append(i.copy(n)) |
|---|
| 1081 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 1082 |
dup = self.__dict__[k].copy(n) |
|---|
| 1083 |
else: |
|---|
| 1084 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 1085 |
if ok: |
|---|
| 1086 |
setattr(n, k, dup) |
|---|
| 1087 |
return n |
|---|
| 1088 |
|
|---|
| 1089 |
def _setParentTree(self): |
|---|
| 1090 |
"""Set the parents of all contained and dependent objects (and their dependents) right.""" |
|---|
| 1091 |
for item in self.contours: |
|---|
| 1092 |
item.setParent(self) |
|---|
| 1093 |
item._setParentTree() |
|---|
| 1094 |
for item in self.components: |
|---|
| 1095 |
item.setParent(self) |
|---|
| 1096 |
for items in self.anchors: |
|---|
| 1097 |
item.setParent(self) |
|---|
| 1098 |
|
|---|
| 1099 |
def getGlyph(self, glyphName): |
|---|
| 1100 |
"""Provided there is a font parent for this glyph, return a sibling glyph.""" |
|---|
| 1101 |
if glyphName == self.name: |
|---|
| 1102 |
return self |
|---|
| 1103 |
if self.getParent() is not None: |
|---|
| 1104 |
return self.getParent()[glyphName] |
|---|
| 1105 |
return None |
|---|
| 1106 |
|
|---|
| 1107 |
def getPen(self): |
|---|
| 1108 |
"""Return a Pen object for creating an outline in this glyph.""" |
|---|
| 1109 |
from robofab.pens.adapterPens import SegmentToPointPen |
|---|
| 1110 |
return SegmentToPointPen(self.getPointPen()) |
|---|
| 1111 |
|
|---|
| 1112 |
def getPointPen(self): |
|---|
| 1113 |
"""Return a PointPen object for creating an outline in this glyph.""" |
|---|
| 1114 |
raise NotImplementedError, "getPointPen() must be implemented by subclass" |
|---|
| 1115 |
|
|---|
| 1116 |
def deSelect(self): |
|---|
| 1117 |
"""Set all selected attrs in glyph to False: for the glyph, components, anchors, points.""" |
|---|
| 1118 |
for a in self.anchors: |
|---|
| 1119 |
a.selected = False |
|---|
| 1120 |
for a in self.components: |
|---|
| 1121 |
a.selected = False |
|---|
| 1122 |
for c in self.contours: |
|---|
| 1123 |
for p in c.points: |
|---|
| 1124 |
p.selected = False |
|---|
| 1125 |
self.selected = False |
|---|
| 1126 |
|
|---|
| 1127 |
def isEmpty(self): |
|---|
| 1128 |
"""return true if the glyph has no contours or components""" |
|---|
| 1129 |
if len(self.contours) + len(self.components) == 0: |
|---|
| 1130 |
return True |
|---|
| 1131 |
else: |
|---|
| 1132 |
return False |
|---|
| 1133 |
|
|---|
| 1134 |
def _saveToGlyphSet(self, glyphSet, glyphName=None, force=False): |
|---|
| 1135 |
"""Save the glyph to GlyphSet, a private method that's part of the saving process.""" |
|---|
| 1136 |
|
|---|
| 1137 |
if force or self.changed: |
|---|
| 1138 |
if glyphName is None: |
|---|
| 1139 |
glyphName = self.name |
|---|
| 1140 |
glyphSet.writeGlyph(glyphName, self, self.drawPoints) |
|---|
| 1141 |
|
|---|
| 1142 |
def update(self): |
|---|
| 1143 |
"""update the glyph""" |
|---|
| 1144 |
pass |
|---|
| 1145 |
|
|---|
| 1146 |
def draw(self, pen): |
|---|
| 1147 |
"""draw the object with a RoboFab segment pen""" |
|---|
| 1148 |
try: |
|---|
| 1149 |
pen.setWidth(self.width) |
|---|
| 1150 |
if self.note is not None: |
|---|
| 1151 |
pen.setNote(self.note) |
|---|
| 1152 |
except AttributeError: |
|---|
| 1153 |
|
|---|
| 1154 |
pass |
|---|
| 1155 |
for a in self.anchors: |
|---|
| 1156 |
a.draw(pen) |
|---|
| 1157 |
for c in self.contours: |
|---|
| 1158 |
c.draw(pen) |
|---|
| 1159 |
for c in self.components: |
|---|
| 1160 |
c.draw(pen) |
|---|
| 1161 |
try: |
|---|
| 1162 |
pen.doneDrawing() |
|---|
| 1163 |
except AttributeError: |
|---|
| 1164 |
|
|---|
| 1165 |
pass |
|---|
| 1166 |
|
|---|
| 1167 |
def drawPoints(self, pen): |
|---|
| 1168 |
"""draw the object with a point pen""" |
|---|
| 1169 |
for a in self.anchors: |
|---|
| 1170 |
a.drawPoints(pen) |
|---|
| 1171 |
for c in self.contours: |
|---|
| 1172 |
c.drawPoints(pen) |
|---|
| 1173 |
for c in self.components: |
|---|
| 1174 |
c.drawPoints(pen) |
|---|
| 1175 |
|
|---|
| 1176 |
def appendContour(self, aContour, offset=(0, 0)): |
|---|
| 1177 |
"""append a contour to the glyph""" |
|---|
| 1178 |
x, y = offset |
|---|
| 1179 |
pen = self.getPointPen() |
|---|
| 1180 |
aContour.drawPoints(pen) |
|---|
| 1181 |
self.contours[-1].move((x, y)) |
|---|
| 1182 |
|
|---|
| 1183 |
def appendGlyph(self, aGlyph, offset=(0, 0)): |
|---|
| 1184 |
"""append another glyph to the glyph""" |
|---|
| 1185 |
x, y = offset |
|---|
| 1186 |
pen = self.getPointPen() |
|---|
| 1187 |
|
|---|
| 1188 |
aGlyph.move((x, y)) |
|---|
| 1189 |
aGlyph.drawPoints(pen) |
|---|
| 1190 |
aGlyph.move((-x, -y)) |
|---|
| 1191 |
|
|---|
| 1192 |
def round(self): |
|---|
| 1193 |
"""round all coordinates in all contours, components and anchors""" |
|---|
| 1194 |
for n in self.contours: |
|---|
| 1195 |
n.round() |
|---|
| 1196 |
for n in self.components: |
|---|
| 1197 |
n.round() |
|---|
| 1198 |
for n in self.anchors: |
|---|
| 1199 |
n.round() |
|---|
| 1200 |
self.width = int(round(self.width)) |
|---|
| 1201 |
|
|---|
| 1202 |
def autoUnicodes(self): |
|---|
| 1203 |
"""Using fontTools.agl, assign Unicode list to the glyph""" |
|---|
| 1204 |
from fontTools.agl import AGL2UV |
|---|
| 1205 |
if AGL2UV.has_key(self.name): |
|---|
| 1206 |
self.unicode = AGL2UV[self.name] |
|---|
| 1207 |
self._hasChanged() |
|---|
| 1208 |
|
|---|
| 1209 |
def pointInside(self, (x, y), evenOdd=0): |
|---|
| 1210 |
"""determine if the point is in the black or white of the glyph""" |
|---|
| 1211 |
from fontTools.pens.pointInsidePen import PointInsidePen |
|---|
| 1212 |
font = self.getParent() |
|---|
| 1213 |
piPen = PointInsidePen(glyphSet=font, testPoint=(x, y), evenOdd=evenOdd) |
|---|
| 1214 |
self.draw(piPen) |
|---|
| 1215 |
return piPen.getResult() |
|---|
| 1216 |
|
|---|
| 1217 |
def correctDirection(self, trueType=False): |
|---|
| 1218 |
"""corect the direction of the contours in the glyph.""" |
|---|
| 1219 |
|
|---|
| 1220 |
|
|---|
| 1221 |
|
|---|
| 1222 |
|
|---|
| 1223 |
|
|---|
| 1224 |
|
|---|
| 1225 |
|
|---|
| 1226 |
from fontTools.pens.pointInsidePen import PointInsidePen |
|---|
| 1227 |
baseDirection = 0 |
|---|
| 1228 |
if trueType: |
|---|
| 1229 |
baseDirection = 1 |
|---|
| 1230 |
|
|---|
| 1231 |
count = len(self.contours) |
|---|
| 1232 |
if count == 0: |
|---|
| 1233 |
return |
|---|
| 1234 |
elif count == 1: |
|---|
| 1235 |
self.contours[0].clockwise = baseDirection |
|---|
| 1236 |
return |
|---|
| 1237 |
|
|---|
| 1238 |
|
|---|
| 1239 |
contourDict = {} |
|---|
| 1240 |
for contourIndex in range(len(self.contours)): |
|---|
| 1241 |
contour = self.contours[contourIndex] |
|---|
| 1242 |
contourDict[contourIndex] = {'box':contour.box, 'dir':contour.clockwise, 'hit':[], 'notHit':[]} |
|---|
| 1243 |
|
|---|
| 1244 |
|
|---|
| 1245 |
|
|---|
| 1246 |
allIndexes = contourDict.keys() |
|---|
| 1247 |
for contourIndex in allIndexes: |
|---|
| 1248 |
for otherContourIndex in allIndexes: |
|---|
| 1249 |
if otherContourIndex != contourIndex: |
|---|
| 1250 |
if contourIndex not in contourDict[otherContourIndex]['hit'] and contourIndex not in contourDict[otherContourIndex]['notHit']: |
|---|
| 1251 |
xMin1, yMin1, xMax1, yMax1 = contourDict[contourIndex]['box'] |
|---|
| 1252 |
xMin2, yMin2, xMax2, yMax2= contourDict[otherContourIndex]['box'] |
|---|
| 1253 |
hit, pos = sectRect((xMin1, yMin1, xMax1, yMax1), (xMin2, yMin2, xMax2, yMax2)) |
|---|
| 1254 |
if hit == 1: |
|---|
| 1255 |
contourDict[contourIndex]['hit'].append(otherContourIndex) |
|---|
| 1256 |
contourDict[otherContourIndex]['hit'].append(contourIndex) |
|---|
| 1257 |
else: |
|---|
| 1258 |
contourDict[contourIndex]['notHit'].append(otherContourIndex) |
|---|
| 1259 |
contourDict[otherContourIndex]['notHit'].append(contourIndex) |
|---|
| 1260 |
|
|---|
| 1261 |
font = self.getParent() |
|---|
| 1262 |
piPen = PointInsidePen(glyphSet=font, testPoint=(0, 0), evenOdd=0) |
|---|
| 1263 |
|
|---|
| 1264 |
for contourIndex in allIndexes: |
|---|
| 1265 |
direction = baseDirection |
|---|
| 1266 |
contour = self.contours[contourIndex] |
|---|
| 1267 |
startPoint = contour.segments[0].onCurve |
|---|
| 1268 |
if startPoint is not None: |
|---|
| 1269 |
if len(contourDict[contourIndex]['hit']) != 0: |
|---|
| 1270 |
for otherContourIndex in contourDict[contourIndex]['hit']: |
|---|
| 1271 |
piPen.setTestPoint(testPoint=(startPoint.x, startPoint.y)) |
|---|
| 1272 |
otherContour = self.contours[otherContourIndex] |
|---|
| 1273 |
otherContour.draw(piPen) |
|---|
| 1274 |
direction = direction + piPen.getResult() |
|---|
| 1275 |
newDirection = direction % 2 |
|---|
| 1276 |
|
|---|
| 1277 |
if newDirection != contourDict[contourIndex]['dir']: |
|---|
| 1278 |
contour.reverseContour() |
|---|
| 1279 |
|
|---|
| 1280 |
def autoContourOrder(self): |
|---|
| 1281 |
"""attempt to sort the contours based on their centers""" |
|---|
| 1282 |
|
|---|
| 1283 |
|
|---|
| 1284 |
|
|---|
| 1285 |
|
|---|
| 1286 |
|
|---|
| 1287 |
|
|---|
| 1288 |
|
|---|
| 1289 |
|
|---|
| 1290 |
|
|---|
| 1291 |
|
|---|
| 1292 |
tempContourList = [] |
|---|
| 1293 |
contourList = [] |
|---|
| 1294 |
xThreshold = None |
|---|
| 1295 |
yThreshold = None |
|---|
| 1296 |
for contour in self.contours: |
|---|
| 1297 |
xMin, yMin, xMax, yMax = contour.box |
|---|
| 1298 |
width = xMax - xMin |
|---|
| 1299 |
height = yMax - yMin |
|---|
| 1300 |
xC = 0.5 * (xMin + xMax) |
|---|
| 1301 |
yC = 0.5 * (yMin + yMax) |
|---|
| 1302 |
xTh = abs(width * .5) |
|---|
| 1303 |
yTh = abs(height * .5) |
|---|
| 1304 |
if xThreshold is None or xThreshold > xTh: |
|---|
| 1305 |
xThreshold = xTh |
|---|
| 1306 |
if yThreshold is None or yThreshold > yTh: |
|---|
| 1307 |
yThreshold = yTh |
|---|
| 1308 |
tempContourList.append((-len(contour.points), -len(contour.segments), xC, yC, -(width * height), contour)) |
|---|
| 1309 |
for points, segments, x, y, surface, contour in tempContourList: |
|---|
| 1310 |
contourList.append((points, segments, FuzzyNumber(x, xThreshold), FuzzyNumber(y, yThreshold), surface, contour)) |
|---|
| 1311 |
contourList.sort() |
|---|
| 1312 |
for i in range(len(contourList)): |
|---|
| 1313 |
points, segments, xO, yO, surface, contour = contourList[i] |
|---|
| 1314 |
contour.index = i |
|---|
| 1315 |
|
|---|
| 1316 |
def rasterize(self, cellSize=50, xMin=None, yMin=None, xMax=None, yMax=None): |
|---|
| 1317 |
""" |
|---|
| 1318 |
Slice the glyph into a grid based on the cell size. |
|---|
| 1319 |
It returns a list of lists containing bool values |
|---|
| 1320 |
that indicate the black (True) or white (False) |
|---|
| 1321 |
value of that particular cell. These lists are |
|---|
| 1322 |
arranged from top to bottom of the glyph and |
|---|
| 1323 |
proceed from left to right. |
|---|
| 1324 |
This is an expensive operation! |
|---|
| 1325 |
""" |
|---|
| 1326 |
from fontTools.pens.pointInsidePen import PointInsidePen |
|---|
| 1327 |
piPen = PointInsidePen(glyphSet=self.getParent(), testPoint=(0, 0), evenOdd=0) |
|---|
| 1328 |
if xMin is None or yMin is None or xMax is None or yMax is None: |
|---|
| 1329 |
_xMin, _yMin, _xMax, _yMax = self.box |
|---|
| 1330 |
if xMin is None: |
|---|
| 1331 |
xMin = _xMin |
|---|
| 1332 |
if yMin is None: |
|---|
| 1333 |
yMin = _yMin |
|---|
| 1334 |
if xMax is None: |
|---|
| 1335 |
xMax = _xMax |
|---|
| 1336 |
if yMax is None: |
|---|
| 1337 |
yMax = _yMax |
|---|
| 1338 |
|
|---|
| 1339 |
hitXMax = False |
|---|
| 1340 |
hitYMin = False |
|---|
| 1341 |
xSlice = 0 |
|---|
| 1342 |
ySlice = 0 |
|---|
| 1343 |
halfCellSize = cellSize / 2.0 |
|---|
| 1344 |
|
|---|
| 1345 |
map = [] |
|---|
| 1346 |
|
|---|
| 1347 |
while not hitYMin: |
|---|
| 1348 |
map.append([]) |
|---|
| 1349 |
yScan = -(ySlice * cellSize) + yMax - halfCellSize |
|---|
| 1350 |
if yScan < yMin: |
|---|
| 1351 |
hitYMin = True |
|---|
| 1352 |
while not hitXMax: |
|---|
| 1353 |
xScan = (xSlice * cellSize) + xMin - halfCellSize |
|---|
| 1354 |
if xScan > xMax: |
|---|
| 1355 |
hitXMax = True |
|---|
| 1356 |
piPen.setTestPoint((xScan, yScan)) |
|---|
| 1357 |
self.draw(piPen) |
|---|
| 1358 |
test = piPen.getResult() |
|---|
| 1359 |
if test: |
|---|
| 1360 |
map[-1].append(True) |
|---|
| 1361 |
else: |
|---|
| 1362 |
map[-1].append(False) |
|---|
| 1363 |
xSlice = xSlice + 1 |
|---|
| 1364 |
hitXMax = False |
|---|
| 1365 |
xSlice = 0 |
|---|
| 1366 |
ySlice = ySlice + 1 |
|---|
| 1367 |
return map |
|---|
| 1368 |
|
|---|
| 1369 |
def move(self, (x, y), contours=True, components=True, anchors=True): |
|---|
| 1370 |
"""Move a glyph's items that are flagged as True""" |
|---|
| 1371 |
x, y = roundPt((x, y)) |
|---|
| 1372 |
for contour in self.contours: |
|---|
| 1373 |
contour.move((x, y)) |
|---|
| 1374 |
for component in self.components: |
|---|
| 1375 |
component.move((x, y)) |
|---|
| 1376 |
for anchor in self.anchors: |
|---|
| 1377 |
anchor.move((x, y)) |
|---|
| 1378 |
|
|---|
| 1379 |
def scale(self, (x, y), center=(0, 0)): |
|---|
| 1380 |
"""scale the glyph""" |
|---|
| 1381 |
for contour in self.contours: |
|---|
| 1382 |
contour.scale((x, y), center=center) |
|---|
| 1383 |
for component in self.components: |
|---|
| 1384 |
offset = component.offset |
|---|
| 1385 |
component.offset = _scalePointFromCenter(offset, (x, y), center) |
|---|
| 1386 |
sX, sY = component.scale |
|---|
| 1387 |
component.scale = (sX*x, sY*y) |
|---|
| 1388 |
for anchor in self.anchors: |
|---|
| 1389 |
anchor.scale((x, y), center=center) |
|---|
| 1390 |
|
|---|
| 1391 |
def transform(self, matrix): |
|---|
| 1392 |
"""Transform this glyph. |
|---|
| 1393 |
Use a Transform matrix object from |
|---|
| 1394 |
robofab.transform""" |
|---|
| 1395 |
n = [] |
|---|
| 1396 |
for c in self.contours: |
|---|
| 1397 |
c.transform(matrix) |
|---|
| 1398 |
for a in self.anchors: |
|---|
| 1399 |
a.transform(matrix) |
|---|
| 1400 |
|
|---|
| 1401 |
def rotate(self, angle, offset=None): |
|---|
| 1402 |
"""rotate the glyph""" |
|---|
| 1403 |
from fontTools.misc.transform import Identity |
|---|
| 1404 |
radAngle = angle / DEGREE |
|---|
| 1405 |
if offset is None: |
|---|
| 1406 |
offset = (0,0) |
|---|
| 1407 |
rT = Identity.translate(offset[0], offset[1]) |
|---|
| 1408 |
rT = rT.rotate(radAngle) |
|---|
| 1409 |
rT = rT.translate(-offset[0], -offset[1]) |
|---|
| 1410 |
self.transform(rT) |
|---|
| 1411 |
|
|---|
| 1412 |
def skew(self, angle, offset=None): |
|---|
| 1413 |
"""skew the glyph""" |
|---|
| 1414 |
from fontTools.misc.transform import Identity |
|---|
| 1415 |
radAngle = angle / DEGREE |
|---|
| 1416 |
if offset is None: |
|---|
| 1417 |
offset = (0,0) |
|---|
| 1418 |
rT = Identity.translate(offset[0], offset[1]) |
|---|
| 1419 |
rT = rT.skew(radAngle) |
|---|
| 1420 |
self.transform(rT) |
|---|
| 1421 |
|
|---|
| 1422 |
|
|---|
| 1423 |
class BaseContour(RBaseObject): |
|---|
| 1424 |
|
|---|
| 1425 |
"""Base class for all contour objects.""" |
|---|
| 1426 |
|
|---|
| 1427 |
def __init__(self): |
|---|
| 1428 |
RBaseObject.__init__(self) |
|---|
| 1429 |
|
|---|
| 1430 |
self.changed = False |
|---|
| 1431 |
|
|---|
| 1432 |
def __repr__(self): |
|---|
| 1433 |
font = "unnamed_font" |
|---|
| 1434 |
glyph = "unnamed_glyph" |
|---|
| 1435 |
glyphParent = self.getParent() |
|---|
| 1436 |
if glyphParent is not None: |
|---|
| 1437 |
try: |
|---|
| 1438 |
glyph = glyphParent.name |
|---|
| 1439 |
except AttributeError: pass |
|---|
| 1440 |
fontParent = glyphParent.getParent() |
|---|
| 1441 |
if fontParent is not None: |
|---|
| 1442 |
try: |
|---|
| 1443 |
font = fontParent.info.fullName |
|---|
| 1444 |
except AttributeError: pass |
|---|
| 1445 |
try: |
|---|
| 1446 |
idx = `self.index` |
|---|
| 1447 |
except ValueError: |
|---|
| 1448 |
|
|---|
| 1449 |
idx = "XXX" |
|---|
| 1450 |
return "<RContour for %s.%s[%s]>"%(font, glyph, idx) |
|---|
| 1451 |
|
|---|
| 1452 |
def __len__(self): |
|---|
| 1453 |
return len(self.segments) |
|---|
| 1454 |
|
|---|
| 1455 |
def __mul__(self, factor): |
|---|
| 1456 |
from warnings import warn |
|---|
| 1457 |
warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1458 |
n = self.copy() |
|---|
| 1459 |
n.segments = [] |
|---|
| 1460 |
for i in range(len(self.segments)): |
|---|
| 1461 |
n.segments.append(self.segments[i] * factor) |
|---|
| 1462 |
n._setParentTree() |
|---|
| 1463 |
return n |
|---|
| 1464 |
|
|---|
| 1465 |
__rmul__ = __mul__ |
|---|
| 1466 |
|
|---|
| 1467 |
def __add__(self, other): |
|---|
| 1468 |
from warnings import warn |
|---|
| 1469 |
warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1470 |
n = self.copy() |
|---|
| 1471 |
n.segments = [] |
|---|
| 1472 |
for i in range(len(self.segments)): |
|---|
| 1473 |
n.segments.append(self.segments[i] + other.segments[i]) |
|---|
| 1474 |
n._setParentTree() |
|---|
| 1475 |
return n |
|---|
| 1476 |
|
|---|
| 1477 |
def __sub__(self, other): |
|---|
| 1478 |
from warnings import warn |
|---|
| 1479 |
warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1480 |
n = self.copy() |
|---|
| 1481 |
n.segments = [] |
|---|
| 1482 |
for i in range(len(self.segments)): |
|---|
| 1483 |
n.segments.append(self.segments[i] - other.segments[i]) |
|---|
| 1484 |
n._setParentTree() |
|---|
| 1485 |
return n |
|---|
| 1486 |
|
|---|
| 1487 |
def __getitem__(self, index): |
|---|
| 1488 |
return self.segments[index] |
|---|
| 1489 |
|
|---|
| 1490 |
def _hasChanged(self): |
|---|
| 1491 |
"""mark the object and it's parent as changed""" |
|---|
| 1492 |
self.setChanged(True) |
|---|
| 1493 |
if self.getParent() is not None: |
|---|
| 1494 |
self.getParent()._hasChanged() |
|---|
| 1495 |
|
|---|
| 1496 |
def _nextSegment(self, segmentIndex): |
|---|
| 1497 |
return self.segments[(segmentIndex + 1) % len(self.segments)] |
|---|
| 1498 |
|
|---|
| 1499 |
def _prevSegment(self, segmentIndex): |
|---|
| 1500 |
segments = self.segments |
|---|
| 1501 |
return self.segments[(segmentIndex - 1) % len(self.segments)] |
|---|
| 1502 |
|
|---|
| 1503 |
def _get_box(self): |
|---|
| 1504 |
bounds = _box(self) |
|---|
| 1505 |
return bounds |
|---|
| 1506 |
|
|---|
| 1507 |
box = property(_get_box, doc="the bounding box for the contour") |
|---|
| 1508 |
|
|---|
| 1509 |
def _set_clockwise(self, value): |
|---|
| 1510 |
if self.clockwise != value: |
|---|
| 1511 |
self.reverseContour() |
|---|
| 1512 |
|
|---|
| 1513 |
def _get_clockwise(self): |
|---|
| 1514 |
anchors = [] |
|---|
| 1515 |
lastOn = None |
|---|
| 1516 |
for segment in self.segments: |
|---|
| 1517 |
anchor = segment.onCurve |
|---|
| 1518 |
anchor = (anchor.x, anchor.y) |
|---|
| 1519 |
|
|---|
| 1520 |
if anchor == lastOn: |
|---|
| 1521 |
continue |
|---|
| 1522 |
anchors.append(anchor) |
|---|
| 1523 |
lastOn = anchor |
|---|
| 1524 |
|
|---|
| 1525 |
first = anchors[0] |
|---|
| 1526 |
last = anchors[-1] |
|---|
| 1527 |
if first == last: |
|---|
| 1528 |
del anchors[-1] |
|---|
| 1529 |
nPoints = len(anchors) |
|---|
| 1530 |
angles = [] |
|---|
| 1531 |
for i1 in range(nPoints): |
|---|
| 1532 |
i2 = (i1 + 1) % nPoints |
|---|
| 1533 |
x1, y1 = anchors[i1] |
|---|
| 1534 |
x2, y2 = anchors[i2] |
|---|
| 1535 |
angles.append(math.atan2(y2-y1, x2-x1)) |
|---|
| 1536 |
total = 0 |
|---|
| 1537 |
pi = math.pi |
|---|
| 1538 |
pi2 = pi * 2 |
|---|
| 1539 |
for i1 in range(nPoints): |
|---|
| 1540 |
i2 = (i1 + 1) % nPoints |
|---|
| 1541 |
d = ((angles[i2] - angles[i1] + pi) % pi2) - pi |
|---|
| 1542 |
total += d |
|---|
| 1543 |
return total < 0 |
|---|
| 1544 |
|
|---|
| 1545 |
clockwise = property(_get_clockwise, _set_clockwise, doc="direction of contour: 1=clockwise 0=counterclockwise") |
|---|
| 1546 |
|
|---|
| 1547 |
def copy(self, aParent=None): |
|---|
| 1548 |
"""Duplicate this contour""" |
|---|
| 1549 |
n = self.__class__() |
|---|
| 1550 |
if aParent is not None: |
|---|
| 1551 |
n.setParent(aParent) |
|---|
| 1552 |
elif self.getParent() is not None: |
|---|
| 1553 |
n.setParent(self.getParent()) |
|---|
| 1554 |
dont = ['_object', 'points', 'bPoints', 'getParent'] |
|---|
| 1555 |
for k in self.__dict__.keys(): |
|---|
| 1556 |
ok = True |
|---|
| 1557 |
if k in dont: |
|---|
| 1558 |
continue |
|---|
| 1559 |
elif k == "segments": |
|---|
| 1560 |
dup = [] |
|---|
| 1561 |
for i in self.segments: |
|---|
| 1562 |
dup.append(i.copy(n)) |
|---|
| 1563 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 1564 |
dup = self.__dict__[k].copy(n) |
|---|
| 1565 |
else: |
|---|
| 1566 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 1567 |
if ok: |
|---|
| 1568 |
setattr(n, k, dup) |
|---|
| 1569 |
return n |
|---|
| 1570 |
|
|---|
| 1571 |
def _setParentTree(self): |
|---|
| 1572 |
"""Set the parents of all contained and dependent objects (and their dependents) right.""" |
|---|
| 1573 |
for item in self.segments: |
|---|
| 1574 |
item.setParent(self) |
|---|
| 1575 |
|
|---|
| 1576 |
def round(self): |
|---|
| 1577 |
"""round the value of all points in the contour""" |
|---|
| 1578 |
for n in self.points: |
|---|
| 1579 |
n.round() |
|---|
| 1580 |
|
|---|
| 1581 |
def draw(self, pen): |
|---|
| 1582 |
"""draw the object with a fontTools pen""" |
|---|
| 1583 |
firstOn = self.segments[0].onCurve |
|---|
| 1584 |
firstType = self.segments[0].type |
|---|
| 1585 |
lastOn = self.segments[-1].onCurve |
|---|
| 1586 |
|
|---|
| 1587 |
|
|---|
| 1588 |
|
|---|
| 1589 |
|
|---|
| 1590 |
|
|---|
| 1591 |
|
|---|
| 1592 |
|
|---|
| 1593 |
if firstType == QCURVE: |
|---|
| 1594 |
pen.moveTo((firstOn.x, firstOn.y)) |
|---|
| 1595 |
for segment in self.segments[1:]: |
|---|
| 1596 |
segmentType = segment.type |
|---|
| 1597 |
pt = segment.onCurve.x, segment.onCurve.y |
|---|
| 1598 |
if segmentType == LINE: |
|---|
| 1599 |
pen.lineTo(pt) |
|---|
| 1600 |
elif segmentType == CURVE: |
|---|
| 1601 |
pts = [(point.x, point.y) for point in segment.points] |
|---|
| 1602 |
pen.curveTo(*pts) |
|---|
| 1603 |
elif segmentType == QCURVE: |
|---|
| 1604 |
pts = [(point.x, point.y) for point in segment.points] |
|---|
| 1605 |
pen.qCurveTo(*pts) |
|---|
| 1606 |
else: |
|---|
| 1607 |
assert 0, "unsupported segment type" |
|---|
| 1608 |
pts = [(point.x, point.y) for point in self.segments[0].points] |
|---|
| 1609 |
pen.qCurveTo(*pts) |
|---|
| 1610 |
pen.closePath() |
|---|
| 1611 |
else: |
|---|
| 1612 |
if firstType == MOVE and (firstOn.x, firstOn.y) == (lastOn.x, lastOn.y): |
|---|
| 1613 |
closed = True |
|---|
| 1614 |
else: |
|---|
| 1615 |
closed = True |
|---|
| 1616 |
for segment in self.segments: |
|---|
| 1617 |
segmentType = segment.type |
|---|
| 1618 |
pt = segment.onCurve.x, segment.onCurve.y |
|---|
| 1619 |
if segmentType == MOVE: |
|---|
| 1620 |
pen.moveTo(pt) |
|---|
| 1621 |
elif segmentType == LINE: |
|---|
| 1622 |
pen.lineTo(pt) |
|---|
| 1623 |
elif segmentType == CURVE: |
|---|
| 1624 |
pts = [(point.x, point.y) for point in segment.points] |
|---|
| 1625 |
pen.curveTo(*pts) |
|---|
| 1626 |
elif segmentType == QCURVE: |
|---|
| 1627 |
pts = [(point.x, point.y) for point in segment.points] |
|---|
| 1628 |
pen.qCurveTo(*pts) |
|---|
| 1629 |
else: |
|---|
| 1630 |
assert 0, "unsupported segment type" |
|---|
| 1631 |
if closed: |
|---|
| 1632 |
pen.closePath() |
|---|
| 1633 |
else: |
|---|
| 1634 |
pen.endPath() |
|---|
| 1635 |
|
|---|
| 1636 |
def drawPoints(self, pen): |
|---|
| 1637 |
"""draw the object with a point pen""" |
|---|
| 1638 |
pen.beginPath() |
|---|
| 1639 |
lastOn = self.segments[-1].onCurve |
|---|
| 1640 |
didLastOn = False |
|---|
| 1641 |
flQCurveException = False |
|---|
| 1642 |
lastIndex = len(self.segments) - 1 |
|---|
| 1643 |
for i in range(len(self.segments)): |
|---|
| 1644 |
segment = self.segments[i] |
|---|
| 1645 |
segmentType = segment.type |
|---|
| 1646 |
|
|---|
| 1647 |
|
|---|
| 1648 |
|
|---|
| 1649 |
if segmentType == MOVE and (segment.onCurve.x, segment.onCurve.y) == (lastOn.x, lastOn.y): |
|---|
| 1650 |
point = self.segments[-1].onCurve |
|---|
| 1651 |
name = getattr(point, 'name', None) |
|---|
| 1652 |
pen.addPoint((point.x, point.y), point.type, smooth=self.segments[-1].smooth, name=name) |
|---|
| 1653 |
didLastOn = True |
|---|
| 1654 |
continue |
|---|
| 1655 |
|
|---|
| 1656 |
|
|---|
| 1657 |
|
|---|
| 1658 |
|
|---|
| 1659 |
|
|---|
| 1660 |
|
|---|
| 1661 |
|
|---|
| 1662 |
|
|---|
| 1663 |
if i == 0 and segmentType == QCURVE: |
|---|
| 1664 |
flQCurveException = True |
|---|
| 1665 |
if segmentType == MOVE: |
|---|
| 1666 |
segmentType = LINE |
|---|
| 1667 |
|
|---|
| 1668 |
if i == 0 and flQCurveException: |
|---|
| 1669 |
pass |
|---|
| 1670 |
else: |
|---|
| 1671 |
for point in segment.offCurve: |
|---|
| 1672 |
name = getattr(point, 'name', None) |
|---|
| 1673 |
pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected) |
|---|
| 1674 |
|
|---|
| 1675 |
|
|---|
| 1676 |
if i == lastIndex and didLastOn: |
|---|
| 1677 |
continue |
|---|
| 1678 |
point = segment.onCurve |
|---|
| 1679 |
name = getattr(point, 'name', None) |
|---|
| 1680 |
pen.addPoint((point.x, point.y), segmentType, smooth=segment.smooth, name=name, selected=point.selected) |
|---|
| 1681 |
|
|---|
| 1682 |
|
|---|
| 1683 |
if flQCurveException: |
|---|
| 1684 |
for point in self.segments[0].offCurve: |
|---|
| 1685 |
name = getattr(point, 'name', None) |
|---|
| 1686 |
pen.addPoint((point.x, point.y), segmentType=None, smooth=None, name=name, selected=point.selected) |
|---|
| 1687 |
pen.endPath() |
|---|
| 1688 |
|
|---|
| 1689 |
def move(self, (x, y)): |
|---|
| 1690 |
"""move the contour""" |
|---|
| 1691 |
|
|---|
| 1692 |
for point in self.points: |
|---|
| 1693 |
point.move((x, y)) |
|---|
| 1694 |
|
|---|
| 1695 |
def scale(self,(x, y), center=(0, 0)): |
|---|
| 1696 |
"""scale the contour""" |
|---|
| 1697 |
|
|---|
| 1698 |
for point in self.points: |
|---|
| 1699 |
point.scale((x, y), center=center) |
|---|
| 1700 |
|
|---|
| 1701 |
def transform(self, matrix): |
|---|
| 1702 |
"""Transform this contour. |
|---|
| 1703 |
Use a Transform matrix object from |
|---|
| 1704 |
robofab.transform""" |
|---|
| 1705 |
n = [] |
|---|
| 1706 |
for s in self.segments: |
|---|
| 1707 |
s.transform(matrix) |
|---|
| 1708 |
|
|---|
| 1709 |
def rotate(self, angle, offset=None): |
|---|
| 1710 |
"""rotate the contour""" |
|---|
| 1711 |
from fontTools.misc.transform import Identity |
|---|
| 1712 |
radAngle = angle / DEGREE |
|---|
| 1713 |
if offset is None: |
|---|
| 1714 |
offset = (0,0) |
|---|
| 1715 |
rT = Identity.translate(offset[0], offset[1]) |
|---|
| 1716 |
rT = rT.rotate(radAngle) |
|---|
| 1717 |
self.transform(rT) |
|---|
| 1718 |
|
|---|
| 1719 |
def skew(self, angle, offset=None): |
|---|
| 1720 |
"""skew the contour""" |
|---|
| 1721 |
from fontTools.misc.transform import Identity |
|---|
| 1722 |
radAngle = angle / DEGREE |
|---|
| 1723 |
if offset is None: |
|---|
| 1724 |
offset = (0,0) |
|---|
| 1725 |
rT = Identity.translate(offset[0], offset[1]) |
|---|
| 1726 |
rT = rT.skew(radAngle) |
|---|
| 1727 |
self.transform(rT) |
|---|
| 1728 |
|
|---|
| 1729 |
def pointInside(self, (x, y), evenOdd=0): |
|---|
| 1730 |
"""determine if the point is inside or ouside of the contour""" |
|---|
| 1731 |
from fontTools.pens.pointInsidePen import PointInsidePen |
|---|
| 1732 |
glyph = self.getParent() |
|---|
| 1733 |
font = glyph.getParent() |
|---|
| 1734 |
piPen = PointInsidePen(glyphSet=font, testPoint=(x, y), evenOdd=evenOdd) |
|---|
| 1735 |
self.draw(piPen) |
|---|
| 1736 |
return piPen.getResult() |
|---|
| 1737 |
|
|---|
| 1738 |
def autoStartSegment(self): |
|---|
| 1739 |
"""automatically set the lower left point of the contour as the first point.""" |
|---|
| 1740 |
|
|---|
| 1741 |
startIndex = 0 |
|---|
| 1742 |
startSegment = self.segments[0] |
|---|
| 1743 |
for i in range(len(self.segments)): |
|---|
| 1744 |
segment = self.segments[i] |
|---|
| 1745 |
startOn = startSegment.onCurve |
|---|
| 1746 |
on = segment.onCurve |
|---|
| 1747 |
if on.y <= startOn.y: |
|---|
| 1748 |
if on.y == startOn.y: |
|---|
| 1749 |
if on.x < startOn.x: |
|---|
| 1750 |
startSegment = segment |
|---|
| 1751 |
startIndex = i |
|---|
| 1752 |
else: |
|---|
| 1753 |
startSegment = segment |
|---|
| 1754 |
startIndex = i |
|---|
| 1755 |
if startIndex != 0: |
|---|
| 1756 |
self.setStartSegment(startIndex) |
|---|
| 1757 |
|
|---|
| 1758 |
def appendBPoint(self, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)): |
|---|
| 1759 |
"""append a bPoint to the contour""" |
|---|
| 1760 |
self.insertBPoint(len(self.segments), pointType=pointType, anchor=anchor, bcpIn=bcpIn, bcpOut=bcpOut) |
|---|
| 1761 |
|
|---|
| 1762 |
def insertBPoint(self, index, pointType, anchor, bcpIn=(0, 0), bcpOut=(0, 0)): |
|---|
| 1763 |
"""insert a bPoint at index on the contour""" |
|---|
| 1764 |
|
|---|
| 1765 |
nextSegment = self._nextSegment(index-1) |
|---|
| 1766 |
if nextSegment.type == QCURVE: |
|---|
| 1767 |
return |
|---|
| 1768 |
if nextSegment.type == MOVE: |
|---|
| 1769 |
prevSegment = self.segments[index-1] |
|---|
| 1770 |
prevOn = prevSegment.onCurve |
|---|
| 1771 |
if bcpIn != (0, 0): |
|---|
| 1772 |
new = self.appendSegment(CURVE, [(prevOn.x, prevOn.y), absoluteBCPIn(anchor, bcpIn), anchor], smooth=False) |
|---|
| 1773 |
if pointType == CURVE: |
|---|
| 1774 |
new.smooth = True |
|---|
| 1775 |
else: |
|---|
| 1776 |
new = self.appendSegment(LINE, [anchor], smooth=False) |
|---|
| 1777 |
|
|---|
| 1778 |
if bcpOut != (0, 0): |
|---|
| 1779 |
nextOn = nextSegment.onCurve |
|---|
| 1780 |
self.appendSegment(CURVE, [absoluteBCPOut(anchor, bcpOut), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False) |
|---|
| 1781 |
else: |
|---|
| 1782 |
|
|---|
| 1783 |
if nextSegment.type != CURVE: |
|---|
| 1784 |
prevSegment = self.segments[index-1] |
|---|
| 1785 |
prevOn = prevSegment.onCurve |
|---|
| 1786 |
prevOutX, prevOutY = (prevOn.x, prevOn.y) |
|---|
| 1787 |
else: |
|---|
| 1788 |
prevOut = nextSegment.offCurve[0] |
|---|
| 1789 |
prevOutX, prevOutY = (prevOut.x, prevOut.y) |
|---|
| 1790 |
self.insertSegment(index, segmentType=CURVE, points=[(prevOutX, prevOutY), anchor, anchor], smooth=False) |
|---|
| 1791 |
newSegment = self.segments[index] |
|---|
| 1792 |
prevSegment = self._prevSegment(index) |
|---|
| 1793 |
nextSegment = self._nextSegment(index) |
|---|
| 1794 |
if nextSegment.type == MOVE: |
|---|
| 1795 |
raise RoboFabError, 'still working out curving at the end of a contour' |
|---|
| 1796 |
elif nextSegment.type == QCURVE: |
|---|
| 1797 |
return |
|---|
| 1798 |
|
|---|
| 1799 |
newIn = newSegment.offCurve[1] |
|---|
| 1800 |
nIX, nIY = absoluteBCPIn(anchor, bcpIn) |
|---|
| 1801 |
newIn.x = nIX |
|---|
| 1802 |
newIn.y = nIY |
|---|
| 1803 |
|
|---|
| 1804 |
hasCurve = True |
|---|
| 1805 |
if nextSegment.type != CURVE: |
|---|
| 1806 |
if bcpOut != (0, 0): |
|---|
| 1807 |
nextSegment.type = CURVE |
|---|
| 1808 |
hasCurve = True |
|---|
| 1809 |
else: |
|---|
| 1810 |
hasCurve = False |
|---|
| 1811 |
if hasCurve: |
|---|
| 1812 |
newOut = nextSegment.offCurve[0] |
|---|
| 1813 |
nOX, nOY = absoluteBCPOut(anchor, bcpOut) |
|---|
| 1814 |
newOut.x = nOX |
|---|
| 1815 |
newOut.y = nOY |
|---|
| 1816 |
|
|---|
| 1817 |
newAnchor = newSegment.onCurve |
|---|
| 1818 |
newA = newSegment.offCurve[0] |
|---|
| 1819 |
newB = newSegment.offCurve[1] |
|---|
| 1820 |
nextAnchor = nextSegment.onCurve |
|---|
| 1821 |
prevAnchor = prevSegment.onCurve |
|---|
| 1822 |
if (prevAnchor.x, prevAnchor.y) == (newA.x, newA.y) and (newAnchor.x, newAnchor.y) == (newB.x, newB.y): |
|---|
| 1823 |
newSegment.type = LINE |
|---|
| 1824 |
|
|---|
| 1825 |
if pointType == CURVE: |
|---|
| 1826 |
newSegment.smooth = True |
|---|
| 1827 |
|
|---|
| 1828 |
|
|---|
| 1829 |
class BaseSegment(RBaseObject): |
|---|
| 1830 |
|
|---|
| 1831 |
"""Base class for all segment objects""" |
|---|
| 1832 |
|
|---|
| 1833 |
def __init__(self): |
|---|
| 1834 |
self.changed = False |
|---|
| 1835 |
|
|---|
| 1836 |
def __repr__(self): |
|---|
| 1837 |
font = "unnamed_font" |
|---|
| 1838 |
glyph = "unnamed_glyph" |
|---|
| 1839 |
contourIndex = "unknown_contour" |
|---|
| 1840 |
contourParent = self.getParent() |
|---|
| 1841 |
if contourParent is not None: |
|---|
| 1842 |
try: |
|---|
| 1843 |
contourIndex = `contourParent.index` |
|---|
| 1844 |
except AttributeError: pass |
|---|
| 1845 |
glyphParent = contourParent.getParent() |
|---|
| 1846 |
if glyphParent is not None: |
|---|
| 1847 |
try: |
|---|
| 1848 |
glyph = glyphParent.name |
|---|
| 1849 |
except AttributeError: pass |
|---|
| 1850 |
fontParent = glyphParent.getParent() |
|---|
| 1851 |
if fontParent is not None: |
|---|
| 1852 |
try: |
|---|
| 1853 |
font = fontParent.info.fullName |
|---|
| 1854 |
except AttributeError: pass |
|---|
| 1855 |
try: |
|---|
| 1856 |
idx = `self.index` |
|---|
| 1857 |
except ValueError: |
|---|
| 1858 |
idx = "XXX" |
|---|
| 1859 |
return "<RSegment for %s.%s[%s][%s]>"%(font, glyph, contourIndex, idx) |
|---|
| 1860 |
|
|---|
| 1861 |
def __mul__(self, factor): |
|---|
| 1862 |
from warnings import warn |
|---|
| 1863 |
warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1864 |
n = self.copy() |
|---|
| 1865 |
n.points = [] |
|---|
| 1866 |
for i in range(len(self.points)): |
|---|
| 1867 |
n.points.append(self.points[i] * factor) |
|---|
| 1868 |
n._setParentTree() |
|---|
| 1869 |
return n |
|---|
| 1870 |
|
|---|
| 1871 |
__rmul__ = __mul__ |
|---|
| 1872 |
|
|---|
| 1873 |
def __add__(self, other): |
|---|
| 1874 |
from warnings import warn |
|---|
| 1875 |
warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1876 |
n = self.copy() |
|---|
| 1877 |
n.points = [] |
|---|
| 1878 |
for i in range(len(self.points)): |
|---|
| 1879 |
n.points.append(self.points[i] + other.points[i]) |
|---|
| 1880 |
return n |
|---|
| 1881 |
|
|---|
| 1882 |
def __sub__(self, other): |
|---|
| 1883 |
from warnings import warn |
|---|
| 1884 |
warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1885 |
n = self.copy() |
|---|
| 1886 |
n.points = [] |
|---|
| 1887 |
for i in range(len(self.points)): |
|---|
| 1888 |
n.points.append(self.points[i] - other.points[i]) |
|---|
| 1889 |
return n |
|---|
| 1890 |
|
|---|
| 1891 |
def _hasChanged(self): |
|---|
| 1892 |
"""mark the object and it's parent as changed""" |
|---|
| 1893 |
self.setChanged(True) |
|---|
| 1894 |
if self.getParent() is not None: |
|---|
| 1895 |
self.getParent()._hasChanged() |
|---|
| 1896 |
|
|---|
| 1897 |
def copy(self, aParent=None): |
|---|
| 1898 |
"""Duplicate this segment""" |
|---|
| 1899 |
n = self.__class__() |
|---|
| 1900 |
if aParent is not None: |
|---|
| 1901 |
n.setParent(aParent) |
|---|
| 1902 |
elif self.getParent() is not None: |
|---|
| 1903 |
n.setParent(self.getParent()) |
|---|
| 1904 |
dont = ['_object', 'getParent', 'offCurve', 'onCurve'] |
|---|
| 1905 |
for k in self.__dict__.keys(): |
|---|
| 1906 |
ok = True |
|---|
| 1907 |
if k in dont: |
|---|
| 1908 |
continue |
|---|
| 1909 |
if k == "points": |
|---|
| 1910 |
dup = [] |
|---|
| 1911 |
for i in self.points: |
|---|
| 1912 |
dup.append(i.copy(n)) |
|---|
| 1913 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 1914 |
dup = self.__dict__[k].copy(n) |
|---|
| 1915 |
else: |
|---|
| 1916 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 1917 |
if ok: |
|---|
| 1918 |
setattr(n, k, dup) |
|---|
| 1919 |
return n |
|---|
| 1920 |
|
|---|
| 1921 |
def _setParentTree(self): |
|---|
| 1922 |
"""Set the parents of all contained and dependent objects (and their dependents) right.""" |
|---|
| 1923 |
for item in self.points: |
|---|
| 1924 |
item.setParent(self) |
|---|
| 1925 |
|
|---|
| 1926 |
def round(self): |
|---|
| 1927 |
"""round all points in the segment""" |
|---|
| 1928 |
for point in self.points: |
|---|
| 1929 |
point.round() |
|---|
| 1930 |
|
|---|
| 1931 |
def move(self, (x, y)): |
|---|
| 1932 |
"""move the segment""" |
|---|
| 1933 |
for point in self.points: |
|---|
| 1934 |
point.move((x, y)) |
|---|
| 1935 |
|
|---|
| 1936 |
def scale(self, (x, y), center=(0, 0)): |
|---|
| 1937 |
"""scale the segment""" |
|---|
| 1938 |
for point in self.points: |
|---|
| 1939 |
point.scale((x, y), center=center) |
|---|
| 1940 |
|
|---|
| 1941 |
def transform(self, matrix): |
|---|
| 1942 |
"""Transform this segment. |
|---|
| 1943 |
Use a Transform matrix object from |
|---|
| 1944 |
robofab.transform""" |
|---|
| 1945 |
n = [] |
|---|
| 1946 |
for p in self.points: |
|---|
| 1947 |
p.transform(matrix) |
|---|
| 1948 |
|
|---|
| 1949 |
def _get_onCurve(self): |
|---|
| 1950 |
return self.points[-1] |
|---|
| 1951 |
|
|---|
| 1952 |
def _get_offCurve(self): |
|---|
| 1953 |
return self.points[:-1] |
|---|
| 1954 |
|
|---|
| 1955 |
offCurve = property(_get_offCurve, doc="on curve point for the segment") |
|---|
| 1956 |
onCurve = property(_get_onCurve, doc="list of off curve points for the segment") |
|---|
| 1957 |
|
|---|
| 1958 |
|
|---|
| 1959 |
|
|---|
| 1960 |
class BasePoint(RBaseObject): |
|---|
| 1961 |
|
|---|
| 1962 |
"""Base class for point objects.""" |
|---|
| 1963 |
|
|---|
| 1964 |
def __init__(self): |
|---|
| 1965 |
|
|---|
| 1966 |
self.changed = False |
|---|
| 1967 |
self.selected = False |
|---|
| 1968 |
|
|---|
| 1969 |
def __repr__(self): |
|---|
| 1970 |
font = "unnamed_font" |
|---|
| 1971 |
glyph = "unnamed_glyph" |
|---|
| 1972 |
contourIndex = "unknown_contour" |
|---|
| 1973 |
segmentIndex = "unknown_segment" |
|---|
| 1974 |
segmentParent = self.getParent() |
|---|
| 1975 |
if segmentParent is not None: |
|---|
| 1976 |
try: |
|---|
| 1977 |
segmentIndex = `segmentParent.index` |
|---|
| 1978 |
except AttributeError: pass |
|---|
| 1979 |
contourParent = self.getParent().getParent() |
|---|
| 1980 |
if contourParent is not None: |
|---|
| 1981 |
try: |
|---|
| 1982 |
contourIndex = `contourParent.index` |
|---|
| 1983 |
except AttributeError: pass |
|---|
| 1984 |
glyphParent = contourParent.getParent() |
|---|
| 1985 |
if glyphParent is not None: |
|---|
| 1986 |
try: |
|---|
| 1987 |
glyph = glyphParent.name |
|---|
| 1988 |
except AttributeError: pass |
|---|
| 1989 |
fontParent = glyphParent.getParent() |
|---|
| 1990 |
if fontParent is not None: |
|---|
| 1991 |
try: |
|---|
| 1992 |
font = fontParent.info.fullName |
|---|
| 1993 |
except AttributeError: pass |
|---|
| 1994 |
return "<RPoint for %s.%s[%s][%s]>"%(font, glyph, contourIndex, segmentIndex) |
|---|
| 1995 |
|
|---|
| 1996 |
def __add__(self, other): |
|---|
| 1997 |
from warnings import warn |
|---|
| 1998 |
warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 1999 |
|
|---|
| 2000 |
n = self.copy() |
|---|
| 2001 |
n.x, n.y = addPt((self.x, self.y), (other.x, other.y)) |
|---|
| 2002 |
return n |
|---|
| 2003 |
|
|---|
| 2004 |
def __sub__(self, other): |
|---|
| 2005 |
from warnings import warn |
|---|
| 2006 |
warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2007 |
|
|---|
| 2008 |
n = self.copy() |
|---|
| 2009 |
n.x, n.y = subPt((self.x, self.y), (other.x, other.y)) |
|---|
| 2010 |
return n |
|---|
| 2011 |
|
|---|
| 2012 |
def __mul__(self, factor): |
|---|
| 2013 |
from warnings import warn |
|---|
| 2014 |
warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2015 |
|
|---|
| 2016 |
n = self.copy() |
|---|
| 2017 |
n.x, n.y = mulPt((self.x, self.y), factor) |
|---|
| 2018 |
return n |
|---|
| 2019 |
|
|---|
| 2020 |
__rmul__ = __mul__ |
|---|
| 2021 |
|
|---|
| 2022 |
def _hasChanged(self): |
|---|
| 2023 |
|
|---|
| 2024 |
self.setChanged(True) |
|---|
| 2025 |
if self.getParent() is not None: |
|---|
| 2026 |
self.getParent()._hasChanged() |
|---|
| 2027 |
|
|---|
| 2028 |
def copy(self, aParent=None): |
|---|
| 2029 |
"""Duplicate this point""" |
|---|
| 2030 |
n = self.__class__() |
|---|
| 2031 |
if aParent is not None: |
|---|
| 2032 |
n.setParent(aParent) |
|---|
| 2033 |
elif self.getParent() is not None: |
|---|
| 2034 |
n.setParent(self.getParent()) |
|---|
| 2035 |
dont = ['getParent', 'offCurve', 'onCurve'] |
|---|
| 2036 |
for k in self.__dict__.keys(): |
|---|
| 2037 |
ok = True |
|---|
| 2038 |
if k in dont: |
|---|
| 2039 |
continue |
|---|
| 2040 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 2041 |
dup = self.__dict__[k].copy(n) |
|---|
| 2042 |
else: |
|---|
| 2043 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 2044 |
if ok: |
|---|
| 2045 |
setattr(n, k, dup) |
|---|
| 2046 |
return n |
|---|
| 2047 |
|
|---|
| 2048 |
def select(self, state=True): |
|---|
| 2049 |
"""Set the selection of this point. |
|---|
| 2050 |
XXXX This method should be a lot more versatile, dealing with |
|---|
| 2051 |
different kinds of selection, select the bcp's seperately etc. |
|---|
| 2052 |
But that's for later when we need it more. For now it's just |
|---|
| 2053 |
one flag for the entire thing.""" |
|---|
| 2054 |
self.selected = state |
|---|
| 2055 |
|
|---|
| 2056 |
def round(self): |
|---|
| 2057 |
"""round the values in the point""" |
|---|
| 2058 |
self.x, self.y = roundPt((self.x, self.y)) |
|---|
| 2059 |
|
|---|
| 2060 |
def move(self, (x, y)): |
|---|
| 2061 |
"""Move the point""" |
|---|
| 2062 |
self.x, self.y = addPt((self.x, self.y), (x, y)) |
|---|
| 2063 |
|
|---|
| 2064 |
def scale(self, (x, y), center=(0, 0)): |
|---|
| 2065 |
"""scale the point""" |
|---|
| 2066 |
nX, nY = _scalePointFromCenter((self.x, self.y), (x, y), center) |
|---|
| 2067 |
self.x = nX |
|---|
| 2068 |
self.y = nY |
|---|
| 2069 |
|
|---|
| 2070 |
def transform(self, matrix): |
|---|
| 2071 |
"""Transform this point. Use a Transform matrix |
|---|
| 2072 |
object from fontTools.misc.transform""" |
|---|
| 2073 |
self.x, self.y = matrix.transformPoint((self.x, self.y)) |
|---|
| 2074 |
|
|---|
| 2075 |
|
|---|
| 2076 |
class BaseBPoint(RBaseObject): |
|---|
| 2077 |
|
|---|
| 2078 |
"""Base class for bPoints objects.""" |
|---|
| 2079 |
|
|---|
| 2080 |
def __init__(self): |
|---|
| 2081 |
RBaseObject.__init__(self) |
|---|
| 2082 |
self.changed = False |
|---|
| 2083 |
self.selected = False |
|---|
| 2084 |
|
|---|
| 2085 |
def __repr__(self): |
|---|
| 2086 |
font = "unnamed_font" |
|---|
| 2087 |
glyph = "unnamed_glyph" |
|---|
| 2088 |
contourIndex = "unknown_contour" |
|---|
| 2089 |
segmentIndex = "unknown_segment" |
|---|
| 2090 |
segmentParent = self.getParent() |
|---|
| 2091 |
if segmentParent is not None: |
|---|
| 2092 |
try: |
|---|
| 2093 |
segmentIndex = `segmentParent.index` |
|---|
| 2094 |
except AttributeError: pass |
|---|
| 2095 |
contourParent = segmentParent.getParent() |
|---|
| 2096 |
if contourParent is not None: |
|---|
| 2097 |
try: |
|---|
| 2098 |
contourIndex = `contourParent.index` |
|---|
| 2099 |
except AttributeError: pass |
|---|
| 2100 |
glyphParent = contourParent.getParent() |
|---|
| 2101 |
if glyphParent is not None: |
|---|
| 2102 |
try: |
|---|
| 2103 |
glyph = glyphParent.name |
|---|
| 2104 |
except AttributeError: pass |
|---|
| 2105 |
fontParent = glyphParent.getParent() |
|---|
| 2106 |
if fontParent is not None: |
|---|
| 2107 |
try: |
|---|
| 2108 |
font = fontParent.info.fullName |
|---|
| 2109 |
except AttributeError: pass |
|---|
| 2110 |
return "<RBPoint for %s.%s[%s][%s][%s]>"%(font, glyph, contourIndex, segmentIndex, `self.index`) |
|---|
| 2111 |
|
|---|
| 2112 |
|
|---|
| 2113 |
def __add__(self, other): |
|---|
| 2114 |
from warnings import warn |
|---|
| 2115 |
warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2116 |
|
|---|
| 2117 |
n = self.copy() |
|---|
| 2118 |
n.anchor = addPt(self.anchor, other.anchor) |
|---|
| 2119 |
n.bcpIn = addPt(self.bcpIn, other.bcpIn) |
|---|
| 2120 |
n.bcpOut = addPt(self.bcpOut, other.bcpOut) |
|---|
| 2121 |
return n |
|---|
| 2122 |
|
|---|
| 2123 |
def __sub__(self, other): |
|---|
| 2124 |
from warnings import warn |
|---|
| 2125 |
warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2126 |
|
|---|
| 2127 |
n = self.copy() |
|---|
| 2128 |
n.anchor = subPt(self.anchor, other.anchor) |
|---|
| 2129 |
n.bcpIn = subPt(self.bcpIn, other.bcpIn) |
|---|
| 2130 |
n.bcpOut = subPt(self.bcpOut, other.bcpOut) |
|---|
| 2131 |
return n |
|---|
| 2132 |
|
|---|
| 2133 |
def __mul__(self, factor): |
|---|
| 2134 |
from warnings import warn |
|---|
| 2135 |
warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2136 |
|
|---|
| 2137 |
n = self.copy() |
|---|
| 2138 |
n.anchor = mulPt(self.anchor, factor) |
|---|
| 2139 |
n.bcpIn = mulPt(self.bcpIn, factor) |
|---|
| 2140 |
n.bcpOut = mulPt(self.bcpOut, factor) |
|---|
| 2141 |
return n |
|---|
| 2142 |
|
|---|
| 2143 |
__rmul__ = __mul__ |
|---|
| 2144 |
|
|---|
| 2145 |
def _hasChanged(self): |
|---|
| 2146 |
|
|---|
| 2147 |
self.setChanged(True) |
|---|
| 2148 |
if self.getParent() is not None: |
|---|
| 2149 |
self.getParent()._hasChanged() |
|---|
| 2150 |
|
|---|
| 2151 |
def select(self, state=True): |
|---|
| 2152 |
"""Set the selection of this point. |
|---|
| 2153 |
XXXX This method should be a lot more versatile, dealing with |
|---|
| 2154 |
different kinds of selection, select the bcp's seperately etc. |
|---|
| 2155 |
But that's for later when we need it more. For now it's just |
|---|
| 2156 |
one flag for the entire thing.""" |
|---|
| 2157 |
self.selected = state |
|---|
| 2158 |
|
|---|
| 2159 |
def round(self): |
|---|
| 2160 |
"""Round the coordinates to integers""" |
|---|
| 2161 |
self.anchor = roundPt(self.anchor) |
|---|
| 2162 |
pSeg = self._parentSegment |
|---|
| 2163 |
if pSeg.type != MOVE: |
|---|
| 2164 |
self.bcpIn = roundPt(self.bcpIn) |
|---|
| 2165 |
if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: |
|---|
| 2166 |
self.bcpOut = roundPt(self.bcpOut) |
|---|
| 2167 |
|
|---|
| 2168 |
def move(self, (x, y)): |
|---|
| 2169 |
"""move the bPoint""" |
|---|
| 2170 |
bcpIn = self.bcpIn |
|---|
| 2171 |
bcpOut = self.bcpOut |
|---|
| 2172 |
self.anchor = (self.anchor[0] + x, self.anchor[1] + y) |
|---|
| 2173 |
pSeg = self._parentSegment |
|---|
| 2174 |
if pSeg.type != MOVE: |
|---|
| 2175 |
self.bcpIn = bcpIn |
|---|
| 2176 |
if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: |
|---|
| 2177 |
self.bcpOut = bcpOut |
|---|
| 2178 |
|
|---|
| 2179 |
def scale(self, (x, y), center=(0, 0)): |
|---|
| 2180 |
"""scale the bPoint""" |
|---|
| 2181 |
centerX, centerY = center |
|---|
| 2182 |
ogCenter = (centerX, centerY) |
|---|
| 2183 |
scaledCenter = (centerX * x, centerY * y) |
|---|
| 2184 |
shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) |
|---|
| 2185 |
anchor = self.anchor |
|---|
| 2186 |
bcpIn = self.bcpIn |
|---|
| 2187 |
bcpOut = self.bcpOut |
|---|
| 2188 |
self.anchor = ((anchor[0] * x) - shiftVal[0], (anchor[1] * y) - shiftVal[1]) |
|---|
| 2189 |
pSeg = self._parentSegment |
|---|
| 2190 |
if pSeg.type != MOVE: |
|---|
| 2191 |
self.bcpIn = ((bcpIn[0] * x), (bcpIn[1] * y)) |
|---|
| 2192 |
if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: |
|---|
| 2193 |
self.bcpOut = ((bcpOut[0] * x), (bcpOut[1] * y)) |
|---|
| 2194 |
|
|---|
| 2195 |
def transform(self, matrix): |
|---|
| 2196 |
"""Transform this point. Use a Transform matrix |
|---|
| 2197 |
object from fontTools.misc.transform""" |
|---|
| 2198 |
self.anchor = matrix.transformPoint(self.anchor) |
|---|
| 2199 |
pSeg = self._parentSegment |
|---|
| 2200 |
if pSeg.type != MOVE: |
|---|
| 2201 |
self.bcpIn = matrix.transformPoint(self.bcpIn) |
|---|
| 2202 |
if pSeg.getParent()._nextSegment(pSeg.index).type != MOVE: |
|---|
| 2203 |
self.bcpOut = matrix.transformPoint(self.bcpOut) |
|---|
| 2204 |
|
|---|
| 2205 |
def _get__anchorPoint(self): |
|---|
| 2206 |
return self._parentSegment.onCurve |
|---|
| 2207 |
|
|---|
| 2208 |
_anchorPoint = property(_get__anchorPoint, doc="the oncurve point in the parent segment") |
|---|
| 2209 |
|
|---|
| 2210 |
def _get_anchor(self): |
|---|
| 2211 |
point = self._anchorPoint |
|---|
| 2212 |
return (point.x, point.y) |
|---|
| 2213 |
|
|---|
| 2214 |
def _set_anchor(self, value): |
|---|
| 2215 |
x, y = value |
|---|
| 2216 |
point = self._anchorPoint |
|---|
| 2217 |
point.x = x |
|---|
| 2218 |
point.y = y |
|---|
| 2219 |
|
|---|
| 2220 |
anchor = property(_get_anchor, _set_anchor, doc="the position of the anchor") |
|---|
| 2221 |
|
|---|
| 2222 |
def _get_bcpIn(self): |
|---|
| 2223 |
pSeg = self._parentSegment |
|---|
| 2224 |
pCount = len(pSeg.offCurve) |
|---|
| 2225 |
if pCount == 2: |
|---|
| 2226 |
p = pSeg.offCurve[1] |
|---|
| 2227 |
pOn = pSeg.onCurve |
|---|
| 2228 |
return relativeBCPIn((pOn.x, pOn.y), (p.x, p.y)) |
|---|
| 2229 |
else: |
|---|
| 2230 |
return (0, 0) |
|---|
| 2231 |
|
|---|
| 2232 |
def _set_bcpIn(self, value): |
|---|
| 2233 |
x, y = (absoluteBCPIn(self.anchor, value)) |
|---|
| 2234 |
pSeg = self._parentSegment |
|---|
| 2235 |
if pSeg.type == MOVE: |
|---|
| 2236 |
|
|---|
| 2237 |
if value == (0, 0) and self.bcpOut == (0, 0): |
|---|
| 2238 |
|
|---|
| 2239 |
pass |
|---|
| 2240 |
else: |
|---|
| 2241 |
|
|---|
| 2242 |
contour = self._parentSegment.getParent() |
|---|
| 2243 |
|
|---|
| 2244 |
prevSeg = contour._prevSegment(self._parentSegment.index) |
|---|
| 2245 |
prevOn = prevSeg.onCurve |
|---|
| 2246 |
contour.appendSegment(CURVE, [(prevOn.x, prevOn.y), (x, y), self.anchor], smooth=False) |
|---|
| 2247 |
else: |
|---|
| 2248 |
pCount = len(pSeg.offCurve) |
|---|
| 2249 |
if pCount == 2: |
|---|
| 2250 |
|
|---|
| 2251 |
|
|---|
| 2252 |
if value == (0, 0) and self.bcpOut == (0, 0): |
|---|
| 2253 |
pSeg.type = LINE |
|---|
| 2254 |
pSeg.smooth = False |
|---|
| 2255 |
else: |
|---|
| 2256 |
pSeg.offCurve[1].x = x |
|---|
| 2257 |
pSeg.offCurve[1].y = y |
|---|
| 2258 |
elif value != (0, 0): |
|---|
| 2259 |
pSeg.type = CURVE |
|---|
| 2260 |
pSeg.offCurve[1].x = x |
|---|
| 2261 |
pSeg.offCurve[1].y = y |
|---|
| 2262 |
|
|---|
| 2263 |
bcpIn = property(_get_bcpIn, _set_bcpIn, doc="the (x,y) for the incoming bcp") |
|---|
| 2264 |
|
|---|
| 2265 |
def _get_bcpOut(self): |
|---|
| 2266 |
pSeg = self._parentSegment |
|---|
| 2267 |
nextSeg = pSeg.getParent()._nextSegment(pSeg.index) |
|---|
| 2268 |
nsCount = len(nextSeg.offCurve) |
|---|
| 2269 |
if nsCount == 2: |
|---|
| 2270 |
p = nextSeg.offCurve[0] |
|---|
| 2271 |
return relativeBCPOut(self.anchor, (p.x, p.y)) |
|---|
| 2272 |
else: |
|---|
| 2273 |
return (0, 0) |
|---|
| 2274 |
|
|---|
| 2275 |
def _set_bcpOut(self, value): |
|---|
| 2276 |
x, y = (absoluteBCPOut(self.anchor, value)) |
|---|
| 2277 |
pSeg = self._parentSegment |
|---|
| 2278 |
nextSeg = pSeg.getParent()._nextSegment(pSeg.index) |
|---|
| 2279 |
if nextSeg.type == MOVE: |
|---|
| 2280 |
if value == (0, 0) and self.bcpIn == (0, 0): |
|---|
| 2281 |
pass |
|---|
| 2282 |
else: |
|---|
| 2283 |
|
|---|
| 2284 |
contour = self._parentSegment.getParent() |
|---|
| 2285 |
nextOn = nextSeg.onCurve |
|---|
| 2286 |
contour.appendSegment(CURVE, [(x, y), (nextOn.x, nextOn.y), (nextOn.x, nextOn.y)], smooth=False) |
|---|
| 2287 |
else: |
|---|
| 2288 |
nsCount = len(nextSeg.offCurve) |
|---|
| 2289 |
if nsCount == 2: |
|---|
| 2290 |
|
|---|
| 2291 |
|
|---|
| 2292 |
if value == (0, 0) and self.bcpIn == (0, 0): |
|---|
| 2293 |
nextSeg.type = LINE |
|---|
| 2294 |
nextSeg.smooth = False |
|---|
| 2295 |
else: |
|---|
| 2296 |
nextSeg.offCurve[0].x = x |
|---|
| 2297 |
nextSeg.offCurve[0].y = y |
|---|
| 2298 |
elif value != (0, 0): |
|---|
| 2299 |
nextSeg.type = CURVE |
|---|
| 2300 |
nextSeg.offCurve[0].x = x |
|---|
| 2301 |
nextSeg.offCurve[0].y = y |
|---|
| 2302 |
|
|---|
| 2303 |
bcpOut = property(_get_bcpOut, _set_bcpOut, doc="the (x,y) for the outgoing bcp") |
|---|
| 2304 |
|
|---|
| 2305 |
def _get_type(self): |
|---|
| 2306 |
pType = self._parentSegment.type |
|---|
| 2307 |
bpType = CORNER |
|---|
| 2308 |
if pType == CURVE: |
|---|
| 2309 |
if self._parentSegment.smooth: |
|---|
| 2310 |
bpType = CURVE |
|---|
| 2311 |
return bpType |
|---|
| 2312 |
|
|---|
| 2313 |
def _set_type(self, pointType): |
|---|
| 2314 |
pSeg = self._parentSegment |
|---|
| 2315 |
segType = pSeg.type |
|---|
| 2316 |
|
|---|
| 2317 |
if pointType == CURVE and segType == LINE: |
|---|
| 2318 |
pSeg.type = CURVE |
|---|
| 2319 |
pSeg.smooth = True |
|---|
| 2320 |
|
|---|
| 2321 |
elif pointType == CORNER and segType == CURVE: |
|---|
| 2322 |
pSeg.smooth = False |
|---|
| 2323 |
|
|---|
| 2324 |
type = property(_get_type, _set_type, doc="the type of bPoint, either 'corner' or 'curve'") |
|---|
| 2325 |
|
|---|
| 2326 |
|
|---|
| 2327 |
class BaseComponent(RBaseObject): |
|---|
| 2328 |
|
|---|
| 2329 |
"""Base class for all component objects.""" |
|---|
| 2330 |
|
|---|
| 2331 |
def __init__(self): |
|---|
| 2332 |
RBaseObject.__init__(self) |
|---|
| 2333 |
self.changed = False |
|---|
| 2334 |
self.selected = False |
|---|
| 2335 |
|
|---|
| 2336 |
def __repr__(self): |
|---|
| 2337 |
font = "unnamed_font" |
|---|
| 2338 |
glyph = "unnamed_glyph" |
|---|
| 2339 |
glyphParent = self.getParent() |
|---|
| 2340 |
if glyphParent is not None: |
|---|
| 2341 |
try: |
|---|
| 2342 |
glyph = glyphParent.name |
|---|
| 2343 |
except AttributeError: pass |
|---|
| 2344 |
fontParent = glyphParent.getParent() |
|---|
| 2345 |
if fontParent is not None: |
|---|
| 2346 |
try: |
|---|
| 2347 |
font = fontParent.info.fullName |
|---|
| 2348 |
except AttributeError: pass |
|---|
| 2349 |
return "<RComponent for %s.%s.components[%s]>"%(font, glyph, `self.index`) |
|---|
| 2350 |
|
|---|
| 2351 |
def _hasChanged(self): |
|---|
| 2352 |
"""mark the object and it's parent as changed""" |
|---|
| 2353 |
self.setChanged(True) |
|---|
| 2354 |
if self.getParent() is not None: |
|---|
| 2355 |
self.getParent()._hasChanged() |
|---|
| 2356 |
|
|---|
| 2357 |
def copy(self, aParent=None): |
|---|
| 2358 |
"""Duplicate this component.""" |
|---|
| 2359 |
n = self.__class__() |
|---|
| 2360 |
if aParent is not None: |
|---|
| 2361 |
n.setParent(aParent) |
|---|
| 2362 |
elif self.getParent() is not None: |
|---|
| 2363 |
n.setParent(self.getParent()) |
|---|
| 2364 |
dont = ['getParent', '_object'] |
|---|
| 2365 |
for k in self.__dict__.keys(): |
|---|
| 2366 |
if k in dont: |
|---|
| 2367 |
continue |
|---|
| 2368 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 2369 |
dup = self.__dict__[k].copy(n) |
|---|
| 2370 |
else: |
|---|
| 2371 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 2372 |
setattr(n, k, dup) |
|---|
| 2373 |
return n |
|---|
| 2374 |
|
|---|
| 2375 |
def __add__(self, other): |
|---|
| 2376 |
from warnings import warn |
|---|
| 2377 |
warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2378 |
|
|---|
| 2379 |
n = self.copy() |
|---|
| 2380 |
n.offset = addPt(self.offset, other.offset) |
|---|
| 2381 |
n.scale = addPt(self.scale, other.scale) |
|---|
| 2382 |
return n |
|---|
| 2383 |
|
|---|
| 2384 |
def __sub__(self, other): |
|---|
| 2385 |
from warnings import warn |
|---|
| 2386 |
warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2387 |
|
|---|
| 2388 |
n = self.copy() |
|---|
| 2389 |
n.offset = subPt(self.offset, other.offset) |
|---|
| 2390 |
n.scale = subPt(self.scale, other.scale) |
|---|
| 2391 |
return n |
|---|
| 2392 |
|
|---|
| 2393 |
def __mul__(self, factor): |
|---|
| 2394 |
from warnings import warn |
|---|
| 2395 |
warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2396 |
|
|---|
| 2397 |
n = self.copy() |
|---|
| 2398 |
n.offset = mulPt(self.offset, factor) |
|---|
| 2399 |
n.scale = mulPt(self.scale, factor) |
|---|
| 2400 |
return n |
|---|
| 2401 |
|
|---|
| 2402 |
__rmul__ = __mul__ |
|---|
| 2403 |
|
|---|
| 2404 |
def _get_box(self): |
|---|
| 2405 |
parentGlyph = self.getParent() |
|---|
| 2406 |
|
|---|
| 2407 |
if parentGlyph is None: |
|---|
| 2408 |
return None |
|---|
| 2409 |
parentFont = parentGlyph.getParent() |
|---|
| 2410 |
|
|---|
| 2411 |
|
|---|
| 2412 |
if parentFont is None: |
|---|
| 2413 |
return None |
|---|
| 2414 |
|
|---|
| 2415 |
|
|---|
| 2416 |
|
|---|
| 2417 |
if not parentFont.has_key(self.baseGlyph): |
|---|
| 2418 |
return None |
|---|
| 2419 |
return _box(self, parentFont) |
|---|
| 2420 |
|
|---|
| 2421 |
box = property(_get_box, doc="the bounding box of the component: (xMin, yMin, xMax, yMax)") |
|---|
| 2422 |
|
|---|
| 2423 |
def round(self): |
|---|
| 2424 |
"""round the offset values""" |
|---|
| 2425 |
self.offset = roundPt(self.offset) |
|---|
| 2426 |
self._hasChanged() |
|---|
| 2427 |
|
|---|
| 2428 |
def draw(self, pen): |
|---|
| 2429 |
"""Segment pen drawing method.""" |
|---|
| 2430 |
if isinstance(pen, AbstractPen): |
|---|
| 2431 |
|
|---|
| 2432 |
|
|---|
| 2433 |
self.drawPoints(pen) |
|---|
| 2434 |
else: |
|---|
| 2435 |
|
|---|
| 2436 |
pen.addComponent(self.baseGlyph, self.offset, self.scale) |
|---|
| 2437 |
|
|---|
| 2438 |
def drawPoints(self, pen): |
|---|
| 2439 |
"""draw the object with a point pen""" |
|---|
| 2440 |
oX, oY = self.offset |
|---|
| 2441 |
sX, sY = self.scale |
|---|
| 2442 |
|
|---|
| 2443 |
pen.addComponent(self.baseGlyph, (sX, 0, 0, sY, oX, oY)) |
|---|
| 2444 |
|
|---|
| 2445 |
|
|---|
| 2446 |
class BaseAnchor(RBaseObject): |
|---|
| 2447 |
|
|---|
| 2448 |
"""Base class for all anchor point objects.""" |
|---|
| 2449 |
|
|---|
| 2450 |
def __init__(self): |
|---|
| 2451 |
RBaseObject.__init__(self) |
|---|
| 2452 |
self.changed = False |
|---|
| 2453 |
self.selected = False |
|---|
| 2454 |
|
|---|
| 2455 |
def __repr__(self): |
|---|
| 2456 |
font = "unnamed_font" |
|---|
| 2457 |
glyph = "unnamed_glyph" |
|---|
| 2458 |
glyphParent = self.getParent() |
|---|
| 2459 |
if glyphParent is not None: |
|---|
| 2460 |
try: |
|---|
| 2461 |
glyph = glyphParent.name |
|---|
| 2462 |
except AttributeError: pass |
|---|
| 2463 |
fontParent = glyphParent.getParent() |
|---|
| 2464 |
if fontParent is not None: |
|---|
| 2465 |
try: |
|---|
| 2466 |
font = fontParent.info.fullName |
|---|
| 2467 |
except AttributeError: pass |
|---|
| 2468 |
return "<RAnchor for %s.%s.anchors[%s]>"%(font, glyph, `self.index`) |
|---|
| 2469 |
|
|---|
| 2470 |
def __add__(self, other): |
|---|
| 2471 |
from warnings import warn |
|---|
| 2472 |
warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2473 |
|
|---|
| 2474 |
n = self.copy() |
|---|
| 2475 |
n.x, n.y = addPt((self.x, self.y), (other.x, other.y)) |
|---|
| 2476 |
return n |
|---|
| 2477 |
|
|---|
| 2478 |
def __sub__(self, other): |
|---|
| 2479 |
from warnings import warn |
|---|
| 2480 |
warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2481 |
|
|---|
| 2482 |
n = self.copy() |
|---|
| 2483 |
n.x, n.y = subPt((self.x, self.y), (other.x, other.y)) |
|---|
| 2484 |
return n |
|---|
| 2485 |
|
|---|
| 2486 |
def __mul__(self, factor): |
|---|
| 2487 |
from warnings import warn |
|---|
| 2488 |
warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) |
|---|
| 2489 |
|
|---|
| 2490 |
n = self.copy() |
|---|
| 2491 |
n.x, n.y = mulPt((self.x, self.y), factor) |
|---|
| 2492 |
return n |
|---|
| 2493 |
|
|---|
| 2494 |
__rmul__ = __mul__ |
|---|
| 2495 |
|
|---|
| 2496 |
def _hasChanged(self): |
|---|
| 2497 |
|
|---|
| 2498 |
self.setChanged(True) |
|---|
| 2499 |
if self.getParent() is not None: |
|---|
| 2500 |
self.getParent()._hasChanged() |
|---|
| 2501 |
|
|---|
| 2502 |
def copy(self, aParent=None): |
|---|
| 2503 |
"""Duplicate this anchor.""" |
|---|
| 2504 |
n = self.__class__() |
|---|
| 2505 |
if aParent is not None: |
|---|
| 2506 |
n.setParent(aParent) |
|---|
| 2507 |
elif self.getParent() is not None: |
|---|
| 2508 |
n.setParent(self.getParent()) |
|---|
| 2509 |
dont = ['getParent', '_object'] |
|---|
| 2510 |
for k in self.__dict__.keys(): |
|---|
| 2511 |
if k in dont: |
|---|
| 2512 |
continue |
|---|
| 2513 |
elif isinstance(self.__dict__[k], (RBaseObject, BaseLib)): |
|---|
| 2514 |
dup = self.__dict__[k].copy(n) |
|---|
| 2515 |
else: |
|---|
| 2516 |
dup = copy.deepcopy(self.__dict__[k]) |
|---|
| 2517 |
setattr(n, k, dup) |
|---|
| 2518 |
return n |
|---|
| 2519 |
|
|---|
| 2520 |
def round(self): |
|---|
| 2521 |
"""round the values in the anchor""" |
|---|
| 2522 |
self.x, self.y = roundPt((self.x, self.y)) |
|---|
| 2523 |
self._hasChanged() |
|---|
| 2524 |
|
|---|
| 2525 |
def draw(self, pen): |
|---|
| 2526 |
"""Draw the object onto a segment pen""" |
|---|
| 2527 |
if isinstance(pen, AbstractPen): |
|---|
| 2528 |
|
|---|
| 2529 |
pen.moveTo((self.x, self.y)) |
|---|
| 2530 |
pen.endPath() |
|---|
| 2531 |
else: |
|---|
| 2532 |
|
|---|
| 2533 |
pen.addAnchor(self.name, (self.x, self.y)) |
|---|
| 2534 |
|
|---|
| 2535 |
def drawPoints(self, pen): |
|---|
| 2536 |
"""draw the object with a point pen""" |
|---|
| 2537 |
pen.beginPath() |
|---|
| 2538 |
pen.addPoint((self.x, self.y), segmentType="move", smooth=False, name=self.name) |
|---|
| 2539 |
pen.endPath() |
|---|
| 2540 |
|
|---|
| 2541 |
def move(self, (x, y)): |
|---|
| 2542 |
"""Move the anchor""" |
|---|
| 2543 |
pX, pY = self.position |
|---|
| 2544 |
self.position = (pX+x, pY+y) |
|---|
| 2545 |
|
|---|
| 2546 |
def scale(self, (x, y), center=(0, 0)): |
|---|
| 2547 |
"""scale the anchor""" |
|---|
| 2548 |
pos = self.position |
|---|
| 2549 |
self.position = _scalePointFromCenter(pos, (x, y), center) |
|---|
| 2550 |
|
|---|
| 2551 |
def transform(self, matrix): |
|---|
| 2552 |
"""Transform this anchor. Use a Transform matrix |
|---|
| 2553 |
object from fontTools.misc.transform""" |
|---|
| 2554 |
self.x, self.y = matrix.transformPoint((self.x, self.y)) |
|---|
| 2555 |
|
|---|
| 2556 |
|
|---|
| 2557 |
class BaseGuide(RBaseObject): |
|---|
| 2558 |
|
|---|
| 2559 |
"""Base class for all guide objects.""" |
|---|
| 2560 |
|
|---|
| 2561 |
def __init__(self): |
|---|
| 2562 |
RBaseObject.__init__(self) |
|---|
| 2563 |
self.changed = False |
|---|
| 2564 |
self.selected = False |
|---|
| 2565 |
|
|---|
| 2566 |
|
|---|
| 2567 |
|
|---|
| 2568 |
|
|---|
| 2569 |
class BaseInfo(RBaseObject): |
|---|
| 2570 |
|
|---|
| 2571 |
"""Base class for all font.info objects.""" |
|---|
| 2572 |
|
|---|
| 2573 |
def __init__(self): |
|---|
| 2574 |
RBaseObject.__init__(self) |
|---|
| 2575 |
|
|---|
| 2576 |
def __repr__(self): |
|---|
| 2577 |
font = self.fullName |
|---|
| 2578 |
return "<RInfo for %s>"%font |
|---|
| 2579 |
|
|---|
| 2580 |
def _get_familyName(self): |
|---|
| 2581 |
raise NotImplementedError |
|---|
| 2582 |
|
|---|
| 2583 |
def _set_familyName(self, value): |
|---|
| 2584 |
raise NotImplementedError |
|---|
| 2585 |
|
|---|
| 2586 |
familyName = property(_get_familyName, _set_familyName, doc="family name") |
|---|
| 2587 |
|
|---|
| 2588 |
def _get_styleName(self): |
|---|
| 2589 |
raise NotImplementedError |
|---|
| 2590 |
|
|---|
| 2591 |
def _set_styleName(self, value): |
|---|
| 2592 |
raise NotImplementedError |
|---|
| 2593 |
|
|---|
| 2594 |
styleName = property(_get_styleName, _set_styleName, doc="style name") |
|---|
| 2595 |
|
|---|
| 2596 |
def _get_fullName(self): |
|---|
| 2597 |
raise NotImplementedError |
|---|
| 2598 |
|
|---|
| 2599 |
def _set_fullName(self, value): |
|---|
| 2600 |
raise NotImplementedError |
|---|
| 2601 |
|
|---|
| 2602 |
fullName = property(_get_fullName, _set_fullName, doc="full name") |
|---|
| 2603 |
|
|---|
| 2604 |
def _get_fontName(self): |
|---|
| 2605 |
raise NotImplementedError |
|---|
| 2606 |
|
|---|
| 2607 |
def _set_fontName(self, value): |
|---|
| 2608 |
raise NotImplementedError |
|---|
| 2609 |
|
|---|
| 2610 |
fontName = property(_get_fontName, _set_fontName, doc="font name") |
|---|
| 2611 |
|
|---|
| 2612 |
def _get_menuName(self): |
|---|
| 2613 |
raise NotImplementedError |
|---|
| 2614 |
|
|---|
| 2615 |
def _set_menuName(self, value): |
|---|
| 2616 |
raise NotImplementedError |
|---|
| 2617 |
|
|---|
| 2618 |
menuName = property(_get_menuName, _set_menuName, doc="menu name") |
|---|
| 2619 |
|
|---|
| 2620 |
def _get_fondName(self): |
|---|
| 2621 |
raise NotImplementedError |
|---|
| 2622 |
|
|---|
| 2623 |
def _set_fondName(self, value): |
|---|
| 2624 |
raise NotImplementedError |
|---|
| 2625 |
|
|---|
| 2626 |
fondName = property(_get_fondName, _set_fondName, doc="fond name") |
|---|
| 2627 |
|
|---|
| 2628 |
def _get_otFamilyName(self): |
|---|
| 2629 |
raise NotImplementedError |
|---|
| 2630 |
|
|---|
| 2631 |
def _set_otFamilyName(self, value): |
|---|
| 2632 |
raise NotImplementedError |
|---|
| 2633 |
|
|---|
| 2634 |
otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="OpenType family name") |
|---|
| 2635 |
|
|---|
| 2636 |
def _get_otStyleName(self): |
|---|
| 2637 |
raise NotImplementedError |
|---|
| 2638 |
|
|---|
| 2639 |
def _set_otStyleName(self, value): |
|---|
| 2640 |
raise NotImplementedError |
|---|
| 2641 |
|
|---|
| 2642 |
otStyleName = property(_get_otStyleName, _set_otStyleName, doc="OpenType style name") |
|---|
| 2643 |
|
|---|
| 2644 |
def _get_otMacName(self): |
|---|
| 2645 |
raise NotImplementedError |
|---|
| 2646 |
|
|---|
| 2647 |
def _set_otMacName(self, value): |
|---|
| 2648 |
raise NotImplementedError |
|---|
| 2649 |
|
|---|
| 2650 |
otMacName = property(_get_otMacName, _set_otMacName, doc="Mac specific OpenType name") |
|---|
| 2651 |
|
|---|
| 2652 |
def _get_weightValue(self): |
|---|
| 2653 |
raise NotImplementedError |
|---|
| 2654 |
|
|---|
| 2655 |
def _set_weightValue(self, value): |
|---|
| 2656 |
raise NotImplementedError |
|---|
| 2657 |
|
|---|
| 2658 |
weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") |
|---|
| 2659 |
|
|---|
| 2660 |
def _get_weightName(self): |
|---|
| 2661 |
raise NotImplementedError |
|---|
| 2662 |
|
|---|
| 2663 |
def _set_weightName(self, value): |
|---|
| 2664 |
raise NotImplementedError |
|---|
| 2665 |
|
|---|
| 2666 |
weightName = property(_get_weightName, _set_weightName, doc="weight name") |
|---|
| 2667 |
|
|---|
| 2668 |
def _get_widthName(self): |
|---|
| 2669 |
raise NotImplementedError |
|---|
| 2670 |
|
|---|
| 2671 |
def _set_widthName(self, value): |
|---|
| 2672 |
raise NotImplementedError |
|---|
| 2673 |
|
|---|
| 2674 |
widthName = property(_get_widthName, _set_widthName, doc="width name") |
|---|
| 2675 |
|
|---|
| 2676 |
def _get_fontStyle(self): |
|---|
| 2677 |
raise NotImplementedError |
|---|
| 2678 |
|
|---|
| 2679 |
def _set_fontStyle(self, value): |
|---|
| 2680 |
raise NotImplementedError |
|---|
| 2681 |
|
|---|
| 2682 |
fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font style") |
|---|
| 2683 |
|
|---|
| 2684 |
def _get_msCharSet(self): |
|---|
| 2685 |
raise NotImplementedError |
|---|
| 2686 |
|
|---|
| 2687 |
def _set_msCharSet(self, value): |
|---|
| 2688 |
raise NotImplementedError |
|---|
| 2689 |
|
|---|
| 2690 |
msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms charset") |
|---|
| 2691 |
|
|---|
| 2692 |
def _get_note(self): |
|---|
| 2693 |
raise NotImplementedError |
|---|
| 2694 |
|
|---|
| 2695 |
def _set_note(self, value): |
|---|
| 2696 |
raise NotImplementedError |
|---|
| 2697 |
|
|---|
| 2698 |
note = property(_get_note, _set_note, doc="note") |
|---|
| 2699 |
|
|---|
| 2700 |
def _get_fondID(self): |
|---|
| 2701 |
raise NotImplementedError |
|---|
| 2702 |
|
|---|
| 2703 |
def _set_fondID(self, value): |
|---|
| 2704 |
raise NotImplementedError |
|---|
| 2705 |
|
|---|
| 2706 |
fondID = property(_get_fondID, _set_fondID, doc="fond id number") |
|---|
| 2707 |
|
|---|
| 2708 |
def _get_uniqueID(self): |
|---|
| 2709 |
raise NotImplementedError |
|---|
| 2710 |
|
|---|
| 2711 |
def _set_uniqueID(self, value): |
|---|
| 2712 |
raise NotImplementedError |
|---|
| 2713 |
|
|---|
| 2714 |
uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique id number") |
|---|
| 2715 |
|
|---|
| 2716 |
def _get_versionMajor(self): |
|---|
| 2717 |
raise NotImplementedError |
|---|
| 2718 |
|
|---|
| 2719 |
def _set_versionMajor(self, value): |
|---|
| 2720 |
raise NotImplementedError |
|---|
| 2721 |
|
|---|
| 2722 |
versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version major") |
|---|
| 2723 |
|
|---|
| 2724 |
def _get_versionMinor(self): |
|---|
| 2725 |
raise NotImplementedError |
|---|
| 2726 |
|
|---|
| 2727 |
def _set_versionMinor(self, value): |
|---|
| 2728 |
raise NotImplementedError |
|---|
| 2729 |
|
|---|
| 2730 |
versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version minor") |
|---|
| 2731 |
|
|---|
| 2732 |
def _get_year(self): |
|---|
| 2733 |
raise NotImplementedError |
|---|
| 2734 |
|
|---|
| 2735 |
def _set_year(self, value): |
|---|
| 2736 |
raise NotImplementedError |
|---|
| 2737 |
|
|---|
| 2738 |
year = property(_get_year, _set_year, doc="year") |
|---|
| 2739 |
|
|---|
| 2740 |
def _get_copyright(self): |
|---|
| 2741 |
raise NotImplementedError |
|---|
| 2742 |
|
|---|
| 2743 |
def _set_copyright(self, value): |
|---|
| 2744 |
raise NotImplementedError |
|---|
| 2745 |
|
|---|
| 2746 |
copyright = property(_get_copyright, _set_copyright, doc="copyright") |
|---|
| 2747 |
|
|---|
| 2748 |
def _get_notice(self): |
|---|
| 2749 |
raise NotImplementedError |
|---|
| 2750 |
|
|---|
| 2751 |
def _set_notice(self, value): |
|---|
| 2752 |
raise NotImplementedError |
|---|
| 2753 |
|
|---|
| 2754 |
notice = property(_get_notice, _set_notice, doc="notice") |
|---|
| 2755 |
|
|---|
| 2756 |
def _get_trademark(self): |
|---|
| 2757 |
raise NotImplementedError |
|---|
| 2758 |
|
|---|
| 2759 |
def _set_trademark(self, value): |
|---|
| 2760 |
raise NotImplementedError |
|---|
| 2761 |
|
|---|
| 2762 |
trademark = property(_get_trademark, _set_trademark, doc="trademark") |
|---|
| 2763 |
|
|---|
| 2764 |
def _get_license(self): |
|---|
| 2765 |
raise NotImplementedError |
|---|
| 2766 |
|
|---|
| 2767 |
def _set_license(self, value): |
|---|
| 2768 |
raise NotImplementedError |
|---|
| 2769 |
|
|---|
| 2770 |
license = property(_get_license, _set_license, doc="license") |
|---|
| 2771 |
|
|---|
| 2772 |
def _get_licenseURL(self): |
|---|
| 2773 |
raise NotImplementedError |
|---|
| 2774 |
|
|---|
| 2775 |
def _set_licenseURL(self, value): |
|---|
| 2776 |
raise NotImplementedError |
|---|
| 2777 |
|
|---|
| 2778 |
licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license url") |
|---|
| 2779 |
|
|---|
| 2780 |
def _get_createdBy(self): |
|---|
| 2781 |
raise NotImplementedError |
|---|
| 2782 |
|
|---|
| 2783 |
def _set_createdBy(self, value): |
|---|
| 2784 |
raise NotImplementedError |
|---|
| 2785 |
|
|---|
| 2786 |
createdBy = property(_get_createdBy, _set_createdBy, doc="source") |
|---|
| 2787 |
|
|---|
| 2788 |
def _get_designer(self): |
|---|
| 2789 |
raise NotImplementedError |
|---|
| 2790 |
|
|---|
| 2791 |
def _set_designer(self, value): |
|---|
| 2792 |
raise NotImplementedError |
|---|
| 2793 |
|
|---|
| 2794 |
designer = property(_get_designer, _set_designer, doc="designer") |
|---|
| 2795 |
|
|---|
| 2796 |
def _get_designerURL(self): |
|---|
| 2797 |
raise NotImplementedError |
|---|
| 2798 |
|
|---|
| 2799 |
def _set_designerURL(self, value): |
|---|
| 2800 |
raise NotImplementedError |
|---|
| 2801 |
|
|---|
| 2802 |
designerURL = property(_get_designerURL, _set_designerURL, doc="designer url") |
|---|
| 2803 |
|
|---|
| 2804 |
def _get_vendorURL(self): |
|---|
| 2805 |
raise NotImplementedError |
|---|
| 2806 |
|
|---|
| 2807 |
def _set_vendorURL(self, value): |
|---|
| 2808 |
raise NotImplementedError |
|---|
| 2809 |
|
|---|
| 2810 |
vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor url") |
|---|
| 2811 |
|
|---|
| 2812 |
def _get_ttVendor(self): |
|---|
| 2813 |
raise NotImplementedError |
|---|
| 2814 |
|
|---|
| 2815 |
def _set_ttVendor(self, value): |
|---|
| 2816 |
raise NotImplementedError |
|---|
| 2817 |
|
|---|
| 2818 |
ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") |
|---|
| 2819 |
|
|---|
| 2820 |
def _get_ttUniqueID(self): |
|---|
| 2821 |
raise NotImplementedError |
|---|
| 2822 |
|
|---|
| 2823 |
def _set_ttUniqueID(self, value): |
|---|
| 2824 |
raise NotImplementedError |
|---|
| 2825 |
|
|---|
| 2826 |
ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="TrueType unique id number") |
|---|
| 2827 |
|
|---|
| 2828 |
def _get_ttVersion(self): |
|---|
| 2829 |
raise NotImplementedError |
|---|
| 2830 |
|
|---|
| 2831 |
def _set_ttVersion(self, value): |
|---|
| 2832 |
raise NotImplementedError |
|---|
| 2833 |
|
|---|
| 2834 |
ttVersion = property(_get_ttVersion, _set_ttVersion, doc="TrueType version") |
|---|
| 2835 |
|
|---|
| 2836 |
def _get_unitsPerEm(self): |
|---|
| 2837 |
raise NotImplementedError |
|---|
| 2838 |
|
|---|
| 2839 |
def _set_unitsPerEm(self): |
|---|
| 2840 |
raise NotImplementedError |
|---|
| 2841 |
|
|---|
| 2842 |
unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value") |
|---|
| 2843 |
|
|---|
| 2844 |
def _get_ascender(self): |
|---|
| 2845 |
raise NotImplementedError |
|---|
| 2846 |
|
|---|
| 2847 |
def _set_ascender(self, value): |
|---|
| 2848 |
raise NotImplementedError |
|---|
| 2849 |
|
|---|
| 2850 |
ascender = property(_get_ascender, _set_ascender, doc="ascender value") |
|---|
| 2851 |
|
|---|
| 2852 |
def _get_descender(self): |
|---|
| 2853 |
raise NotImplementedError |
|---|
| 2854 |
|
|---|
| 2855 |
def _set_descender(self, value): |
|---|
| 2856 |
raise NotImplementedError |
|---|
| 2857 |
|
|---|
| 2858 |
descender = property(_get_descender, _set_descender, doc="descender value") |
|---|
| 2859 |
|
|---|
| 2860 |
def _get_capHeight(self): |
|---|
| 2861 |
raise NotImplementedError |
|---|
| 2862 |
|
|---|
| 2863 |
def _set_capHeight(self, value): |
|---|
| 2864 |
raise NotImplementedError |
|---|
| 2865 |
|
|---|
| 2866 |
capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") |
|---|
| 2867 |
|
|---|
| 2868 |
def _get_xHeight(self): |
|---|
| 2869 |
raise NotImplementedError |
|---|
| 2870 |
|
|---|
| 2871 |
def _set_xHeight(self, value): |
|---|
| 2872 |
raise NotImplementedError |
|---|
| 2873 |
|
|---|
| 2874 |
xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") |
|---|
| 2875 |
|
|---|
| 2876 |
def _get_defaultWidth(self): |
|---|
| 2877 |
raise NotImplementedError |
|---|
| 2878 |
|
|---|
| 2879 |
def _set_defaultWidth(self, value): |
|---|
| 2880 |
raise NotImplementedError |
|---|
| 2881 |
|
|---|
| 2882 |
defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") |
|---|
| 2883 |
|
|---|
| 2884 |
def _get_italicAngle(self): |
|---|
| 2885 |
raise NotImplementedError |
|---|
| 2886 |
|
|---|
| 2887 |
def _set_italicAngle(self, value): |
|---|
| 2888 |
raise NotImplementedError |
|---|
| 2889 |
|
|---|
| 2890 |
italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") |
|---|
| 2891 |
|
|---|
| 2892 |
def _get_slantAngle(self): |
|---|
| 2893 |
raise NotImplementedError |
|---|
| 2894 |
|
|---|
| 2895 |
def _set_slantAngle(self, value): |
|---|
| 2896 |
raise NotImplementedError |
|---|
| 2897 |
|
|---|
| 2898 |
slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") |
|---|
| 2899 |
|
|---|
| 2900 |
def autoNaming(self, familyName=None, styleName=None): |
|---|
| 2901 |
"""Automatically set the font naming info based on family and style names.""" |
|---|
| 2902 |
|
|---|
| 2903 |
if familyName is None: |
|---|
| 2904 |
if not self.familyName: |
|---|
| 2905 |
raise RoboFabError, "Family name and style name must be complete" |
|---|
| 2906 |
else: |
|---|
| 2907 |
self.familyName = familyName |
|---|
| 2908 |
if styleName is None: |
|---|
| 2909 |
if not self.styleName: |
|---|
| 2910 |
raise RoboFabError, "Family name and style name must be complete" |
|---|
| 2911 |
else: |
|---|
| 2912 |
self.styleName = styleName |
|---|
| 2913 |
family = self.familyName |
|---|
| 2914 |
style = self.styleName |
|---|
| 2915 |
self.fullName = ' '.join((family, style)) |
|---|
| 2916 |
self.fontName = '-'.join((family, style)).replace(' ', '') |
|---|
| 2917 |
self.fondName = family |
|---|
| 2918 |
self.menuName = ' '.join((family, style)) |
|---|
| 2919 |
self.otFamilyName = family |
|---|
| 2920 |
self.otStyleName = style |
|---|
| 2921 |
self.otMacName = ' '.join((family, style)) |
|---|
| 2922 |
|
|---|
| 2923 |
|
|---|
| 2924 |
class BaseGroups(dict): |
|---|
| 2925 |
|
|---|
| 2926 |
"""Base class for all RFont.groups objects""" |
|---|
| 2927 |
|
|---|
| 2928 |
def __init__(self): |
|---|
| 2929 |
pass |
|---|
| 2930 |
|
|---|
| 2931 |
def __repr__(self): |
|---|
| 2932 |
font = "unnamed_font" |
|---|
| 2933 |
fontParent = self.getParent() |
|---|
| 2934 |
if fontParent is not None: |
|---|
| 2935 |
try: |
|---|
| 2936 |
font = fontParent.info.fullName |
|---|
| 2937 |
except AttributeError: pass |
|---|
| 2938 |
return "<RGroups for %s>"%font |
|---|
| 2939 |
|
|---|
| 2940 |
def getParent(self): |
|---|
| 2941 |
"""this method will be overwritten with a weakref if there is a parent.""" |
|---|
| 2942 |
pass |
|---|
| 2943 |
|
|---|
| 2944 |
def setParent(self, parent): |
|---|
| 2945 |
import weakref |
|---|
| 2946 |
self.__dict__['getParent'] = weakref.ref(parent) |
|---|
| 2947 |
|
|---|
| 2948 |
def __setitem__(self, key, value): |
|---|
| 2949 |
|
|---|
| 2950 |
if not isinstance(key, str): |
|---|
| 2951 |
raise RoboFabError, 'key must be a string' |
|---|
| 2952 |
if not isinstance(value, list): |
|---|
| 2953 |
raise RoboFabError, 'group must be a list' |
|---|
| 2954 |
super(BaseGroups, self).__setitem__(key, value) |
|---|
| 2955 |
|
|---|
| 2956 |
def findGlyph(self, glyphName): |
|---|
| 2957 |
"""return a list of all groups contianing glyphName""" |
|---|
| 2958 |
found = [] |
|---|
| 2959 |
for i in self.keys(): |
|---|
| 2960 |
l = self[i] |
|---|
| 2961 |
if glyphName in l: |
|---|
| 2962 |
found.append(i) |
|---|
| 2963 |
return found |
|---|
| 2964 |
|
|---|
| 2965 |
|
|---|
| 2966 |
class BaseLib(dict): |
|---|
| 2967 |
|
|---|
| 2968 |
"""Base class for all lib objects""" |
|---|
| 2969 |
|
|---|
| 2970 |
def __init__(self): |
|---|
| 2971 |
pass |
|---|
| 2972 |
|
|---|
| 2973 |
def __repr__(self): |
|---|
| 2974 |
|
|---|
| 2975 |
parent = "unknown_parent" |
|---|
| 2976 |
parentObject = self.getParent() |
|---|
| 2977 |
if parentObject is not None: |
|---|
| 2978 |
|
|---|
| 2979 |
try: |
|---|
| 2980 |
parent = parentObject.info.fullName |
|---|
| 2981 |
except AttributeError: |
|---|
| 2982 |
|
|---|
| 2983 |
try: |
|---|
| 2984 |
parent = parentObject.name |
|---|
| 2985 |
|
|---|
| 2986 |
except AttributeError: pass |
|---|
| 2987 |
return "<RLib for %s>"%parent |
|---|
| 2988 |
|
|---|
| 2989 |
def getParent(self): |
|---|
| 2990 |
"""this method will be overwritten with a weakref if there is a parent.""" |
|---|
| 2991 |
pass |
|---|
| 2992 |
|
|---|
| 2993 |
def setParent(self, parent): |
|---|
| 2994 |
import weakref |
|---|
| 2995 |
self.__dict__['getParent'] = weakref.ref(parent) |
|---|
| 2996 |
|
|---|
| 2997 |
def copy(self, aParent=None): |
|---|
| 2998 |
"""Duplicate this lib.""" |
|---|
| 2999 |
n = self.__class__() |
|---|
| 3000 |
if aParent is not None: |
|---|
| 3001 |
n.setParent(aParent) |
|---|
| 3002 |
elif self.getParent() is not None: |
|---|
| 3003 |
n.setParent(self.getParent()) |
|---|
| 3004 |
for k in self.keys(): |
|---|
| 3005 |
n[k] = copy.deepcopy(self[k]) |
|---|
| 3006 |
return n |
|---|
| 3007 |
|
|---|
| 3008 |
|
|---|
| 3009 |
class BaseKerning(RBaseObject): |
|---|
| 3010 |
|
|---|
| 3011 |
"""Base class for all kerning objects. Object behaves like a dict but has |
|---|
| 3012 |
some special kerning specific tricks.""" |
|---|
| 3013 |
|
|---|
| 3014 |
def __init__(self, kerningDict=None): |
|---|
| 3015 |
if not kerningDict: |
|---|
| 3016 |
kerningDict = {} |
|---|
| 3017 |
self._kerning = kerningDict |
|---|
| 3018 |
self.changed = False |
|---|
| 3019 |
|
|---|
| 3020 |
def __repr__(self): |
|---|
| 3021 |
font = "unnamed_font" |
|---|
| 3022 |
fontParent = self.getParent() |
|---|
| 3023 |
if fontParent is not None: |
|---|
| 3024 |
try: |
|---|
| 3025 |
font = fontParent.info.fullName |
|---|
| 3026 |
except AttributeError: pass |
|---|
| 3027 |
return "<RKerning for %s>"%font |
|---|
| 3028 |
|
|---|
| 3029 |
def __getitem__(self, key): |
|---|
| 3030 |
if isinstance(key, tuple): |
|---|
| 3031 |
pair = key |
|---|
| 3032 |
return self.get(pair) |
|---|
| 3033 |
elif isinstance(key, str): |
|---|
| 3034 |
raise RoboFabError, 'kerning pair must be a tuple: (left, right)' |
|---|
| 3035 |
else: |
|---|
| 3036 |
keys = self.keys() |
|---|
| 3037 |
if key > len(keys): |
|---|
| 3038 |
raise IndexError |
|---|
| 3039 |
keys.sort() |
|---|
| 3040 |
pair = keys[key] |
|---|
| 3041 |
if not self._kerning.has_key(pair): |
|---|
| 3042 |
raise IndexError |
|---|
| 3043 |
else: |
|---|
| 3044 |
return pair |
|---|
| 3045 |
|
|---|
| 3046 |
def __setitem__(self, pair, value): |
|---|
| 3047 |
if not isinstance(pair, tuple): |
|---|
| 3048 |
raise RoboFabError, 'kerning pair must be a tuple: (left, right)' |
|---|
| 3049 |
else: |
|---|
| 3050 |
if len(pair) != 2: |
|---|
| 3051 |
raise RoboFabError, 'kerning pair must be a tuple: (left, right)' |
|---|
| 3052 |
else: |
|---|
| 3053 |
if value == 0: |
|---|
| 3054 |
if self._kerning.get(pair) is not None: |
|---|
| 3055 |
del self._kerning[pair] |
|---|
| 3056 |
else: |
|---|
| 3057 |
self._kerning[pair] = value |
|---|
| 3058 |
self._hasChanged() |
|---|
| 3059 |
|
|---|
| 3060 |
def __len__(self): |
|---|
| 3061 |
return len(self._kerning.keys()) |
|---|
| 3062 |
|
|---|
| 3063 |
def _hasChanged(self): |
|---|
| 3064 |
"""mark the object and it's parent as changed""" |
|---|
| 3065 |
self.setChanged(True) |
|---|
| 3066 |
if self.getParent() is not None: |
|---|
| 3067 |
self.getParent()._hasChanged() |
|---|
| 3068 |
|
|---|
| 3069 |
def keys(self): |
|---|
| 3070 |
"""return list of kerning pairs""" |
|---|
| 3071 |
return self._kerning.keys() |
|---|
| 3072 |
|
|---|
| 3073 |
def values(self): |
|---|
| 3074 |
"""return a list of kerning values""" |
|---|
| 3075 |
return self._kerning.values() |
|---|
| 3076 |
|
|---|
| 3077 |
def items(self): |
|---|
| 3078 |
"""return a list of kerning items""" |
|---|
| 3079 |
return self._kerning.items() |
|---|
| 3080 |
|
|---|
| 3081 |
def has_key(self, pair): |
|---|
| 3082 |
return self._kerning.has_key(pair) |
|---|
| 3083 |
|
|---|
| 3084 |
def get(self, pair, default=None): |
|---|
| 3085 |
"""get a value. return None if the pair does not exist""" |
|---|
| 3086 |
value = self._kerning.get(pair, default) |
|---|
| 3087 |
return value |
|---|
| 3088 |
|
|---|
| 3089 |
def remove(self, pair): |
|---|
| 3090 |
"""remove a kerning pair""" |
|---|
| 3091 |
self[pair] = 0 |
|---|
| 3092 |
|
|---|
| 3093 |
def getAverage(self): |
|---|
| 3094 |
"""return average of all kerning pairs""" |
|---|
| 3095 |
if len(self) == 0: |
|---|
| 3096 |
return 0 |
|---|
| 3097 |
value = 0 |
|---|
| 3098 |
for i in self.values(): |
|---|
| 3099 |
value = value + i |
|---|
| 3100 |
return value / float(len(self)) |
|---|
| 3101 |
|
|---|
| 3102 |
def getExtremes(self): |
|---|
| 3103 |
"""return the lowest and highest kerning values""" |
|---|
| 3104 |
if len(self) == 0: |
|---|
| 3105 |
return 0 |
|---|
| 3106 |
values = self.values() |
|---|
| 3107 |
values.append(0) |
|---|
| 3108 |
values.sort() |
|---|
| 3109 |
return (values[0], values[-1]) |
|---|
| 3110 |
|
|---|
| 3111 |
def update(self, kerningDict): |
|---|
| 3112 |
"""replace kerning data with the data in the given kerningDict""" |
|---|
| 3113 |
for pair in kerningDict.keys(): |
|---|
| 3114 |
self[pair] = kerningDict[pair] |
|---|
| 3115 |
|
|---|
| 3116 |
def clear(self): |
|---|
| 3117 |
"""clear all kerning""" |
|---|
| 3118 |
self._kerning = {} |
|---|
| 3119 |
|
|---|
| 3120 |
def add(self, value): |
|---|
| 3121 |
"""add value to all kerning pairs""" |
|---|
| 3122 |
for pair in self.keys(): |
|---|
| 3123 |
self[pair] = self[pair] + value |
|---|
| 3124 |
|
|---|
| 3125 |
def scale(self, value): |
|---|
| 3126 |
"""scale all kernng pairs by value""" |
|---|
| 3127 |
for pair in self.keys(): |
|---|
| 3128 |
self[pair] = self[pair] * value |
|---|
| 3129 |
|
|---|
| 3130 |
def minimize(self, minimum=10): |
|---|
| 3131 |
"""eliminate pairs with value less than minimum""" |
|---|
| 3132 |
for pair in self.keys(): |
|---|
| 3133 |
if abs(self[pair]) < minimum: |
|---|
| 3134 |
self[pair] = 0 |
|---|
| 3135 |
|
|---|
| 3136 |
def eliminate(self, leftGlyphsToEliminate=None, rightGlyphsToEliminate=None, analyzeOnly=False): |
|---|
| 3137 |
"""eliminate pairs containing a left glyph that is in the leftGlyphsToEliminate list |
|---|
| 3138 |
or a right glyph that is in the rightGlyphsToELiminate list. |
|---|
| 3139 |
sideGlyphsToEliminate can be a string: 'a' or list: ['a', 'b']. |
|---|
| 3140 |
analyzeOnly will not remove pairs. it will return a count |
|---|
| 3141 |
of all pairs that would be removed.""" |
|---|
| 3142 |
if analyzeOnly: |
|---|
| 3143 |
count = 0 |
|---|
| 3144 |
lgte = leftGlyphsToEliminate |
|---|
| 3145 |
rgte = rightGlyphsToEliminate |
|---|
| 3146 |
if isinstance(lgte, str): |
|---|
| 3147 |
lgte = [lgte] |
|---|
| 3148 |
if isinstance(rgte, str): |
|---|
| 3149 |
rgte = [rgte] |
|---|
| 3150 |
for pair in self.keys(): |
|---|
| 3151 |
left, right = pair |
|---|
| 3152 |
if left in lgte or right in rgte: |
|---|
| 3153 |
if analyzeOnly: |
|---|
| 3154 |
count = count + 1 |
|---|
| 3155 |
else: |
|---|
| 3156 |
self[pair] = 0 |
|---|
| 3157 |
if analyzeOnly: |
|---|
| 3158 |
return count |
|---|
| 3159 |
else: |
|---|
| 3160 |
return None |
|---|
| 3161 |
|
|---|
| 3162 |
def interpolate(self, sourceDictOne, sourceDictTwo, value, clearExisting=True): |
|---|
| 3163 |
"""interpolate the kerning between sourceDictOne |
|---|
| 3164 |
and sourceDictTwo. clearExisting will clear existing |
|---|
| 3165 |
kerning first.""" |
|---|
| 3166 |
from sets import Set |
|---|
| 3167 |
if isinstance(value, tuple): |
|---|
| 3168 |
|
|---|
| 3169 |
value = value[0] |
|---|
| 3170 |
if clearExisting: |
|---|
| 3171 |
self.clear() |
|---|
| 3172 |
pairs = Set(sourceDictOne.keys()) | Set(sourceDictTwo.keys()) |
|---|
| 3173 |
for pair in pairs: |
|---|
| 3174 |
s1 = sourceDictOne.get(pair, 0) |
|---|
| 3175 |
s2 = sourceDictTwo.get(pair, 0) |
|---|
| 3176 |
self[pair] = _interpolate(s1, s2, value) |
|---|
| 3177 |
|
|---|
| 3178 |
def round(self, multiple=10): |
|---|
| 3179 |
"""round the kerning pair values to increments of multiple""" |
|---|
| 3180 |
for pair in self.keys(): |
|---|
| 3181 |
value = self[pair] |
|---|
| 3182 |
self[pair] = int(round(value / float(multiple))) * multiple |
|---|
| 3183 |
|
|---|
| 3184 |
def occurrenceCount(self, glyphsToCount): |
|---|
| 3185 |
"""return a dict with glyphs as keys and the number of |
|---|
| 3186 |
occurances of that glyph in the kerning pairs as the value |
|---|
| 3187 |
glyphsToCount can be a string: 'a' or list: ['a', 'b']""" |
|---|
| 3188 |
gtc = glyphsToCount |
|---|
| 3189 |
if isinstance(gtc, str): |
|---|
| 3190 |
gtc = [gtc] |
|---|
| 3191 |
gtcDict = {} |
|---|
| 3192 |
for glyph in gtc: |
|---|
| 3193 |
gtcDict[glyph] = 0 |
|---|
| 3194 |
for pair in self.keys(): |
|---|
| 3195 |
left, right = pair |
|---|
| 3196 |
if not gtcDict.get(left): |
|---|
| 3197 |
gtcDict[left] = 0 |
|---|
| 3198 |
if not gtcDict.get(right): |
|---|
| 3199 |
gtcDict[right] = 0 |
|---|
| 3200 |
gtcDict[left] = gtcDict[left] + 1 |
|---|
| 3201 |
gtcDict[right] = gtcDict[right] + 1 |
|---|
| 3202 |
found = {} |
|---|
| 3203 |
for glyphName in gtc: |
|---|
| 3204 |
found[glyphName] = gtcDict[glyphName] |
|---|
| 3205 |
return found |
|---|
| 3206 |
|
|---|
| 3207 |
def getLeft(self, glyphName): |
|---|
| 3208 |
"""Return a list of kerns with glyphName as left character.""" |
|---|
| 3209 |
hits = [] |
|---|
| 3210 |
for k, v in self.items(): |
|---|
| 3211 |
if k[0] == glyphName: |
|---|
| 3212 |
hits.append((k, v)) |
|---|
| 3213 |
return hits |
|---|
| 3214 |
|
|---|
| 3215 |
def getRight(self, glyphName): |
|---|
| 3216 |
"""Return a list of kerns with glyphName as left character.""" |
|---|
| 3217 |
hits = [] |
|---|
| 3218 |
for k, v in self.items(): |
|---|
| 3219 |
if k[1] == glyphName: |
|---|
| 3220 |
hits.append((k, v)) |
|---|
| 3221 |
return hits |
|---|
| 3222 |
|
|---|
| 3223 |
def combine(self, kerningDicts, overwriteExisting=True): |
|---|
| 3224 |
"""combine two or more kerning dictionaries. |
|---|
| 3225 |
overwrite exsisting duplicate pairs if overwriteExisting=True""" |
|---|
| 3226 |
if isinstance(kerningDicts, dict): |
|---|
| 3227 |
kerningDicts = [kerningDicts] |
|---|
| 3228 |
for kd in kerningDicts: |
|---|
| 3229 |
for pair in kd.keys(): |
|---|
| 3230 |
exists = self.has_key(pair) |
|---|
| 3231 |
if exists and overwriteExisting: |
|---|
| 3232 |
self[pair] = kd[pair] |
|---|
| 3233 |
elif not exists: |
|---|
| 3234 |
self[pair] = kd[pair] |
|---|
| 3235 |
|
|---|
| 3236 |
def swapNames(self, swapTable): |
|---|
| 3237 |
"""change glyph names in all kerning pairs based on swapTable. |
|---|
| 3238 |
swapTable = {'BeforeName':'AfterName', ...}""" |
|---|
| 3239 |
for pair in self.keys(): |
|---|
| 3240 |
foundInstance = False |
|---|
| 3241 |
left, right = pair |
|---|
| 3242 |
if swapTable.has_key(left): |
|---|
| 3243 |
left = swapTable[left] |
|---|
| 3244 |
foundInstance = True |
|---|
| 3245 |
if swapTable.has_key(right): |
|---|
| 3246 |
right = swapTable[right] |
|---|
| 3247 |
foundInstance = True |
|---|
| 3248 |
if foundInstance: |
|---|
| 3249 |
self[(left, right)] = self[pair] |
|---|
| 3250 |
self[pair] = 0 |
|---|
| 3251 |
|
|---|
| 3252 |
def explodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False): |
|---|
| 3253 |
"""turn class kerns into real kerning pairs. classes should |
|---|
| 3254 |
be defined in dicts: {'O':['C', 'G', 'Q'], 'H':['B', 'D', 'E', 'F', 'I']}. |
|---|
| 3255 |
analyzeOnly will not remove pairs. it will return a count |
|---|
| 3256 |
of all pairs that would be added""" |
|---|
| 3257 |
if not leftClassDict: |
|---|
| 3258 |
leftClassDict = {} |
|---|
| 3259 |
if not rightClassDict: |
|---|
| 3260 |
rightClassDict = {} |
|---|
| 3261 |
if analyzeOnly: |
|---|
| 3262 |
count = 0 |
|---|
| 3263 |
for pair in self.keys(): |
|---|
| 3264 |
left, right = pair |
|---|
| 3265 |
value = self[pair] |
|---|
| 3266 |
if leftClassDict.get(left) and rightClassDict.get(right): |
|---|
| 3267 |
allLeft = leftClassDict[left] + [left] |
|---|
| 3268 |
allRight = rightClassDict[right] + [right] |
|---|
| 3269 |
for leftSub in allLeft: |
|---|
| 3270 |
for rightSub in allRight: |
|---|
| 3271 |
if analyzeOnly: |
|---|
| 3272 |
count = count + 1 |
|---|
| 3273 |
else: |
|---|
| 3274 |
self[(leftSub, rightSub)] = value |
|---|
| 3275 |
elif leftClassDict.get(left) and not rightClassDict.get(right): |
|---|
| 3276 |
allLeft = leftClassDict[left] + [left] |
|---|
| 3277 |
for leftSub in allLeft: |
|---|
| 3278 |
if analyzeOnly: |
|---|
| 3279 |
count = count + 1 |
|---|
| 3280 |
else: |
|---|
| 3281 |
self[(leftSub, right)] = value |
|---|
| 3282 |
elif rightClassDict.get(right) and not leftClassDict.get(left): |
|---|
| 3283 |
allRight = rightClassDict[right] + [right] |
|---|
| 3284 |
for rightSub in allRight: |
|---|
| 3285 |
if analyzeOnly: |
|---|
| 3286 |
count = count + 1 |
|---|
| 3287 |
else: |
|---|
| 3288 |
self[(left, rightSub)] = value |
|---|
| 3289 |
if analyzeOnly: |
|---|
| 3290 |
return count |
|---|
| 3291 |
else: |
|---|
| 3292 |
return None |
|---|
| 3293 |
|
|---|
| 3294 |
def implodeClasses(self, leftClassDict=None, rightClassDict=None, analyzeOnly=False): |
|---|
| 3295 |
"""condense the number of kerning pairs by applying classes. |
|---|
| 3296 |
this will eliminate all pairs containg the classed glyphs leaving |
|---|
| 3297 |
pairs that contain the key glyphs behind. analyzeOnly will not |
|---|
| 3298 |
remove pairs. it will return a count of all pairs that would be removed.""" |
|---|
| 3299 |
if not leftClassDict: |
|---|
| 3300 |
leftClassDict = {} |
|---|
| 3301 |
if not rightClassDict: |
|---|
| 3302 |
rightClassDict = {} |
|---|
| 3303 |
leftImplode = [] |
|---|
| 3304 |
rightImplode = [] |
|---|
| 3305 |
for value in leftClassDict.values(): |
|---|
| 3306 |
leftImplode = leftImplode + value |
|---|
| 3307 |
for value in rightClassDict.values(): |
|---|
| 3308 |
rightImplode = rightImplode + value |
|---|
| 3309 |
analyzed = self.eliminate(leftGlyphsToEliminate=leftImplode, rightGlyphsToEliminate=rightImplode, analyzeOnly=analyzeOnly) |
|---|
| 3310 |
if analyzeOnly: |
|---|
| 3311 |
return analyzed |
|---|
| 3312 |
else: |
|---|
| 3313 |
return None |
|---|
| 3314 |
|
|---|
| 3315 |
def importAFM(self, path, clearExisting=True): |
|---|
| 3316 |
"""Import kerning pairs from an AFM file. clearExisting=True will |
|---|
| 3317 |
clear all exising kerning""" |
|---|
| 3318 |
from fontTools.afmLib import AFM |
|---|
| 3319 |
|
|---|
| 3320 |
f = open(path, 'rb') |
|---|
| 3321 |
text = f.read().replace('\r', '\n') |
|---|
| 3322 |
f.close() |
|---|
| 3323 |
f = open(path, 'wb') |
|---|
| 3324 |
f.write(text) |
|---|
| 3325 |
f.close() |
|---|
| 3326 |
|
|---|
| 3327 |
kerning = AFM(path)._kerning |
|---|
| 3328 |
if clearExisting: |
|---|
| 3329 |
self.clear() |
|---|
| 3330 |
for pair in kerning.keys(): |
|---|
| 3331 |
self[pair] = kerning[pair] |
|---|
| 3332 |
|
|---|
| 3333 |
def asDict(self, returnIntegers=True): |
|---|
| 3334 |
"""return the object as a dictionary""" |
|---|
| 3335 |
if not returnIntegers: |
|---|
| 3336 |
return self._kerning |
|---|
| 3337 |
else: |
|---|
| 3338 |
|
|---|
| 3339 |
kerning = {} |
|---|
| 3340 |
for pair in self.keys(): |
|---|
| 3341 |
kerning[pair] = int(round(self[pair])) |
|---|
| 3342 |
return kerning |
|---|
| 3343 |
|
|---|
| 3344 |
def __add__(self, other): |
|---|
| 3345 |
from sets import Set |
|---|
| 3346 |
new = self.__class__() |
|---|
| 3347 |
k = Set(self.keys()) | Set(other.keys()) |
|---|
| 3348 |
for key in k: |
|---|
| 3349 |
new[key] = self.get(key, 0) + other.get(key, 0) |
|---|
| 3350 |
return new |
|---|
| 3351 |
|
|---|
| 3352 |
def __sub__(self, other): |
|---|
| 3353 |
from sets import Set |
|---|
| 3354 |
new = self.__class__() |
|---|
| 3355 |
k = Set(self.keys()) | Set(other.keys()) |
|---|
| 3356 |
for key in k: |
|---|
| 3357 |
new[key] = self.get(key, 0) - other.get(key, 0) |
|---|
| 3358 |
return new |
|---|
| 3359 |
|
|---|
| 3360 |
def __mul__(self, factor): |
|---|
| 3361 |
new = self.__class__() |
|---|
| 3362 |
for name, value in self.items(): |
|---|
| 3363 |
new[name] = value * factor |
|---|
| 3364 |
return new |
|---|
| 3365 |
|
|---|
| 3366 |
__rmul__ = __mul__ |
|---|
| 3367 |
|
|---|
| 3368 |
def __div__(self, factor): |
|---|
| 3369 |
if factor == 0: |
|---|
| 3370 |
raise ZeroDivisionError |
|---|
| 3371 |
return self.__mul__(1.0/factor) |
|---|
| 3372 |
|
|---|
| 3373 |
|
|---|