| 1 | """UFO implementation for the objects as used by FontLab 4.5 and higher""" |
|---|
| 2 | |
|---|
| 3 | from FL import * |
|---|
| 4 | |
|---|
| 5 | from robofab.tools.toolsFL import GlyphIndexTable,\ |
|---|
| 6 | AllFonts, NewGlyph |
|---|
| 7 | from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ |
|---|
| 8 | BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseGroups, BaseLib,\ |
|---|
| 9 | roundPt, addPt, _box,\ |
|---|
| 10 | MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ |
|---|
| 11 | relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut,\ |
|---|
| 12 | BasePostScriptFontHintValues, postScriptHintDataLibKey |
|---|
| 13 | from fontTools.misc import arrayTools |
|---|
| 14 | from robofab.pens.flPen import FLPointPen |
|---|
| 15 | from robofab import RoboFabError |
|---|
| 16 | import os |
|---|
| 17 | from robofab.plistlib import Data, Dict, readPlist, writePlist |
|---|
| 18 | from StringIO import StringIO |
|---|
| 19 | |
|---|
| 20 | # local encoding |
|---|
| 21 | if os.name in ["mac", "posix"]: |
|---|
| 22 | LOCAL_ENCODING = "macroman" |
|---|
| 23 | else: |
|---|
| 24 | LOCAL_ENCODING = "latin-1" |
|---|
| 25 | |
|---|
| 26 | # a list of attributes that are to be copied when copying a glyph. |
|---|
| 27 | # this is used by glyph.copy and font.insertGlyph |
|---|
| 28 | GLYPH_COPY_ATTRS = [ |
|---|
| 29 | "name", |
|---|
| 30 | "width", |
|---|
| 31 | "unicodes", |
|---|
| 32 | "note", |
|---|
| 33 | "lib", |
|---|
| 34 | ] |
|---|
| 35 | |
|---|
| 36 | # Generate Types |
|---|
| 37 | PC_TYPE1 = 'pctype1' |
|---|
| 38 | PC_MM = 'pcmm' |
|---|
| 39 | PC_TYPE1_ASCII = 'pctype1ascii' |
|---|
| 40 | PC_MM_ASCII = 'pcmmascii' |
|---|
| 41 | UNIX_ASCII = 'unixascii' |
|---|
| 42 | MAC_TYPE1 = 'mactype1' |
|---|
| 43 | OTF_CFF = 'otfcff' |
|---|
| 44 | OTF_TT = 'otfttf' |
|---|
| 45 | MAC_TT = 'macttf' |
|---|
| 46 | MAC_TT_DFONT = 'macttdfont' |
|---|
| 47 | |
|---|
| 48 | # doc for these functions taken from: http://dev.fontlab.net/flpydoc/ |
|---|
| 49 | # internal name (FontLab name, extension) |
|---|
| 50 | _flGenerateTypes ={ PC_TYPE1 : (ftTYPE1, 'pfb'), # PC Type 1 font (binary/PFB) |
|---|
| 51 | PC_MM : (ftTYPE1_MM, 'mm'), # PC MultipleMaster font (PFB) |
|---|
| 52 | PC_TYPE1_ASCII : (ftTYPE1ASCII, 'pfa'), # PC Type 1 font (ASCII/PFA) |
|---|
| 53 | PC_MM_ASCII : (ftTYPE1ASCII_MM, 'mm'), # PC MultipleMaster font (ASCII/PFA) |
|---|
| 54 | UNIX_ASCII : (ftTYPE1ASCII, 'pfa'), # UNIX ASCII font (ASCII/PFA) |
|---|
| 55 | OTF_TT : (ftTRUETYPE, 'ttf'), # PC TrueType/TT OpenType font (TTF) |
|---|
| 56 | OTF_CFF : (ftOPENTYPE, 'otf'), # PS OpenType (CFF-based) font (OTF) |
|---|
| 57 | MAC_TYPE1 : (ftMACTYPE1, 'suit'), # Mac Type 1 font (generates suitcase and LWFN file, optionally AFM) |
|---|
| 58 | MAC_TT : (ftMACTRUETYPE, 'ttf'), # Mac TrueType font (generates suitcase) |
|---|
| 59 | MAC_TT_DFONT : (ftMACTRUETYPE_DFONT, 'dfont'), # Mac TrueType font (generates suitcase with resources in data fork) |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | ## FL Hint stuff |
|---|
| 63 | # this should not be referenced outside of this module |
|---|
| 64 | # since we may be changing the way this works in the future. |
|---|
| 65 | |
|---|
| 66 | |
|---|
| 67 | """ |
|---|
| 68 | |
|---|
| 69 | FontLab implementation of psHints object |
|---|
| 70 | |
|---|
| 71 | Most of the FL methods relating to ps hints return a list of 16 items. |
|---|
| 72 | These values are for the 16 corners of a 4 axis multiple master. |
|---|
| 73 | The odd thing is that even single masters get these 16 values. |
|---|
| 74 | RoboFab doesn't access the MM masters, so by default, the psHints |
|---|
| 75 | object only works with the first element. If you want to access the other |
|---|
| 76 | values in the list, give a value between 0 and 15 for impliedMasterIndex |
|---|
| 77 | when creating the object. |
|---|
| 78 | |
|---|
| 79 | From the FontLab docs: |
|---|
| 80 | http://dev.fontlab.net/flpydoc/ |
|---|
| 81 | |
|---|
| 82 | blue_fuzz |
|---|
| 83 | blue_scale |
|---|
| 84 | blue_shift |
|---|
| 85 | |
|---|
| 86 | blue_values_num(integer) - number of defined blue values |
|---|
| 87 | blue_values[integer[integer]] - two-dimentional array of BlueValues |
|---|
| 88 | master index is top-level index |
|---|
| 89 | |
|---|
| 90 | other_blues_num(integer) - number of defined OtherBlues values |
|---|
| 91 | other_blues[integer[integer]] - two-dimentional array of OtherBlues |
|---|
| 92 | master index is top-level index |
|---|
| 93 | |
|---|
| 94 | family_blues_num(integer) - number of FamilyBlues records |
|---|
| 95 | family_blues[integer[integer]] - two-dimentional array of FamilyBlues |
|---|
| 96 | master index is top-level index |
|---|
| 97 | |
|---|
| 98 | family_other_blues_num(integer) - number of FamilyOtherBlues records |
|---|
| 99 | family_other_blues[integer[integer]] - two-dimentional array of FamilyOtherBlues |
|---|
| 100 | master index is top-level index |
|---|
| 101 | |
|---|
| 102 | force_bold[integer] - list of Force Bold values, one for |
|---|
| 103 | each master |
|---|
| 104 | stem_snap_h_num(integer) |
|---|
| 105 | stem_snap_h |
|---|
| 106 | stem_snap_v_num(integer) |
|---|
| 107 | stem_snap_v |
|---|
| 108 | """ |
|---|
| 109 | |
|---|
| 110 | class PostScriptFontHintValues(BasePostScriptFontHintValues): |
|---|
| 111 | """ Wrapper for font-level PostScript hinting information for FontLab. |
|---|
| 112 | Blues values, stem values. |
|---|
| 113 | """ |
|---|
| 114 | def __init__(self, font=None, impliedMasterIndex=0): |
|---|
| 115 | self._object = font.naked() |
|---|
| 116 | self._masterIndex = impliedMasterIndex |
|---|
| 117 | |
|---|
| 118 | def copy(self): |
|---|
| 119 | from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues |
|---|
| 120 | return _PostScriptFontHintValues(data=self.asDict()) |
|---|
| 121 | |
|---|
| 122 | def _getBlueFuzz(self): |
|---|
| 123 | return self._object.blue_fuzz[self._masterIndex] |
|---|
| 124 | def _setBlueFuzz(self, value): |
|---|
| 125 | self._object.blue_fuzz[self._masterIndex] = value |
|---|
| 126 | |
|---|
| 127 | def _getBlueScale(self): |
|---|
| 128 | return self._object.blue_scale[self._masterIndex] |
|---|
| 129 | def _setBlueScale(self, value): |
|---|
| 130 | self._object.blue_scale[self._masterIndex] = float(value) |
|---|
| 131 | |
|---|
| 132 | def _getBlueShift(self): |
|---|
| 133 | return self._object.blue_shift[self._masterIndex] |
|---|
| 134 | def _setBlueShift(self, value): |
|---|
| 135 | self._object.blue_shift[self._masterIndex] = value |
|---|
| 136 | |
|---|
| 137 | def _getForceBold(self): |
|---|
| 138 | return self._object.force_bold[self._masterIndex] == 1 |
|---|
| 139 | |
|---|
| 140 | def _setForceBold(self, value): |
|---|
| 141 | if value: |
|---|
| 142 | value = 1 |
|---|
| 143 | else: |
|---|
| 144 | value = 0 |
|---|
| 145 | self._object.force_bold[self._masterIndex] = value |
|---|
| 146 | |
|---|
| 147 | # Note: these attributes are wrapppers for lists, |
|---|
| 148 | # but regular list operatons won't have any effect. |
|---|
| 149 | # you really have to _get_ and _set_ a list. |
|---|
| 150 | |
|---|
| 151 | def _asPairs(self, l): |
|---|
| 152 | """Split a list of numbers into a list of pairs""" |
|---|
| 153 | assert len(l)%2 == 0, "Even number of values required: %s"%(`l`) |
|---|
| 154 | n = [[l[i], l[i+1]] for i in range(0, len(l), 2)] |
|---|
| 155 | n.sort() |
|---|
| 156 | return n |
|---|
| 157 | |
|---|
| 158 | def _flattenPairs(self, l): |
|---|
| 159 | """The reverse of _asPairs""" |
|---|
| 160 | n = [] |
|---|
| 161 | l.sort() |
|---|
| 162 | for i in l: |
|---|
| 163 | assert len(i) == 2, "Each entry must consist of two numbers" |
|---|
| 164 | n.append(i[0]) |
|---|
| 165 | n.append(i[1]) |
|---|
| 166 | return n |
|---|
| 167 | |
|---|
| 168 | def _getBlueValues(self): |
|---|
| 169 | return self._asPairs(self._object.blue_values[self._masterIndex]) |
|---|
| 170 | def _setBlueValues(self, values): |
|---|
| 171 | values = self._flattenPairs(values) |
|---|
| 172 | self._object.blue_values_num = min(self._attributeNames['blueValues']['max']*2, len(values)) |
|---|
| 173 | for i in range(self._object.blue_values_num): |
|---|
| 174 | self._object.blue_values[self._masterIndex][i] = values[i] |
|---|
| 175 | |
|---|
| 176 | def _getOtherBlues(self): |
|---|
| 177 | return self._asPairs(self._object.other_blues[self._masterIndex]) |
|---|
| 178 | def _setOtherBlues(self, values): |
|---|
| 179 | values = self._flattenPairs(values) |
|---|
| 180 | self._object.other_blues_num = min(self._attributeNames['otherBlues']['max']*2, len(values)) |
|---|
| 181 | for i in range(self._object.other_blues_num): |
|---|
| 182 | self._object.other_blues[self._masterIndex][i] = values[i] |
|---|
| 183 | |
|---|
| 184 | def _getFamilyBlues(self): |
|---|
| 185 | return self._asPairs(self._object.family_blues[self._masterIndex]) |
|---|
| 186 | def _setFamilyBlues(self, values): |
|---|
| 187 | values = self._flattenPairs(values) |
|---|
| 188 | self._object.family_blues_num = min(self._attributeNames['familyBlues']['max']*2, len(values)) |
|---|
| 189 | for i in range(self._object.family_blues_num): |
|---|
| 190 | self._object.family_blues[self._masterIndex][i] = values[i] |
|---|
| 191 | |
|---|
| 192 | def _getFamilyOtherBlues(self): |
|---|
| 193 | return self._asPairs(self._object.family_other_blues[self._masterIndex]) |
|---|
| 194 | def _setFamilyOtherBlues(self, values): |
|---|
| 195 | values = self._flattenPairs(values) |
|---|
| 196 | self._object.family_other_blues_num = min(self._attributeNames['familyOtherBlues']['max']*2, len(values)) |
|---|
| 197 | for i in range(self._object.family_other_blues_num): |
|---|
| 198 | self._object.family_other_blues[self._masterIndex][i] = values[i] |
|---|
| 199 | |
|---|
| 200 | def _getVStems(self): |
|---|
| 201 | return list(self._object.stem_snap_v[self._masterIndex]) |
|---|
| 202 | def _setVStems(self, values): |
|---|
| 203 | self._object.stem_snap_v_num = min(self._attributeNames['vStems']['max'], len(values)) |
|---|
| 204 | for i in range(self._object.stem_snap_v_num): |
|---|
| 205 | self._object.stem_snap_v[self._masterIndex][i] = values[i] |
|---|
| 206 | |
|---|
| 207 | def _getHStems(self): |
|---|
| 208 | return list(self._object.stem_snap_h[self._masterIndex]) |
|---|
| 209 | def _setHStems(self, values): |
|---|
| 210 | self._object.stem_snap_h_num = min(self._attributeNames['hStems']['max'], len(values)) |
|---|
| 211 | for i in range(self._object.stem_snap_h_num): |
|---|
| 212 | self._object.stem_snap_h[self._masterIndex][i] = values[i] |
|---|
| 213 | |
|---|
| 214 | blueFuzz = property(_getBlueFuzz, _setBlueFuzz, doc="postscript hints: bluefuzz value") |
|---|
| 215 | blueScale = property(_getBlueScale, _setBlueScale, doc="postscript hints: bluescale value") |
|---|
| 216 | blueShift = property(_getBlueShift, _setBlueShift, doc="postscript hints: blueshift value") |
|---|
| 217 | forceBold = property(_getForceBold, _setForceBold, doc="postscript hints: force bold value") |
|---|
| 218 | blueValues = property(_getBlueValues, _setBlueValues, doc="postscript hints: blue values") |
|---|
| 219 | otherBlues = property(_getOtherBlues, _setOtherBlues, doc="postscript hints: other blue values") |
|---|
| 220 | familyBlues = property(_getFamilyBlues, _setFamilyBlues, doc="postscript hints: family blue values") |
|---|
| 221 | familyOtherBlues = property(_getFamilyOtherBlues, _setFamilyOtherBlues, doc="postscript hints: family other blue values") |
|---|
| 222 | vStems = property(_getVStems, _setVStems, doc="postscript hints: vertical stem values") |
|---|
| 223 | hStems = property(_getHStems, _setHStems, doc="postscript hints: horizontal stem values") |
|---|
| 224 | |
|---|
| 225 | |
|---|
| 226 | |
|---|
| 227 | def getPostScriptHintDataFromLib(aFont, fontLib): |
|---|
| 228 | hintData = fontLib.get(postScriptHintDataLibKey) |
|---|
| 229 | psh = PostScriptFontHintValues(aFont) |
|---|
| 230 | psh.fromDict(hintData) |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | def _glyphHintsToDict(glyph): |
|---|
| 234 | data = {} |
|---|
| 235 | ## |
|---|
| 236 | ## horizontal and vertical hints |
|---|
| 237 | ## |
|---|
| 238 | # glyph.hhints and glyph.vhints returns a list of Hint objects. |
|---|
| 239 | # Hint objects have position and width attributes. |
|---|
| 240 | data['hhints'] = [] |
|---|
| 241 | for index in xrange(len(glyph.hhints)): |
|---|
| 242 | hint = glyph.hhints[index] |
|---|
| 243 | d = { 'position' : hint.position, |
|---|
| 244 | 'width' : hint.width, |
|---|
| 245 | } |
|---|
| 246 | data['hhints'].append(d) |
|---|
| 247 | if not data['hhints']: |
|---|
| 248 | del data['hhints'] |
|---|
| 249 | data['vhints'] = [] |
|---|
| 250 | for index in xrange(len(glyph.vhints)): |
|---|
| 251 | hint = glyph.vhints[index] |
|---|
| 252 | d = { 'position' : hint.position, |
|---|
| 253 | 'width' : hint.width, |
|---|
| 254 | } |
|---|
| 255 | data['vhints'].append(d) |
|---|
| 256 | if not data['vhints']: |
|---|
| 257 | del data['vhints'] |
|---|
| 258 | ## |
|---|
| 259 | ## horizontal and vertical links |
|---|
| 260 | ## |
|---|
| 261 | # glyph.hlinks and glyph.vlinks returns a list of Link objects. |
|---|
| 262 | # Link objects have node1 and node2 attributes. |
|---|
| 263 | data['hlinks'] = [] |
|---|
| 264 | for index in xrange(len(glyph.hlinks)): |
|---|
| 265 | link = glyph.hlinks[index] |
|---|
| 266 | d = { 'node1' : link.node1, |
|---|
| 267 | 'node2' : link.node2, |
|---|
| 268 | } |
|---|
| 269 | data['hlinks'].append(d) |
|---|
| 270 | if not data['hlinks']: |
|---|
| 271 | del data['hlinks'] |
|---|
| 272 | data['vlinks'] = [] |
|---|
| 273 | for index in xrange(len(glyph.vlinks)): |
|---|
| 274 | link = glyph.vlinks[index] |
|---|
| 275 | d = { 'node1' : link.node1, |
|---|
| 276 | 'node2' : link.node2, |
|---|
| 277 | } |
|---|
| 278 | data['vlinks'].append(d) |
|---|
| 279 | if not data['vlinks']: |
|---|
| 280 | del data['vlinks'] |
|---|
| 281 | ## |
|---|
| 282 | ## replacement table |
|---|
| 283 | ## |
|---|
| 284 | # glyph.replace_table returns a list of Replace objects. |
|---|
| 285 | # Replace objects have type and index attributes. |
|---|
| 286 | data['replace_table'] = [] |
|---|
| 287 | for index in xrange(len(glyph.replace_table)): |
|---|
| 288 | replace = glyph.replace_table[index] |
|---|
| 289 | d = { 'type' : replace.type, |
|---|
| 290 | 'index' : replace.index, |
|---|
| 291 | } |
|---|
| 292 | data['replace_table'].append(d) |
|---|
| 293 | if not data['replace_table']: |
|---|
| 294 | del data['replace_table'] |
|---|
| 295 | # XXX |
|---|
| 296 | # need to support glyph.instructions and glyph.hdmx? |
|---|
| 297 | # they are not documented very well. |
|---|
| 298 | return data |
|---|
| 299 | |
|---|
| 300 | def _dictHintsToGlyph(glyph, aDict): |
|---|
| 301 | # clear existing hints first |
|---|
| 302 | # RemoveHints requires an "integer mode" argument |
|---|
| 303 | # but it is not documented. from some simple experiments |
|---|
| 304 | # i deduced that |
|---|
| 305 | # 1 = horizontal hints and links, |
|---|
| 306 | # 2 = vertical hints and links |
|---|
| 307 | # 3 = all hints and links |
|---|
| 308 | glyph.RemoveHints(3) |
|---|
| 309 | ## |
|---|
| 310 | ## horizontal and vertical hints |
|---|
| 311 | ## |
|---|
| 312 | if aDict.has_key('hhints'): |
|---|
| 313 | for d in aDict['hhints']: |
|---|
| 314 | glyph.hhints.append(Hint(d['position'], d['width'])) |
|---|
| 315 | if aDict.has_key('vhints'): |
|---|
| 316 | for d in aDict['vhints']: |
|---|
| 317 | glyph.vhints.append(Hint(d['position'], d['width'])) |
|---|
| 318 | ## |
|---|
| 319 | ## horizontal and vertical links |
|---|
| 320 | ## |
|---|
| 321 | if aDict.has_key('hlinks'): |
|---|
| 322 | for d in aDict['hlinks']: |
|---|
| 323 | glyph.hlinks.append(Link(d['node1'], d['node2'])) |
|---|
| 324 | if aDict.has_key('vlinks'): |
|---|
| 325 | for d in aDict['vlinks']: |
|---|
| 326 | glyph.vlinks.append(Link(d['node1'], d['node2'])) |
|---|
| 327 | ## |
|---|
| 328 | ## replacement table |
|---|
| 329 | ## |
|---|
| 330 | if aDict.has_key('replace_table'): |
|---|
| 331 | for d in aDict['replace_table']: |
|---|
| 332 | glyph.replace_table.append(Replace(d['type'], d['index'])) |
|---|
| 333 | |
|---|
| 334 | # FL Node Types |
|---|
| 335 | flMOVE = 17 |
|---|
| 336 | flLINE = 1 |
|---|
| 337 | flCURVE = 35 |
|---|
| 338 | flOFFCURVE = 65 |
|---|
| 339 | flSHARP = 0 |
|---|
| 340 | # I have no idea what the difference between |
|---|
| 341 | # "smooth" and "fixed" is, but booth values |
|---|
| 342 | # are returned by FL |
|---|
| 343 | flSMOOTH = 4096 |
|---|
| 344 | flFIXED = 12288 |
|---|
| 345 | |
|---|
| 346 | |
|---|
| 347 | _flToRFSegmentDict = { flMOVE : MOVE, |
|---|
| 348 | flLINE : LINE, |
|---|
| 349 | flCURVE : CURVE, |
|---|
| 350 | flOFFCURVE : OFFCURVE |
|---|
| 351 | } |
|---|
| 352 | |
|---|
| 353 | _rfToFLSegmentDict = {} |
|---|
| 354 | for k, v in _flToRFSegmentDict.items(): |
|---|
| 355 | _rfToFLSegmentDict[v] = k |
|---|
| 356 | |
|---|
| 357 | def _flToRFSegmentType(segmentType): |
|---|
| 358 | return _flToRFSegmentDict[segmentType] |
|---|
| 359 | |
|---|
| 360 | def _rfToFLSegmentType(segmentType): |
|---|
| 361 | return _rfToFLSegmentDict[segmentType] |
|---|
| 362 | |
|---|
| 363 | def _scalePointFromCenter((pointX, pointY), (scaleX, scaleY), (centerX, centerY)): |
|---|
| 364 | ogCenter = (centerX, centerY) |
|---|
| 365 | scaledCenter = (centerX * scaleX, centerY * scaleY) |
|---|
| 366 | shiftVal = (scaledCenter[0] - ogCenter[0], scaledCenter[1] - ogCenter[1]) |
|---|
| 367 | scaledPointX = (pointX * scaleX) - shiftVal[0] |
|---|
| 368 | scaledPointY = (pointY * scaleY) - shiftVal[1] |
|---|
| 369 | return (scaledPointX, scaledPointY) |
|---|
| 370 | |
|---|
| 371 | # Nostalgia code: |
|---|
| 372 | def CurrentFont(): |
|---|
| 373 | """Return a RoboFab font object for the currently selected font.""" |
|---|
| 374 | f = fl.font |
|---|
| 375 | if f is not None: |
|---|
| 376 | return RFont(fl.font) |
|---|
| 377 | return None |
|---|
| 378 | |
|---|
| 379 | def CurrentGlyph(): |
|---|
| 380 | """Return a RoboFab glyph object for the currently selected glyph.""" |
|---|
| 381 | from robofab.world import AllFonts |
|---|
| 382 | currentPath = fl.font.file_name |
|---|
| 383 | if fl.glyph is None: |
|---|
| 384 | return None |
|---|
| 385 | glyphName = fl.glyph.name |
|---|
| 386 | currentFont = None |
|---|
| 387 | # is this font already loaded as an RFont? |
|---|
| 388 | for font in AllFonts(): |
|---|
| 389 | # ugh this won't work because AllFonts sees non RFonts as well.... |
|---|
| 390 | if font.path == currentPath: |
|---|
| 391 | currentFont = font |
|---|
| 392 | break |
|---|
| 393 | xx = currentFont[glyphName] |
|---|
| 394 | #print "objectsFL.CurrentGlyph parent for %d"% id(xx), xx.getParent() |
|---|
| 395 | return xx |
|---|
| 396 | |
|---|
| 397 | def OpenFont(path=None, note=None): |
|---|
| 398 | """Open a font from a path.""" |
|---|
| 399 | if path == None: |
|---|
| 400 | from robofab.interface.all.dialogs import GetFile |
|---|
| 401 | path = GetFile(note) |
|---|
| 402 | if path: |
|---|
| 403 | if path[-4:].lower() in ['.vfb', '.VFB', '.bak', '.BAK']: |
|---|
| 404 | f = Font(path) |
|---|
| 405 | fl.Add(f) |
|---|
| 406 | return RFont(f) |
|---|
| 407 | return None |
|---|
| 408 | |
|---|
| 409 | def NewFont(familyName=None, styleName=None): |
|---|
| 410 | """Make a new font""" |
|---|
| 411 | from FL import fl, Font |
|---|
| 412 | f = Font() |
|---|
| 413 | fl.Add(f) |
|---|
| 414 | rf = RFont(f) |
|---|
| 415 | rf.info.familyName = familyName |
|---|
| 416 | rf.info.styleName = styleName |
|---|
| 417 | return rf |
|---|
| 418 | |
|---|
| 419 | def AllFonts(): |
|---|
| 420 | """Return a list of all open fonts.""" |
|---|
| 421 | fontCount = len(fl) |
|---|
| 422 | all = [] |
|---|
| 423 | for index in xrange(fontCount): |
|---|
| 424 | naked = fl[index] |
|---|
| 425 | all.append(RFont(naked)) |
|---|
| 426 | return all |
|---|
| 427 | |
|---|
| 428 | # the lib getter and setter are shared by RFont and RGlyph |
|---|
| 429 | def _get_lib(self): |
|---|
| 430 | data = self._object.customdata |
|---|
| 431 | if data: |
|---|
| 432 | f = StringIO(data) |
|---|
| 433 | try: |
|---|
| 434 | pList = readPlist(f) |
|---|
| 435 | except: # XXX ugh, plistlib can raise lots of things |
|---|
| 436 | # Anyway, customdata does not contain valid plist data, |
|---|
| 437 | # but we don't need to toss it! |
|---|
| 438 | pList = {"org.robofab.fontlab.customdata": Data(data)} |
|---|
| 439 | else: |
|---|
| 440 | pList = {} |
|---|
| 441 | # pass it along to the lib object |
|---|
| 442 | l = RLib(pList) |
|---|
| 443 | l.setParent(self) |
|---|
| 444 | return l |
|---|
| 445 | |
|---|
| 446 | def _set_lib(self, aDict): |
|---|
| 447 | l = RLib({}) |
|---|
| 448 | l.setParent(self) |
|---|
| 449 | l.update(aDict) |
|---|
| 450 | |
|---|
| 451 | |
|---|
| 452 | def _normalizeLineEndings(s): |
|---|
| 453 | return s.replace("\r\n", "\n").replace("\r", "\n") |
|---|
| 454 | |
|---|
| 455 | |
|---|
| 456 | class RFont(BaseFont): |
|---|
| 457 | """RoboFab UFO wrapper for FL Font object""" |
|---|
| 458 | |
|---|
| 459 | _title = "FLFont" |
|---|
| 460 | |
|---|
| 461 | def __init__(self, font=None): |
|---|
| 462 | BaseFont.__init__(self) |
|---|
| 463 | if font is None: |
|---|
| 464 | from FL import fl, Font |
|---|
| 465 | # rather than raise an error we could just start a new font. |
|---|
| 466 | font = Font() |
|---|
| 467 | fl.Add(font) |
|---|
| 468 | #raise RoboFabError, "RFont: there's nothing to wrap!?" |
|---|
| 469 | self._object = font |
|---|
| 470 | self._lib = {} |
|---|
| 471 | self._supportHints = False |
|---|
| 472 | |
|---|
| 473 | def keys(self): |
|---|
| 474 | keys = {} |
|---|
| 475 | for glyph in self._object.glyphs: |
|---|
| 476 | glyphName = glyph.name |
|---|
| 477 | if glyphName in keys: |
|---|
| 478 | n = 1 |
|---|
| 479 | while ("%s#%s" % (glyphName, n)) in keys: |
|---|
| 480 | n += 1 |
|---|
| 481 | newGlyphName = "%s#%s" % (glyphName, n) |
|---|
| 482 | print "RoboFab encountered a duplicate glyph name, renaming %r to %r" % (glyphName, newGlyphName) |
|---|
| 483 | glyphName = newGlyphName |
|---|
| 484 | glyph.name = glyphName |
|---|
| 485 | keys[glyphName] = None |
|---|
| 486 | return keys.keys() |
|---|
| 487 | |
|---|
| 488 | def has_key(self, glyphName): |
|---|
| 489 | glyph = self._object[glyphName] |
|---|
| 490 | if glyph is None: |
|---|
| 491 | return False |
|---|
| 492 | else: |
|---|
| 493 | return True |
|---|
| 494 | |
|---|
| 495 | __contains__ = has_key |
|---|
| 496 | |
|---|
| 497 | def __setitem__(self, glyphName, glyph): |
|---|
| 498 | self._object[glyphName] = glyph.naked() |
|---|
| 499 | |
|---|
| 500 | def __cmp__(self, other): |
|---|
| 501 | if not hasattr(other, '_object'): |
|---|
| 502 | return -1 |
|---|
| 503 | return self._compare(other) |
|---|
| 504 | # if self._object.file_name == other._object.file_name: |
|---|
| 505 | # # so, names match. |
|---|
| 506 | # # this will falsely identify two distinct "Untitled" |
|---|
| 507 | # # let's check some more |
|---|
| 508 | # return 0 |
|---|
| 509 | # else: |
|---|
| 510 | # return -1 |
|---|
| 511 | |
|---|
| 512 | |
|---|
| 513 | def _get_psHints(self): |
|---|
| 514 | return PostScriptFontHintValues(self) |
|---|
| 515 | |
|---|
| 516 | psHints = property(_get_psHints, doc="font level postscript hint data") |
|---|
| 517 | |
|---|
| 518 | def _get_info(self): |
|---|
| 519 | return RInfo(self._object) |
|---|
| 520 | |
|---|
| 521 | info = property(_get_info, doc="font info object") |
|---|
| 522 | |
|---|
| 523 | def _get_kerning(self): |
|---|
| 524 | kerning = {} |
|---|
| 525 | f = self._object |
|---|
| 526 | for g in f.glyphs: |
|---|
| 527 | for p in g.kerning: |
|---|
| 528 | try: |
|---|
| 529 | key = (g.name, f[p.key].name) |
|---|
| 530 | kerning[key] = p.value |
|---|
| 531 | except AttributeError: pass #catch for TT exception |
|---|
| 532 | rk = RKerning(kerning) |
|---|
| 533 | rk.setParent(self) |
|---|
| 534 | return rk |
|---|
| 535 | |
|---|
| 536 | kerning = property(_get_kerning, doc="a kerning object") |
|---|
| 537 | |
|---|
| 538 | def _set_groups(self, aDict): |
|---|
| 539 | g = RGroups({}) |
|---|
| 540 | g.setParent(self) |
|---|
| 541 | g.update(aDict) |
|---|
| 542 | |
|---|
| 543 | def _get_groups(self): |
|---|
| 544 | groups = {} |
|---|
| 545 | for i in self._object.classes: |
|---|
| 546 | # test to make sure that the class is properly formatted |
|---|
| 547 | if i.find(':') == -1: |
|---|
| 548 | continue |
|---|
| 549 | key = i.split(':')[0] |
|---|
| 550 | value = i.split(':')[1].lstrip().split(' ') |
|---|
| 551 | groups[key] = value |
|---|
| 552 | rg = RGroups(groups) |
|---|
| 553 | rg.setParent(self) |
|---|
| 554 | return rg |
|---|
| 555 | |
|---|
| 556 | groups = property(_get_groups, _set_groups, doc="a group object") |
|---|
| 557 | |
|---|
| 558 | lib = property(_get_lib, _set_lib, doc="font lib object") |
|---|
| 559 | |
|---|
| 560 | # |
|---|
| 561 | # attributes |
|---|
| 562 | # |
|---|
| 563 | |
|---|
| 564 | def _get_fontIndex(self): |
|---|
| 565 | # find the index of the font |
|---|
| 566 | # by comparing the file_name |
|---|
| 567 | # to all open fonts. if the |
|---|
| 568 | # font has no file_name, meaning |
|---|
| 569 | # it is a new, unsaved font, |
|---|
| 570 | # return the index of the first |
|---|
| 571 | # font with no file_name. |
|---|
| 572 | selfFileName = self._object.file_name |
|---|
| 573 | fontCount = len(fl) |
|---|
| 574 | for index in xrange(fontCount): |
|---|
| 575 | other = fl[index] |
|---|
| 576 | if other.file_name == selfFileName: |
|---|
| 577 | return index |
|---|
| 578 | |
|---|
| 579 | fontIndex = property(_get_fontIndex, doc="the fontindex for this font") |
|---|
| 580 | |
|---|
| 581 | def _get_path(self): |
|---|
| 582 | return self._object.file_name |
|---|
| 583 | |
|---|
| 584 | path = property(_get_path, doc="path to the font") |
|---|
| 585 | |
|---|
| 586 | def _get_fileName(self): |
|---|
| 587 | if self.path is None: |
|---|
| 588 | return None |
|---|
| 589 | return os.path.split(self.path) |
|---|
| 590 | |
|---|
| 591 | fileName = property(_get_fileName, doc="the font's file name") |
|---|
| 592 | |
|---|
| 593 | def _get_selection(self): |
|---|
| 594 | # return a list of glyph names for glyphs selected in the font window |
|---|
| 595 | l=[] |
|---|
| 596 | for i in range(len(self._object.glyphs)): |
|---|
| 597 | if fl.Selected(i) == 1: |
|---|
| 598 | l.append(self._object[i].name) |
|---|
| 599 | return l |
|---|
| 600 | |
|---|
| 601 | def _set_selection(self, list): |
|---|
| 602 | fl.Unselect() |
|---|
| 603 | for i in list: |
|---|
| 604 | fl.Select(i) |
|---|
| 605 | |
|---|
| 606 | selection = property(_get_selection, _set_selection, doc="the glyph selection in the font window") |
|---|
| 607 | |
|---|
| 608 | |
|---|
| 609 | def _makeGlyphlist(self): |
|---|
| 610 | # To allow iterations through Font.glyphs. Should become really big in fonts with lotsa letters. |
|---|
| 611 | gl = [] |
|---|
| 612 | for c in self: |
|---|
| 613 | gl.append(c) |
|---|
| 614 | return gl |
|---|
| 615 | |
|---|
| 616 | def _get_glyphs(self): |
|---|
| 617 | return self._makeGlyphlist() |
|---|
| 618 | |
|---|
| 619 | glyphs = property(_get_glyphs, doc="A list of all glyphs in the font, to allow iterations through Font.glyphs") |
|---|
| 620 | |
|---|
| 621 | def update(self): |
|---|
| 622 | """Don't forget to update the font when you are done.""" |
|---|
| 623 | fl.UpdateFont(self.fontIndex) |
|---|
| 624 | |
|---|
| 625 | def save(self, path=None): |
|---|
| 626 | """Save the font, path is required.""" |
|---|
| 627 | if not path: |
|---|
| 628 | if not self._object.file_name: |
|---|
| 629 | raise RoboFabError, "No destination path specified." |
|---|
| 630 | else: |
|---|
| 631 | path = self._object.file_name |
|---|
| 632 | fl.Save(self.fontIndex, path) |
|---|
| 633 | |
|---|
| 634 | def close(self, save=False): |
|---|
| 635 | """Close the font, saving is optional.""" |
|---|
| 636 | if save: |
|---|
| 637 | self.save() |
|---|
| 638 | else: |
|---|
| 639 | self._object.modified = 0 |
|---|
| 640 | fl.Close(self.fontIndex) |
|---|
| 641 | |
|---|
| 642 | def getGlyph(self, glyphName): |
|---|
| 643 | # XXX may need to become private |
|---|
| 644 | flGlyph = self._object[glyphName] |
|---|
| 645 | if flGlyph is not None: |
|---|
| 646 | glyph = RGlyph(flGlyph) |
|---|
| 647 | glyph.setParent(self) |
|---|
| 648 | return glyph |
|---|
| 649 | return self.newGlyph(glyphName) |
|---|
| 650 | |
|---|
| 651 | def newGlyph(self, glyphName, clear=True): |
|---|
| 652 | """Make a new glyph""" |
|---|
| 653 | #if generate: |
|---|
| 654 | # g = GenerateGlyph(self._object, glyphName, replace=clear) |
|---|
| 655 | #else: |
|---|
| 656 | g = NewGlyph(self._object, glyphName, clear) |
|---|
| 657 | return RGlyph(g) |
|---|
| 658 | |
|---|
| 659 | def insertGlyph(self, glyph, as=None): |
|---|
| 660 | """Returns a new glyph that has been inserted into the font. |
|---|
| 661 | as = another glyphname if you want to insert as with that.""" |
|---|
| 662 | from robofab.objects.objectsRF import RFont as _RFont |
|---|
| 663 | from robofab.objects.objectsRF import RGlyph as _RGlyph |
|---|
| 664 | oldGlyph = glyph |
|---|
| 665 | if as is None: |
|---|
| 666 | name = oldGlyph.name |
|---|
| 667 | else: |
|---|
| 668 | name = as |
|---|
| 669 | # clear the destination glyph if it exists. |
|---|
| 670 | if self.has_key(name): |
|---|
| 671 | self[name].clear() |
|---|
| 672 | # get the parent for the glyph |
|---|
| 673 | otherFont = oldGlyph.getParent() |
|---|
| 674 | # in some cases we will use the native |
|---|
| 675 | # FL method for appending a glyph. |
|---|
| 676 | useNative = True |
|---|
| 677 | testingNative = True |
|---|
| 678 | while testingNative: |
|---|
| 679 | # but, maybe it is an orphan glyph. |
|---|
| 680 | # in that case we should not use the native method. |
|---|
| 681 | if otherFont is None: |
|---|
| 682 | useNative = False |
|---|
| 683 | testingNative = False |
|---|
| 684 | # or maybe the glyph is coming from a NoneLab font |
|---|
| 685 | if otherFont is not None: |
|---|
| 686 | if isinstance(otherFont, _RFont): |
|---|
| 687 | useNative = False |
|---|
| 688 | testingNative = False |
|---|
| 689 | # but, it could be a copied FL glyph |
|---|
| 690 | # which is a NoneLab glyph that |
|---|
| 691 | # has a FontLab font as the parent |
|---|
| 692 | elif isinstance(otherFont, RFont): |
|---|
| 693 | useNative = False |
|---|
| 694 | testingNative = False |
|---|
| 695 | # or, maybe the glyph is being replaced, in which |
|---|
| 696 | # case the native method should not be used |
|---|
| 697 | # since FL will destroy any references to the glyph |
|---|
| 698 | if self.has_key(name): |
|---|
| 699 | useNative = False |
|---|
| 700 | testingNative = False |
|---|
| 701 | # if the glyph contains components the native |
|---|
| 702 | # method should not be used since FL does |
|---|
| 703 | # not reference glyphs in components by |
|---|
| 704 | # name, but by index (!!!). |
|---|
| 705 | if len(oldGlyph.components) != 0: |
|---|
| 706 | useNative = False |
|---|
| 707 | testingNative = False |
|---|
| 708 | testingNative = False |
|---|
| 709 | # finally, insert the glyph. |
|---|
| 710 | if useNative: |
|---|
| 711 | font = self.naked() |
|---|
| 712 | otherFont = oldGlyph.getParent().naked() |
|---|
| 713 | self.naked().glyphs.append(otherFont[name]) |
|---|
| 714 | newGlyph = self.getGlyph(name) |
|---|
| 715 | else: |
|---|
| 716 | newGlyph = self.newGlyph(name) |
|---|
| 717 | newGlyph.appendGlyph(oldGlyph) |
|---|
| 718 | for attr in GLYPH_COPY_ATTRS: |
|---|
| 719 | if attr == "name": |
|---|
| 720 | value = name |
|---|
| 721 | else: |
|---|
| 722 | value = getattr(oldGlyph, attr) |
|---|
| 723 | setattr(newGlyph, attr, value) |
|---|
| 724 | if self._supportHints: |
|---|
| 725 | # now we need to transfer the hints from |
|---|
| 726 | # the old glyph to the new glyph. we'll do this |
|---|
| 727 | # via the dict to hint functions. |
|---|
| 728 | hintDict = {} |
|---|
| 729 | # if the glyph is a NoneLab glyph, then we need |
|---|
| 730 | # to extract the ps hints from the lib |
|---|
| 731 | if isinstance(oldGlyph, _RGlyph): |
|---|
| 732 | hintDict = oldGlyph.lib.get(postScriptHintDataLibKey, {}) |
|---|
| 733 | # otherwise we need to extract the hint dict from the glyph |
|---|
| 734 | else: |
|---|
| 735 | hintDict = _glyphHintsToDict(oldGlyph.naked()) |
|---|
| 736 | # now apply the hint data |
|---|
| 737 | if hintDict: |
|---|
| 738 | _dictHintsToGlyph(newGlyph.naked(), hintDict) |
|---|
| 739 | # delete any remaining hint data from the glyph lib |
|---|
| 740 | if newGlyph.lib.has_key(postScriptHintDataLibKey): |
|---|
| 741 | del newGlyph.lib[postScriptHintDataLibKey] |
|---|
| 742 | return newGlyph |
|---|
| 743 | |
|---|
| 744 | def removeGlyph(self, glyphName): |
|---|
| 745 | """remove a glyph from the font""" |
|---|
| 746 | index = self._object.FindGlyph(glyphName) |
|---|
| 747 | if index != -1: |
|---|
| 748 | del self._object.glyphs[index] |
|---|
| 749 | |
|---|
| 750 | # |
|---|
| 751 | # opentype |
|---|
| 752 | # |
|---|
| 753 | |
|---|
| 754 | def getOTClasses(self): |
|---|
| 755 | """Return all OpenType classes as a dict. Relies on properly formatted classes.""" |
|---|
| 756 | classes = {} |
|---|
| 757 | c = self._object.ot_classes |
|---|
| 758 | if c is None: |
|---|
| 759 | return classes |
|---|
| 760 | c = c.replace('\r', '').replace('\n', '').split(';') |
|---|
| 761 | for i in c: |
|---|
| 762 | if i.find('=') != -1: |
|---|
| 763 | value = [] |
|---|
| 764 | i = i.replace(' = ', '=') |
|---|
| 765 | name = i.split('=')[0] |
|---|
| 766 | v = i.split('=')[1].replace('[', '').replace(']', '').split(' ') |
|---|
| 767 | #catch double spaces? |
|---|
| 768 | for j in v: |
|---|
| 769 | if len(j) > 0: |
|---|
| 770 | value.append(j) |
|---|
| 771 | classes[name] = value |
|---|
| 772 | return classes |
|---|
| 773 | |
|---|
| 774 | def setOTClasses(self, dict): |
|---|
| 775 | """Set all OpenType classes.""" |
|---|
| 776 | l = [] |
|---|
| 777 | for i in dict.keys(): |
|---|
| 778 | l.append(''.join([i, ' = [', ' '.join(dict[i]), '];'])) |
|---|
| 779 | self._object.ot_classes = '\n'.join(l) |
|---|
| 780 | |
|---|
| 781 | def getOTClass(self, name): |
|---|
| 782 | """Get a specific OpenType class.""" |
|---|
| 783 | classes = self.getOTClasses() |
|---|
| 784 | return classes[name] |
|---|
| 785 | |
|---|
| 786 | def setOTClass(self, name, list): |
|---|
| 787 | """Set a specific OpenType class.""" |
|---|
| 788 | classes = self.getOTClasses() |
|---|
| 789 | classes[name] = list |
|---|
| 790 | self.setOTClasses(classes) |
|---|
| 791 | |
|---|
| 792 | def getOTFeatures(self): |
|---|
| 793 | """Return all OpenType features as a dict keyed by name. |
|---|
| 794 | The value is a string of the text of the feature.""" |
|---|
| 795 | features = {} |
|---|
| 796 | for i in self._object.features: |
|---|
| 797 | v = [] |
|---|
| 798 | for j in i.value.replace('\r', '\n').split('\n'): |
|---|
| 799 | if j.find(i.tag) == -1: |
|---|
| 800 | v.append(j) |
|---|
| 801 | features[i.tag] = '\n'.join(v) |
|---|
| 802 | return features |
|---|
| 803 | |
|---|
| 804 | def setOTFeatures(self, dict): |
|---|
| 805 | """Set all OpenType features in the font.""" |
|---|
| 806 | features= {} |
|---|
| 807 | for i in dict.keys(): |
|---|
| 808 | f = [] |
|---|
| 809 | f.append('feature %s {'%i) |
|---|
| 810 | f.append(dict[i]) |
|---|
| 811 | f.append('} %s;'%i) |
|---|
| 812 | features[i] = '\n'.join(f) |
|---|
| 813 | self._object.features.clean() |
|---|
| 814 | for i in features.keys(): |
|---|
| 815 | self._object.features.append(Feature(i, features[i])) |
|---|
| 816 | |
|---|
| 817 | def getOTFeature(self, name): |
|---|
| 818 | """return a specific OpenType feature.""" |
|---|
| 819 | features = self.getOTFeatures() |
|---|
| 820 | return features[name] |
|---|
| 821 | |
|---|
| 822 | def setOTFeature(self, name, text): |
|---|
| 823 | """Set a specific OpenType feature.""" |
|---|
| 824 | features = self.getOTFeatures() |
|---|
| 825 | features[name] = text |
|---|
| 826 | self.setOTFeatures(features) |
|---|
| 827 | |
|---|
| 828 | # |
|---|
| 829 | # guides |
|---|
| 830 | # |
|---|
| 831 | |
|---|
| 832 | def getVGuides(self): |
|---|
| 833 | """Return a list of wrapped vertical guides in this RFont""" |
|---|
| 834 | vguides=[] |
|---|
| 835 | for i in range(len(self._object.vguides)): |
|---|
| 836 | g = RGuide(self._object.vguides[i], i) |
|---|
| 837 | g.setParent(self) |
|---|
| 838 | vguides.append(g) |
|---|
| 839 | return vguides |
|---|
| 840 | |
|---|
| 841 | def getHGuides(self): |
|---|
| 842 | """Return a list of wrapped horizontal guides in this RFont""" |
|---|
| 843 | hguides=[] |
|---|
| 844 | for i in range(len(self._object.hguides)): |
|---|
| 845 | g = RGuide(self._object.hguides[i], i) |
|---|
| 846 | g.setParent(self) |
|---|
| 847 | hguides.append(g) |
|---|
| 848 | return hguides |
|---|
| 849 | |
|---|
| 850 | def appendHGuide(self, position, angle=0): |
|---|
| 851 | """Append a horizontal guide""" |
|---|
| 852 | position = int(round(position)) |
|---|
| 853 | angle = int(round(angle)) |
|---|
| 854 | g=Guide(position, angle) |
|---|
| 855 | self._object.hguides.append(g) |
|---|
| 856 | |
|---|
| 857 | def appendVGuide(self, position, angle=0): |
|---|
| 858 | """Append a horizontal guide""" |
|---|
| 859 | position = int(round(position)) |
|---|
| 860 | angle = int(round(angle)) |
|---|
| 861 | g=Guide(position, angle) |
|---|
| 862 | self._object.vguides.append(g) |
|---|
| 863 | |
|---|
| 864 | def removeHGuide(self, guide): |
|---|
| 865 | """Remove a horizontal guide.""" |
|---|
| 866 | pos = (guide.position, guide.angle) |
|---|
| 867 | for g in self.getHGuides(): |
|---|
| 868 | if (g.position, g.angle) == pos: |
|---|
| 869 | del self._object.hguides[g.index] |
|---|
| 870 | break |
|---|
| 871 | |
|---|
| 872 | def removeVGuide(self, guide): |
|---|
| 873 | """Remove a vertical guide.""" |
|---|
| 874 | pos = (guide.position, guide.angle) |
|---|
| 875 | for g in self.getVGuides(): |
|---|
| 876 | if (g.position, g.angle) == pos: |
|---|
| 877 | del self._object.vguides[g.index] |
|---|
| 878 | break |
|---|
| 879 | |
|---|
| 880 | def clearHGuides(self): |
|---|
| 881 | """Clear all horizontal guides.""" |
|---|
| 882 | self._object.hguides.clean() |
|---|
| 883 | |
|---|
| 884 | def clearVGuides(self): |
|---|
| 885 | """Clear all vertical guides.""" |
|---|
| 886 | self._object.vguides.clean() |
|---|
| 887 | |
|---|
| 888 | |
|---|
| 889 | # |
|---|
| 890 | # generators |
|---|
| 891 | # |
|---|
| 892 | |
|---|
| 893 | def generate(self, outputType, path=None): |
|---|
| 894 | """ |
|---|
| 895 | generate the font. outputType is the type of font to ouput. |
|---|
| 896 | --Ouput Types: |
|---|
| 897 | 'pctype1' : PC Type 1 font (binary/PFB) |
|---|
| 898 | 'pcmm' : PC MultipleMaster font (PFB) |
|---|
| 899 | 'pctype1ascii' : PC Type 1 font (ASCII/PFA) |
|---|
| 900 | 'pcmmascii' : PC MultipleMaster font (ASCII/PFA) |
|---|
| 901 | 'unixascii' : UNIX ASCII font (ASCII/PFA) |
|---|
| 902 | 'mactype1' : Mac Type 1 font (generates suitcase and LWFN file) |
|---|
| 903 | 'otfcff' : PS OpenType (CFF-based) font (OTF) |
|---|
| 904 | 'otfttf' : PC TrueType/TT OpenType font (TTF) |
|---|
| 905 | 'macttf' : Mac TrueType font (generates suitcase) |
|---|
| 906 | 'macttdfont' : Mac TrueType font (generates suitcase with resources in data fork) |
|---|
| 907 | (doc adapted from http://dev.fontlab.net/flpydoc/) |
|---|
| 908 | |
|---|
| 909 | path can be a directory or a directory file name combo: |
|---|
| 910 | path="DirectoryA/DirectoryB" |
|---|
| 911 | path="DirectoryA/DirectoryB/MyFontName" |
|---|
| 912 | if no path is given, the file will be output in the same directory |
|---|
| 913 | as the vfb file. if no file name is given, the filename will be the |
|---|
| 914 | vfb file name with the appropriate suffix. |
|---|
| 915 | """ |
|---|
| 916 | outputType = outputType.lower() |
|---|
| 917 | if not _flGenerateTypes.has_key(outputType): |
|---|
| 918 | raise RoboFabError, "%s output type is not supported"%outputType |
|---|
| 919 | flOutputType, suffix = _flGenerateTypes[outputType] |
|---|
| 920 | if path is None: |
|---|
| 921 | filePath, fileName = os.path.split(self.path) |
|---|
| 922 | fileName = fileName.replace('.vfb', '') |
|---|
| 923 | else: |
|---|
| 924 | if os.path.isdir(path): |
|---|
| 925 | filePath = path |
|---|
| 926 | fileName = os.path.split(self.path)[1].replace('.vfb', '') |
|---|
| 927 | else: |
|---|
| 928 | filePath, fileName = os.path.split(path) |
|---|
| 929 | if '.' in fileName: |
|---|
| 930 | raise RoboFabError, "filename cannot contain periods.", fileName |
|---|
| 931 | fileName = '.'.join([fileName, suffix]) |
|---|
| 932 | finalPath = os.path.join(filePath, fileName) |
|---|
| 933 | # generate is (oddly) an application level method |
|---|
| 934 | # rather than a font level method. because of this, |
|---|
| 935 | # the font must be the current font. so, make it so. |
|---|
| 936 | fl.ifont = self.fontIndex |
|---|
| 937 | fl.GenerateFont(flOutputType, finalPath) |
|---|
| 938 | |
|---|
| 939 | def _writeOpenTypeFeaturesToLib(self, fontLib): |
|---|
| 940 | flFont = self.naked() |
|---|
| 941 | if flFont.ot_classes: |
|---|
| 942 | fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings( |
|---|
| 943 | flFont.ot_classes) |
|---|
| 944 | if flFont.features: |
|---|
| 945 | features = {} |
|---|
| 946 | order = [] |
|---|
| 947 | for feature in flFont.features: |
|---|
| 948 | order.append(feature.tag) |
|---|
| 949 | features[feature.tag] = _normalizeLineEndings(feature.value) |
|---|
| 950 | fontLib["org.robofab.opentype.features"] = features |
|---|
| 951 | fontLib["org.robofab.opentype.featureorder"] = order |
|---|
| 952 | |
|---|
| 953 | def writeUFO(self, path=None, doProgress=False, |
|---|
| 954 | glyphNameToFileNameFunc=None): |
|---|
| 955 | """write a font to .ufo""" |
|---|
| 956 | from robofab.ufoLib import makeUFOPath, UFOWriter |
|---|
| 957 | from robofab.interface.all.dialogs import ProgressBar |
|---|
| 958 | if glyphNameToFileNameFunc is None: |
|---|
| 959 | glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() |
|---|
| 960 | if glyphNameToFileNameFunc is None: |
|---|
| 961 | from robofab.tools.glyphNameSchemes import glyphNameToShortFileName |
|---|
| 962 | glyphNameToFileNameFunc = glyphNameToShortFileName |
|---|
| 963 | if not path: |
|---|
| 964 | if self.path is None: |
|---|
| 965 | # XXX this should really raise an exception instead |
|---|
| 966 | from robofab.interface.all.dialogs import Message |
|---|
| 967 | Message("Please save this font first before exporting to UFO...") |
|---|
| 968 | return |
|---|
| 969 | else: |
|---|
| 970 | path = makeUFOPath(self.path) |
|---|
| 971 | doHints = self._supportHints |
|---|
| 972 | nonGlyphCount = 4 |
|---|
| 973 | bar = None |
|---|
| 974 | if doProgress: |
|---|
| 975 | bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self.glyphs)) |
|---|
| 976 | try: |
|---|
| 977 | u = UFOWriter(path) |
|---|
| 978 | u.writeInfo(self.info) |
|---|
| 979 | if bar: |
|---|
| 980 | bar.tick() |
|---|
| 981 | u.writeKerning(self.kerning.asDict()) |
|---|
| 982 | if bar: |
|---|
| 983 | bar.tick() |
|---|
| 984 | u.writeGroups(self.groups) |
|---|
| 985 | if bar: |
|---|
| 986 | bar.tick() |
|---|
| 987 | count = nonGlyphCount |
|---|
| 988 | glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) |
|---|
| 989 | glyphOrder = [] |
|---|
| 990 | for nakedGlyph in self.naked().glyphs: |
|---|
| 991 | glyph = RGlyph(nakedGlyph) |
|---|
| 992 | glyphOrder.append(glyph.name) |
|---|
| 993 | if doHints: |
|---|
| 994 | hintStuff = _glyphHintsToDict(glyph.naked()) |
|---|
| 995 | if hintStuff: |
|---|
| 996 | glyph.lib[postScriptHintDataLibKey] = hintStuff |
|---|
| 997 | glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) |
|---|
| 998 | # remove the hint dict from the lib |
|---|
| 999 | if doHints and glyph.lib.has_key(postScriptHintDataLibKey): |
|---|
| 1000 | del glyph.lib[postScriptHintDataLibKey] |
|---|
| 1001 | if bar and not count % 10: |
|---|
| 1002 | bar.tick(count) |
|---|
| 1003 | count = count + 1 |
|---|
| 1004 | assert None not in glyphOrder, glyphOrder |
|---|
| 1005 | glyphSet.writeContents() |
|---|
| 1006 | # We make a shallow copy if lib, since we add some stuff for export |
|---|
| 1007 | # that doesn't need to be retained in memory. |
|---|
| 1008 | fontLib = dict(self.lib) |
|---|
| 1009 | # Always export the postscript font hint values |
|---|
| 1010 | psh = PostScriptFontHintValues(self) |
|---|
| 1011 | d = psh.asDict() |
|---|
| 1012 | fontLib[postScriptHintDataLibKey] = d |
|---|
| 1013 | # Export the glyph order |
|---|
| 1014 | fontLib["org.robofab.glyphOrder"] = glyphOrder |
|---|
| 1015 | self._writeOpenTypeFeaturesToLib(fontLib) |
|---|
| 1016 | u.writeLib(fontLib) |
|---|
| 1017 | if bar: |
|---|
| 1018 | bar.tick() |
|---|
| 1019 | except KeyboardInterrupt: |
|---|
| 1020 | if bar: |
|---|
| 1021 | bar.close() |
|---|
| 1022 | bar = None |
|---|
| 1023 | if bar: |
|---|
| 1024 | bar.close() |
|---|
| 1025 | |
|---|
| 1026 | def _getGlyphOrderFromLib(self, fontLib, glyphSet): |
|---|
| 1027 | glyphOrder = fontLib.get("org.robofab.glyphOrder") |
|---|
| 1028 | if glyphOrder is not None: |
|---|
| 1029 | # no need to keep track if the glyph order in lib once the font is loaded. |
|---|
| 1030 | del fontLib["org.robofab.glyphOrder"] |
|---|
| 1031 | glyphNames = [] |
|---|
| 1032 | done = {} |
|---|
| 1033 | for glyphName in glyphOrder: |
|---|
| 1034 | if glyphName in glyphSet: |
|---|
| 1035 | glyphNames.append(glyphName) |
|---|
| 1036 | done[glyphName] = 1 |
|---|
| 1037 | allGlyphNames = glyphSet.keys() |
|---|
| 1038 | allGlyphNames.sort() |
|---|
| 1039 | for glyphName in allGlyphNames: |
|---|
| 1040 | if glyphName not in done: |
|---|
| 1041 | glyphNames.append(glyphName) |
|---|
| 1042 | else: |
|---|
| 1043 | glyphNames = glyphSet.keys() |
|---|
| 1044 | # Sort according to unicode would be best, but is really |
|---|
| 1045 | # expensive... |
|---|
| 1046 | glyphNames.sort() |
|---|
| 1047 | return glyphNames |
|---|
| 1048 | |
|---|
| 1049 | def _readOpenTypeFeaturesFromLib(self, fontLib): |
|---|
| 1050 | classes = fontLib.get("org.robofab.opentype.classes") |
|---|
| 1051 | if classes is not None: |
|---|
| 1052 | del fontLib["org.robofab.opentype.classes"] |
|---|
| 1053 | self.naked().ot_classes = classes |
|---|
| 1054 | features = fontLib.get("org.robofab.opentype.features") |
|---|
| 1055 | if features is not None: |
|---|
| 1056 | order = fontLib.get("org.robofab.opentype.featureorder") |
|---|
| 1057 | if order is None: |
|---|
| 1058 | # for UFOs saved without the feature order, do the same as before. |
|---|
| 1059 | order = features.keys() |
|---|
| 1060 | order.sort() |
|---|
| 1061 | else: |
|---|
| 1062 | del fontLib["org.robofab.opentype.featureorder"] |
|---|
| 1063 | del fontLib["org.robofab.opentype.features"] |
|---|
| 1064 | #features = features.items() |
|---|
| 1065 | orderedFeatures = [] |
|---|
| 1066 | for tag in order: |
|---|
| 1067 | oneFeature = features.get(tag) |
|---|
| 1068 | if oneFeature is not None: |
|---|
| 1069 | orderedFeatures.append((tag, oneFeature)) |
|---|
| 1070 | self.naked().features.clean() |
|---|
| 1071 | for tag, src in orderedFeatures: |
|---|
| 1072 | self.naked().features.append(Feature(tag, src)) |
|---|
| 1073 | |
|---|
| 1074 | def readUFO(self, path, doProgress=False): |
|---|
| 1075 | """read a .ufo into the font""" |
|---|
| 1076 | from robofab.ufoLib import UFOReader |
|---|
| 1077 | from robofab.pens.flPen import FLPointPen |
|---|
| 1078 | from robofab.interface.all.dialogs import ProgressBar |
|---|
| 1079 | doHints = self._supportHints |
|---|
| 1080 | nonGlyphCount = 4 |
|---|
| 1081 | bar = None |
|---|
| 1082 | u = UFOReader(path) |
|---|
| 1083 | glyphSet = u.getGlyphSet() |
|---|
| 1084 | fontLib = u.readLib() |
|---|
| 1085 | glyphNames = self._getGlyphOrderFromLib(fontLib, glyphSet) |
|---|
| 1086 | if doProgress: |
|---|
| 1087 | bar = ProgressBar('Importing UFO', nonGlyphCount+len(glyphNames)) |
|---|
| 1088 | try: |
|---|
| 1089 | u.readInfo(self.info) |
|---|
| 1090 | if bar: |
|---|
| 1091 | bar.tick() |
|---|
| 1092 | self._readOpenTypeFeaturesFromLib(fontLib) |
|---|
| 1093 | self.lib.clear() |
|---|
| 1094 | self.lib = fontLib |
|---|
| 1095 | if bar: |
|---|
| 1096 | bar.tick() |
|---|
| 1097 | count = 2 |
|---|
| 1098 | for glyphName in glyphNames: |
|---|
| 1099 | glyph = self.newGlyph(glyphName, clear=True) |
|---|
| 1100 | pen = FLPointPen(glyph.naked()) |
|---|
| 1101 | glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) |
|---|
| 1102 | if doHints: |
|---|
| 1103 | hintData = glyph.lib.get(postScriptHintDataLibKey) |
|---|
| 1104 | if hintData: |
|---|
| 1105 | _dictHintsToGlyph(glyph.naked(), hintData) |
|---|
| 1106 | # now that the hints have been extracted from the glyph |
|---|
| 1107 | # there is no reason to keep the location in the lib. |
|---|
| 1108 | if glyph.lib.has_key(postScriptHintDataLibKey): |
|---|
| 1109 | del glyph.lib[postScriptHintDataLibKey] |
|---|
| 1110 | glyph.update() |
|---|
| 1111 | if bar and not count % 10: |
|---|
| 1112 | bar.tick(count) |
|---|
| 1113 | count = count + 1 |
|---|
| 1114 | # import postscript font hint data |
|---|
| 1115 | getPostScriptHintDataFromLib(self, fontLib) |
|---|
| 1116 | self.kerning.clear() |
|---|
| 1117 | self.kerning.update(u.readKerning()) |
|---|
| 1118 | if bar: |
|---|
| 1119 | bar.tick() |
|---|
| 1120 | self.groups.clear() |
|---|
| 1121 | self.groups = u.readGroups() |
|---|
| 1122 | except KeyboardInterrupt: |
|---|
| 1123 | bar.close() |
|---|
| 1124 | bar = None |
|---|
| 1125 | if bar: |
|---|
| 1126 | bar.close() |
|---|
| 1127 | |
|---|
| 1128 | |
|---|
| 1129 | class RGlyph(BaseGlyph): |
|---|
| 1130 | """RoboFab wrapper for FL Glyph object""" |
|---|
| 1131 | |
|---|
| 1132 | _title = "FLGlyph" |
|---|
| 1133 | |
|---|
| 1134 | def __init__(self, flGlyph): |
|---|
| 1135 | #BaseGlyph.__init__(self) |
|---|
| 1136 | if flGlyph is None: |
|---|
| 1137 | raise RoboFabError, "RGlyph: there's nothing to wrap!?" |
|---|
| 1138 | self._object = flGlyph |
|---|
| 1139 | self._lib = {} |
|---|
| 1140 | self._contours = None |
|---|
| 1141 | |
|---|
| 1142 | def __getitem__(self, index): |
|---|
| 1143 | return self.contours[index] |
|---|
| 1144 | |
|---|
| 1145 | def __delitem__(self, index): |
|---|
| 1146 | self._object.DeleteContour(index) |
|---|
| 1147 | self._invalidateContours() |
|---|
| 1148 | |
|---|
| 1149 | def __len__(self): |
|---|
| 1150 | return len(self.contours) |
|---|
| 1151 | |
|---|
| 1152 | lib = property(_get_lib, _set_lib, doc="glyph lib object") |
|---|
| 1153 | |
|---|
| 1154 | def _invalidateContours(self): |
|---|
| 1155 | self._contours = None |
|---|
| 1156 | |
|---|
| 1157 | def _buildContours(self): |
|---|
| 1158 | self._contours = [] |
|---|
| 1159 | for contourIndex in range(self._object.GetContoursNumber()): |
|---|
| 1160 | c = RContour(contourIndex) |
|---|
| 1161 | c.setParent(self) |
|---|
| 1162 | c._buildSegments() |
|---|
| 1163 | self._contours.append(c) |
|---|
| 1164 | |
|---|
| 1165 | # |
|---|
| 1166 | # attribute handlers |
|---|
| 1167 | # |
|---|
| 1168 | |
|---|
| 1169 | def _get_index(self): |
|---|
| 1170 | return self._object.parent.FindGlyph(self.name) |
|---|
| 1171 | |
|---|
| 1172 | index = property(_get_index, doc="return the index of the glyph in the font") |
|---|
| 1173 | |
|---|
| 1174 | def _get_name(self): |
|---|
| 1175 | return self._object.name |
|---|
| 1176 | |
|---|
| 1177 | def _set_name(self, value): |
|---|
| 1178 | self._object.name = value |
|---|
| 1179 | |
|---|
| 1180 | name = property(_get_name, _set_name, doc="name") |
|---|
| 1181 | |
|---|
| 1182 | def _get_psName(self): |
|---|
| 1183 | return self._object.name |
|---|
| 1184 | |
|---|
| 1185 | def _set_psName(self, value): |
|---|
| 1186 | self._object.name = value |
|---|
| 1187 | |
|---|
| 1188 | psName = property(_get_psName, _set_psName, doc="name") |
|---|
| 1189 | |
|---|
| 1190 | def _get_baseName(self): |
|---|
| 1191 | return self._object.name.split('.')[0] |
|---|
| 1192 | |
|---|
| 1193 | baseName = property(_get_baseName, doc="") |
|---|
| 1194 | |
|---|
| 1195 | def _get_unicode(self): |
|---|
| 1196 | return self._object.unicode |
|---|
| 1197 | |
|---|
| 1198 | def _set_unicode(self, value): |
|---|
| 1199 | self._object.unicode = value |
|---|
| 1200 | |
|---|
| 1201 | unicode = property(_get_unicode, _set_unicode, doc="unicode") |
|---|
| 1202 | |
|---|
| 1203 | def _get_unicodes(self): |
|---|
| 1204 | return self._object.unicodes |
|---|
| 1205 | |
|---|
| 1206 | def _set_unicodes(self, value): |
|---|
| 1207 | self._object.unicodes = value |
|---|
| 1208 | |
|---|
| 1209 | unicodes = property(_get_unicodes, _set_unicodes, doc="unicodes") |
|---|
| 1210 | |
|---|
| 1211 | def _get_width(self): |
|---|
| 1212 | return self._object.width |
|---|
| 1213 | |
|---|
| 1214 | def _set_width(self, value): |
|---|
| 1215 | value = int(round(value)) |
|---|
| 1216 | self._object.width = value |
|---|
| 1217 | |
|---|
| 1218 | width = property(_get_width, _set_width, doc="the width") |
|---|
| 1219 | |
|---|
| 1220 | def _get_box(self): |
|---|
| 1221 | if not len(self.contours) and not len(self.components): |
|---|
| 1222 | return (0, 0, 0, 0) |
|---|
| 1223 | r = self._object.GetBoundingRect() |
|---|
| 1224 | return (int(round(r.ll.x)), int(round(r.ll.y)), int(round(r.ur.x)), int(round(r.ur.y))) |
|---|
| 1225 | |
|---|
| 1226 | box = property(_get_box, doc="box of glyph as a tuple (xMin, yMin, xMax, yMax)") |
|---|
| 1227 | |
|---|
| 1228 | def _get_selected(self): |
|---|
| 1229 | if fl.Selected(self._object.parent.FindGlyph(self._object.name)): |
|---|
| 1230 | return 1 |
|---|
| 1231 | else: |
|---|
| 1232 | return 0 |
|---|
| 1233 | |
|---|
| 1234 | def _set_selected(self, value): |
|---|
| 1235 | fl.Select(self._object.name, value) |
|---|
| 1236 | |
|---|
| 1237 | selected = property(_get_selected, _set_selected, doc="Select or deselect the glyph in the font window") |
|---|
| 1238 | |
|---|
| 1239 | def _get_mark(self): |
|---|
| 1240 | return self._object.mark |
|---|
| 1241 | |
|---|
| 1242 | def _set_mark(self, value): |
|---|
| 1243 | self._object.mark = value |
|---|
| 1244 | |
|---|
| 1245 | mark = property(_get_mark, _set_mark, doc="mark") |
|---|
| 1246 | |
|---|
| 1247 | def _get_note(self): |
|---|
| 1248 | s = self._object.note |
|---|
| 1249 | if s is None: |
|---|
| 1250 | return s |
|---|
| 1251 | return unicode(s, LOCAL_ENCODING) |
|---|
| 1252 | |
|---|
| 1253 | def _set_note(self, value): |
|---|
| 1254 | if value is None: |
|---|
| 1255 | value = '' |
|---|
| 1256 | if type(value) == type(u""): |
|---|
| 1257 | value = value.encode(LOCAL_ENCODING) |
|---|
| 1258 | self._object.note = value |
|---|
| 1259 | |
|---|
| 1260 | note = property(_get_note, _set_note, doc="note") |
|---|
| 1261 | |
|---|
| 1262 | #def _get_psHints(self): |
|---|
| 1263 | # # get an object representing the postscript zone information |
|---|
| 1264 | # # thes |
|---|
| 1265 | # raise NotImplementedError |
|---|
| 1266 | |
|---|
| 1267 | #psHints = property(_get_psHints, doc="postscript hint data") |
|---|
| 1268 | |
|---|
| 1269 | # |
|---|
| 1270 | # necessary evil |
|---|
| 1271 | # |
|---|
| 1272 | |
|---|
| 1273 | def update(self): |
|---|
| 1274 | """Don't forget to update the glyph when you are done.""" |
|---|
| 1275 | fl.UpdateGlyph(self._object.parent.FindGlyph(self._object.name)) |
|---|
| 1276 | |
|---|
| 1277 | # |
|---|
| 1278 | # methods to make RGlyph compatible with FL.Glyph |
|---|
| 1279 | # ##are these still needed? |
|---|
| 1280 | # |
|---|
| 1281 | |
|---|
| 1282 | def GetBoundingRect(self, masterIndex): |
|---|
| 1283 | """FL compatibility""" |
|---|
| 1284 | return self._object.GetBoundingRect(masterIndex) |
|---|
| 1285 | |
|---|
| 1286 | def GetMetrics(self, masterIndex): |
|---|
| 1287 | """FL compatibility""" |
|---|
| 1288 | return self._object.GetMetrics(masterIndex) |
|---|
| 1289 | |
|---|
| 1290 | def SetMetrics(self, value, masterIndex): |
|---|
| 1291 | """FL compatibility""" |
|---|
| 1292 | return self._object.SetMetrics(value, masterIndex) |
|---|
| 1293 | |
|---|
| 1294 | # |
|---|
| 1295 | # object builders |
|---|
| 1296 | # |
|---|
| 1297 | |
|---|
| 1298 | def _get_anchors(self): |
|---|
| 1299 | return self.getAnchors() |
|---|
| 1300 | |
|---|
| 1301 | anchors = property(_get_anchors, doc="allow for iteration through glyph.anchors") |
|---|
| 1302 | |
|---|
| 1303 | def _get_components(self): |
|---|
| 1304 | return self.getComponents() |
|---|
| 1305 | |
|---|
| 1306 | components = property(_get_components, doc="allow for iteration through glyph.components") |
|---|
| 1307 | |
|---|
| 1308 | def _get_contours(self): |
|---|
| 1309 | if self._contours is None: |
|---|
| 1310 | self._buildContours() |
|---|
| 1311 | return self._contours |
|---|
| 1312 | |
|---|
| 1313 | contours = property(_get_contours, doc="allow for iteration through glyph.contours") |
|---|
| 1314 | |
|---|
| 1315 | def getAnchors(self): |
|---|
| 1316 | """Return a list of wrapped anchors in this RGlyph.""" |
|---|
| 1317 | anchors=[] |
|---|
| 1318 | for i in range(len(self._object.anchors)): |
|---|
| 1319 | a = RAnchor(self._object.anchors[i], i) |
|---|
| 1320 | a.setParent(self) |
|---|
| 1321 | anchors.append(a) |
|---|
| 1322 | return anchors |
|---|
| 1323 | |
|---|
| 1324 | def getComponents(self): |
|---|
| 1325 | """Return a list of wrapped components in this RGlyph.""" |
|---|
| 1326 | components=[] |
|---|
| 1327 | for i in range(len(self._object.components)): |
|---|
| 1328 | c = RComponent(self._object.components[i], i) |
|---|
| 1329 | c.setParent(self) |
|---|
| 1330 | components.append(c) |
|---|
| 1331 | return components |
|---|
| 1332 | |
|---|
| 1333 | def getVGuides(self): |
|---|
| 1334 | """Return a list of wrapped vertical guides in this RGlyph""" |
|---|
| 1335 | vguides=[] |
|---|
| 1336 | for i in range(len(self._object.vguides)): |
|---|
| 1337 | g = RGuide(self._object.vguides[i], i) |
|---|
| 1338 | g.setParent(self) |
|---|
| 1339 | vguides.append(g) |
|---|
| 1340 | return vguides |
|---|
| 1341 | |
|---|
| 1342 | def getHGuides(self): |
|---|
| 1343 | """Return a list of wrapped horizontal guides in this RGlyph""" |
|---|
| 1344 | hguides=[] |
|---|
| 1345 | for i in range(len(self._object.hguides)): |
|---|
| 1346 | g = RGuide(self._object.hguides[i], i) |
|---|
| 1347 | g.setParent(self) |
|---|
| 1348 | hguides.append(g) |
|---|
| 1349 | return hguides |
|---|
| 1350 | |
|---|
| 1351 | # |
|---|
| 1352 | # tools |
|---|
| 1353 | # |
|---|
| 1354 | |
|---|
| 1355 | def getPointPen(self): |
|---|
| 1356 | self._invalidateContours() |
|---|
| 1357 | # Now just don't muck with glyph.contours before you're done drawing... |
|---|
| 1358 | return FLPointPen(self) |
|---|
| 1359 | |
|---|
| 1360 | def appendComponent(self, baseGlyph, offset=(0, 0), scale=(1, 1)): |
|---|
| 1361 | """Append a component to the glyph. x and y are optional offset values""" |
|---|
| 1362 | offset = roundPt((offset[0], offset[1])) |
|---|
| 1363 | p = FLPointPen(self.naked()) |
|---|
| 1364 | xx, yy = scale |
|---|
| 1365 | dx, dy = offset |
|---|
| 1366 | p.addComponent(baseGlyph, (xx, 0, 0, yy, dx, dy)) |
|---|
| 1367 | |
|---|
| 1368 | def appendAnchor(self, name, position): |
|---|
| 1369 | """Append an anchor to the glyph""" |
|---|
| 1370 | value = roundPt((position[0], position[1])) |
|---|
| 1371 | anchor = Anchor(name, value[0], value[1]) |
|---|
| 1372 | self._object.anchors.append(anchor) |
|---|
| 1373 | |
|---|
| 1374 | def appendHGuide(self, position, angle=0): |
|---|
| 1375 | """Append a horizontal guide""" |
|---|
| 1376 | position = int(round(position)) |
|---|
| 1377 | g = Guide(position, angle) |
|---|
| 1378 | self._object.hguides.append(g) |
|---|
| 1379 | |
|---|
| 1380 | def appendVGuide(self, position, angle=0): |
|---|
| 1381 | """Append a horizontal guide""" |
|---|
| 1382 | position = int(round(position)) |
|---|
| 1383 | g = Guide(position, angle) |
|---|
| 1384 | self._object.vguides.append(g) |
|---|
| 1385 | |
|---|
| 1386 | def clearComponents(self): |
|---|
| 1387 | """Clear all components.""" |
|---|
| 1388 | self._object.components.clean() |
|---|
| 1389 | |
|---|
| 1390 | def clearAnchors(self): |
|---|
| 1391 | """Clear all anchors.""" |
|---|
| 1392 | self._object.anchors.clean() |
|---|
| 1393 | |
|---|
| 1394 | def clearHGuides(self): |
|---|
| 1395 | """Clear all horizontal guides.""" |
|---|
| 1396 | self._object.hguides.clean() |
|---|
| 1397 | |
|---|
| 1398 | def clearVGuides(self): |
|---|
| 1399 | """Clear all vertical guides.""" |
|---|
| 1400 | self._object.vguides.clean() |
|---|
| 1401 | |
|---|
| 1402 | def removeComponent(self, component): |
|---|
| 1403 | """Remove a specific component from the glyph. This only works |
|---|
| 1404 | if the glyph does not have duplicate components in the same location.""" |
|---|
| 1405 | pos = (component.baseGlyph, component.offset, component.scale) |
|---|
| 1406 | a = self.getComponents() |
|---|
| 1407 | found = [] |
|---|
| 1408 | for i in a: |
|---|
| 1409 | if (i.baseGlyph, i.offset, i.scale) == pos: |
|---|
| 1410 | found.append(i) |
|---|
| 1411 | if len(found) > 1: |
|---|
| 1412 | raise RoboFabError, 'Found more than one possible component to remove' |
|---|
| 1413 | elif len(found) == 1: |
|---|
| 1414 | del self._object.components[found[0].index] |
|---|
| 1415 | else: |
|---|
| 1416 | raise RoboFabError, 'Component does not exist' |
|---|
| 1417 | |
|---|
| 1418 | def removeContour(self, index): |
|---|
| 1419 | """remove a specific contour from the glyph""" |
|---|
| 1420 | self._object.DeleteContour(index) |
|---|
| 1421 | self._invalidateContours() |
|---|
| 1422 | |
|---|
| 1423 | def removeAnchor(self, anchor): |
|---|
| 1424 | """Remove a specific anchor from the glyph. This only works |
|---|
| 1425 | if the glyph does not have anchors with duplicate names |
|---|
| 1426 | in exactly the same location with the same mark.""" |
|---|
| 1427 | pos = (anchor.name, anchor.position, anchor.mark) |
|---|
| 1428 | a = self.getAnchors() |
|---|
| 1429 | found = [] |
|---|
| 1430 | for i in a: |
|---|
| 1431 | if (i.name, i.position, i.mark) == pos: |
|---|
| 1432 | found.append(i) |
|---|
| 1433 | if len(found) > 1: |
|---|
| 1434 | raise RoboFabError, 'Found more than one possible anchor to remove' |
|---|
| 1435 | elif len(found) == 1: |
|---|
| 1436 | del self._object.anchors[found[0].index] |
|---|
| 1437 | else: |
|---|
| 1438 | raise RoboFabError, 'Anchor does not exist' |
|---|
| 1439 | |
|---|
| 1440 | def removeHGuide(self, guide): |
|---|
| 1441 | """Remove a horizontal guide.""" |
|---|
| 1442 | pos = (guide.position, guide.angle) |
|---|
| 1443 | for g in self.getHGuides(): |
|---|
| 1444 | if (g.position, g.angle) == pos: |
|---|
| 1445 | del self._object.hguides[g.index] |
|---|
| 1446 | break |
|---|
| 1447 | |
|---|
| 1448 | def removeVGuide(self, guide): |
|---|
| 1449 | """Remove a vertical guide.""" |
|---|
| 1450 | pos = (guide.position, guide.angle) |
|---|
| 1451 | for g in self.getVGuides(): |
|---|
| 1452 | if (g.position, g.angle) == pos: |
|---|
| 1453 | del self._object.vguides[g.index] |
|---|
| 1454 | break |
|---|
| 1455 | |
|---|
| 1456 | def center(self, padding=None): |
|---|
| 1457 | """Equalise sidebearings, set to padding if wanted.""" |
|---|
| 1458 | left = self.leftMargin |
|---|
| 1459 | right = self.rightMargin |
|---|
| 1460 | if padding: |
|---|
| 1461 | e_left = e_right = padding |
|---|
| 1462 | else: |
|---|
| 1463 | e_left = (left + right)/2 |
|---|
| 1464 | e_right = (left + right) - e_left |
|---|
| 1465 | self.leftMargin= e_left |
|---|
| 1466 | self.rightMargin= e_right |
|---|
| 1467 | |
|---|
| 1468 | def removeOverlap(self): |
|---|
| 1469 | """Remove overlap""" |
|---|
| 1470 | self._object.RemoveOverlap() |
|---|
| 1471 | self._invalidateContours() |
|---|
| 1472 | |
|---|
| 1473 | def decompose(self): |
|---|
| 1474 | """Decompose all components""" |
|---|
| 1475 | self._object.Decompose() |
|---|
| 1476 | self._invalidateContours() |
|---|
| 1477 | |
|---|
| 1478 | ##broken! |
|---|
| 1479 | #def removeHints(self): |
|---|
| 1480 | # """Remove the hints.""" |
|---|
| 1481 | # self._object.RemoveHints() |
|---|
| 1482 | |
|---|
| 1483 | def autoHint(self): |
|---|
| 1484 | """Automatically generate type 1 hints.""" |
|---|
| 1485 | self._object.Autohint() |
|---|
| 1486 | |
|---|
| 1487 | def move(self, (x, y), contours=True, components=True, anchors=True): |
|---|
| 1488 | """Move a glyph's items that are flagged as True""" |
|---|
| 1489 | x, y = roundPt((x, y)) |
|---|
| 1490 | self._object.Shift(Point(x, y)) |
|---|
| 1491 | for c in self.getComponents(): |
|---|
| 1492 | c.move((x, y)) |
|---|
| 1493 | for a in self.getAnchors(): |
|---|
| 1494 | a.move((x, y)) |
|---|
| 1495 | |
|---|
| 1496 | def clear(self, contours=True, components=True, anchors=True, guides=True, hints=True): |
|---|
| 1497 | """Clear all items marked as true from the glyph""" |
|---|
| 1498 | if contours: |
|---|
| 1499 | self._object.Clear() |
|---|
| 1500 | self._invalidateContours() |
|---|
| 1501 | if components: |
|---|
| 1502 | self._object.components.clean() |
|---|
| 1503 | if anchors: |
|---|
| 1504 | self._object.anchors.clean() |
|---|
| 1505 | if guides: |
|---|
| 1506 | self._object.hguides.clean() |
|---|
| 1507 | self._object.vguides.clean() |
|---|
| 1508 | if hints: |
|---|
| 1509 | # RemoveHints requires an "integer mode" argument |
|---|
| 1510 | # but it is not documented. from some simple experiments |
|---|
| 1511 | # i deduced that |
|---|
| 1512 | # 1 = horizontal hints and links, |
|---|
| 1513 | # 2 = vertical hints and links |
|---|
| 1514 | # 3 = all hints and links |
|---|
| 1515 | self._object.RemoveHints(3) |
|---|
| 1516 | |
|---|
| 1517 | # |
|---|
| 1518 | # special treatment for GlyphMath support in FontLab |
|---|
| 1519 | # |
|---|
| 1520 | |
|---|
| 1521 | def _getMathDestination(self): |
|---|
| 1522 | from robofab.objects.objectsRF import RGlyph as _RGlyph |
|---|
| 1523 | return _RGlyph() |
|---|
| 1524 | |
|---|
| 1525 | def copy(self, aParent=None): |
|---|
| 1526 | """Make a copy of this glyph. |
|---|
| 1527 | Note: the copy is not a duplicate fontlab glyph, but |
|---|
| 1528 | a RF RGlyph with the same outlines. The new glyph is |
|---|
| 1529 | not part of the fontlab font in any way. Use font.appendGlyph(glyph) |
|---|
| 1530 | to get it in a FontLab glyph again.""" |
|---|
| 1531 | from robofab.objects.objectsRF import RGlyph as _RGlyph |
|---|
| 1532 | newGlyph = _RGlyph() |
|---|
| 1533 | newGlyph.appendGlyph(self) |
|---|
| 1534 | for attr in GLYPH_COPY_ATTRS: |
|---|
| 1535 | value = getattr(self, attr) |
|---|
| 1536 | setattr(newGlyph, attr, value) |
|---|
| 1537 | # hints |
|---|
| 1538 | doHints = False |
|---|
| 1539 | parent = self.getParent() |
|---|
| 1540 | if parent is not None and parent._supportHints: |
|---|
| 1541 | hintStuff = _glyphHintsToDict(self.naked()) |
|---|
| 1542 | if hintStuff: |
|---|
| 1543 | newGlyph.lib[postScriptHintDataLibKey] = hintStuff |
|---|
| 1544 | if aParent is not None: |
|---|
| 1545 | newGlyph.setParent(aParent) |
|---|
| 1546 | elif self.getParent() is not None: |
|---|
| 1547 | newGlyph.setParent(self.getParent()) |
|---|
| 1548 | return newGlyph |
|---|
| 1549 | |
|---|
| 1550 | def __mul__(self, factor): |
|---|
| 1551 | return self.copy() *factor |
|---|
| 1552 | |
|---|
| 1553 | __rmul__ = __mul__ |
|---|
| 1554 | |
|---|
| 1555 | def __sub__(self, other): |
|---|
| 1556 | return self.copy() - other.copy() |
|---|
| 1557 | |
|---|
| 1558 | def __add__(self, other): |
|---|
| 1559 | return self.copy() + other.copy() |
|---|
| 1560 | |
|---|
| 1561 | |
|---|
| 1562 | |
|---|
| 1563 | class RContour(BaseContour): |
|---|
| 1564 | |
|---|
| 1565 | """RoboFab wrapper for non FL contour object""" |
|---|
| 1566 | |
|---|
| 1567 | _title = "FLContour" |
|---|
| 1568 | |
|---|
| 1569 | def __init__(self, index): |
|---|
| 1570 | self._index = index |
|---|
| 1571 | self._parentGlyph = None |
|---|
| 1572 | self.segments = [] |
|---|
| 1573 | |
|---|
| 1574 | def __len__(self): |
|---|
| 1575 | return len(self.points) |
|---|
| 1576 | |
|---|
| 1577 | def _buildSegments(self): |
|---|
| 1578 | ####################### |
|---|
| 1579 | # Notes about FL node contour structure |
|---|
| 1580 | ####################### |
|---|
| 1581 | # for TT curves, FL lists them as seperate nodes: |
|---|
| 1582 | # [move, off, off, off, line, off, off] |
|---|
| 1583 | # and, this list is sequential. after the last on curve, |
|---|
| 1584 | # it is possible (and likely) that there will be more offCurves |
|---|
| 1585 | # in our segment object, these should be associated with the |
|---|
| 1586 | # first segment in the contour. |
|---|
| 1587 | # |
|---|
| 1588 | # for PS curves, it is a very different scenerio. |
|---|
| 1589 | # curve nodes contain points: |
|---|
| 1590 | # [on, off, off] |
|---|
| 1591 | # and the list is not in sequential order. the first point in |
|---|
| 1592 | # the list is the on curve and the subsequent points are the off |
|---|
| 1593 | # curve points leading up to that on curve. |
|---|
| 1594 | # |
|---|
| 1595 | # it is very important to remember these structures when trying |
|---|
| 1596 | # to understand the code below |
|---|
| 1597 | |
|---|
| 1598 | self.segments = [] |
|---|
| 1599 | offList = [] |
|---|
| 1600 | nodes = self._nakedParent.nodes |
|---|
| 1601 | for index in range(self._nodeLength): |
|---|
| 1602 | x = index + self._startNodeIndex |
|---|
| 1603 | node = nodes[x] |
|---|
| 1604 | # we do have a loose off curve. deal with it. |
|---|
| 1605 | if node.type == flOFFCURVE: |
|---|
| 1606 | offList.append(x) |
|---|
| 1607 | # we are not dealing with a loose off curve |
|---|
| 1608 | else: |
|---|
| 1609 | s = RSegment(x) |
|---|
| 1610 | s.setParent(self) |
|---|
| 1611 | # but do we have a collection of loose off curves above? |
|---|
| 1612 | # if so, apply them to the segment, and clear the list |
|---|
| 1613 | if len(offList) != 0: |
|---|
| 1614 | s._looseOffCurve = offList |
|---|
| 1615 | offList = [] |
|---|
| 1616 | self.segments.append(s) |
|---|
| 1617 | # do we have some off curves now that the contour is complete? |
|---|
| 1618 | if len(offList) != 0: |
|---|
| 1619 | # ugh. apply them to the first segment |
|---|
| 1620 | self.segments[0]._looseOffCurve = offList |
|---|
| 1621 | |
|---|
| 1622 | def setParent(self, parentGlyph): |
|---|
| 1623 | self._parentGlyph = parentGlyph |
|---|
| 1624 | |
|---|
| 1625 | def getParent(self): |
|---|
| 1626 | return self._parentGlyph |
|---|
| 1627 | |
|---|
| 1628 | def _get__nakedParent(self): |
|---|
| 1629 | return self._parentGlyph.naked() |
|---|
| 1630 | |
|---|
| 1631 | _nakedParent = property(_get__nakedParent, doc="") |
|---|
| 1632 | |
|---|
| 1633 | def _get__startNodeIndex(self): |
|---|
| 1634 | return self._nakedParent.GetContourBegin(self._index) |
|---|
| 1635 | |
|---|
| 1636 | _startNodeIndex = property(_get__startNodeIndex, doc="") |
|---|
| 1637 | |
|---|
| 1638 | def _get__nodeLength(self): |
|---|
| 1639 | return self._nakedParent.GetContourLength(self._index) |
|---|
| 1640 | |
|---|
| 1641 | _nodeLength = property(_get__nodeLength, doc="") |
|---|
| 1642 | |
|---|
| 1643 | def _get__lastNodeIndex(self): |
|---|
| 1644 | return self._startNodeIndex + self._nodeLength - 1 |
|---|
| 1645 | |
|---|
| 1646 | _lastNodeIndex = property(_get__lastNodeIndex, doc="") |
|---|
| 1647 | |
|---|
| 1648 | def _previousNodeIndex(self, index): |
|---|
| 1649 | return (index - 1) % self._nodeLength |
|---|
| 1650 | |
|---|
| 1651 | def _nextNodeIndex(self, index): |
|---|
| 1652 | return (index + 1) % self._nodeLength |
|---|
| 1653 | |
|---|
| 1654 | def _getNode(self, index): |
|---|
| 1655 | return self._nodes[index] |
|---|
| 1656 | |
|---|
| 1657 | def _get__nodes(self): |
|---|
| 1658 | nodes = [] |
|---|
| 1659 | for node in self._nakedParent.nodes[self._startNodeIndex:self._startNodeIndex+self._nodeLength-1]: |
|---|
| 1660 | nodes.append(node) |
|---|
| 1661 | return nodes |
|---|
| 1662 | |
|---|
| 1663 | _nodes = property(_get__nodes, doc="") |
|---|
| 1664 | |
|---|
| 1665 | def _get_points(self): |
|---|
| 1666 | points = [] |
|---|
| 1667 | for segment in self.segments: |
|---|
| 1668 | for point in segment.points: |
|---|
| 1669 | points.append(point) |
|---|
| 1670 | return points |
|---|
| 1671 | |
|---|
| 1672 | points = property(_get_points, doc="") |
|---|
| 1673 | |
|---|
| 1674 | def _get_bPoints(self): |
|---|
| 1675 | bPoints = [] |
|---|
| 1676 | for segment in self.segments: |
|---|
| 1677 | bp = RBPoint(segment.index) |
|---|
| 1678 | bp.setParent(self) |
|---|
| 1679 | bPoints.append(bp) |
|---|
| 1680 | return bPoints |
|---|
| 1681 | |
|---|
| 1682 | bPoints = property(_get_bPoints, doc="") |
|---|
| 1683 | |
|---|
| 1684 | def _get_index(self): |
|---|
| 1685 | return self._index |
|---|
| 1686 | |
|---|
| 1687 | def _set_index(self, index): |
|---|
| 1688 | if index != self._index: |
|---|
| 1689 | self._nakedParent.ReorderContour(self._index, index) |
|---|
| 1690 | # reorder and set the _index of the existing RContour objects |
|---|
| 1691 | # this will be a better solution than reconstructing all the objects |
|---|
| 1692 | # segment objects will still, sadly, have to be reconstructed |
|---|
| 1693 | contourList = self.getParent().contours |
|---|
| 1694 | contourList.insert(index, contourList.pop(self._index)) |
|---|
| 1695 | for i in range(len(contourList)): |
|---|
| 1696 | contourList[i]._index = i |
|---|
| 1697 | contourList[i]._buildSegments() |
|---|
| 1698 | |
|---|
| 1699 | |
|---|
| 1700 | index = property(_get_index, _set_index, doc="the index of the contour") |
|---|
| 1701 | |
|---|
| 1702 | def _get_selected(self): |
|---|
| 1703 | selected = 0 |
|---|
| 1704 | nodes = self._nodes |
|---|
| 1705 | for node in nodes: |
|---|
| 1706 | if node.selected == 1: |
|---|
| 1707 | selected = 1 |
|---|
| 1708 | break |
|---|
| 1709 | return selected |
|---|
| 1710 | |
|---|
| 1711 | def _set_selected(self, value): |
|---|
| 1712 | if value == 1: |
|---|
| 1713 | self._nakedParent.SelectContour(self._index) |
|---|
| 1714 | else: |
|---|
| 1715 | for node in self._nodes: |
|---|
| 1716 | node.selected = value |
|---|
| 1717 | |
|---|
| 1718 | selected = property(_get_selected, _set_selected, doc="selection of the contour: 1-selected or 0-unselected") |
|---|
| 1719 | |
|---|
| 1720 | def appendSegment(self, segmentType, points, smooth=False): |
|---|
| 1721 | segment = self.insertSegment(index=self._nodeLength, segmentType=segmentType, points=points, smooth=smooth) |
|---|
| 1722 | return segment |
|---|
| 1723 | |
|---|
| 1724 | def insertSegment(self, index, segmentType, points, smooth=False): |
|---|
| 1725 | """insert a seggment into the contour""" |
|---|
| 1726 | # do a qcurve insertion |
|---|
| 1727 | if segmentType == QCURVE: |
|---|
| 1728 | count = 0 |
|---|
| 1729 | for point in points[:-1]: |
|---|
| 1730 | newNode = Node(flOFFCURVE, Point(point[0], point[1])) |
|---|
| 1731 | self._nakedParent.Insert(newNode, self._startNodeIndex + index + count) |
|---|
| 1732 | count = count + 1 |
|---|
| 1733 | newNode = Node(flLINE, Point(points[-1][0], points[-1][1])) |
|---|
| 1734 | self._nakedParent.Insert(newNode, self._startNodeIndex + index +len(points) - 1) |
|---|
| 1735 | # do a regular insertion |
|---|
| 1736 | else: |
|---|
| 1737 | onX, onY = points[-1] |
|---|
| 1738 | newNode = Node(_rfToFLSegmentType(segmentType), Point(onX, onY)) |
|---|
| 1739 | # fix the off curves in case the user is inserting a curve |
|---|
| 1740 | # but is not specifying off curve points |
|---|
| 1741 | if segmentType == CURVE and len(points) == 1: |
|---|
| 1742 | pSeg = self._prevSegment(index) |
|---|
| 1743 | pOn = pSeg.onCurve |
|---|
| 1744 | newNode.points[1].Assign(Point(pOn.x, pOn.y)) |
|---|
| 1745 | newNode.points[2].Assign(Point(onX, onY)) |
|---|
| 1746 | for pointIndex in range(len(points[:-1])): |
|---|
| 1747 | x, y = points[pointIndex] |
|---|
| 1748 | newNode.points[1 + pointIndex].Assign(Point(x, y)) |
|---|
| 1749 | if smooth: |
|---|
| 1750 | node.alignment = flSMOOTH |
|---|
| 1751 | self._nakedParent.Insert(newNode, self._startNodeIndex + index) |
|---|
| 1752 | self._buildSegments() |
|---|
| 1753 | return self.segments[index] |
|---|
| 1754 | |
|---|
| 1755 | def removeSegment(self, index): |
|---|
| 1756 | """remove a segment from the contour""" |
|---|
| 1757 | segment = self.segments[index] |
|---|
| 1758 | # we have a qcurve. umph. |
|---|
| 1759 | if segment.type == QCURVE: |
|---|
| 1760 | indexList = [segment._nodeIndex] + segment._looseOffCurve |
|---|
| 1761 | indexList.sort() |
|---|
| 1762 | indexList.reverse() |
|---|
| 1763 | parent = self._nakedParent |
|---|
| 1764 | for nodeIndex in indexList: |
|---|
| 1765 | parent.DeleteNode(nodeIndex) |
|---|
| 1766 | # we have a more sane structure to follow |
|---|
| 1767 | else: |
|---|
| 1768 | # store some info for later |
|---|
| 1769 | next = self._nextSegment(index) |
|---|
| 1770 | nextOffA = None |
|---|
| 1771 | nextOffB = None |
|---|
| 1772 | nextType = next.type |
|---|
| 1773 | if nextType != LINE and nextType != MOVE: |
|---|
| 1774 | pA = next.offCurve[0] |
|---|
| 1775 | nextOffA = (pA.x, pA.y) |
|---|
| 1776 | pB = next.offCurve[-1] |
|---|
| 1777 | nextOffB = (pB.x, pB.y) |
|---|
| 1778 | nodeIndex = segment._nodeIndex |
|---|
| 1779 | self._nakedParent.DeleteNode(nodeIndex) |
|---|
| 1780 | self._buildSegments() |
|---|
| 1781 | # now we must override FL guessing about offCurves |
|---|
| 1782 | next = self._nextSegment(index - 1) |
|---|
| 1783 | nextType = next.type |
|---|
| 1784 | if nextType != LINE and nextType != MOVE: |
|---|
| 1785 | pA = next.offCurve[0] |
|---|
| 1786 | pB = next.offCurve[-1] |
|---|
| 1787 | pA.x, pA.y = nextOffA |
|---|
| 1788 | pB.x, pB.y = nextOffB |
|---|
| 1789 | |
|---|
| 1790 | def reverseContour(self): |
|---|
| 1791 | """reverse contour direction""" |
|---|
| 1792 | self._nakedParent.ReverseContour(self._index) |
|---|
| 1793 | self._buildSegments() |
|---|
| 1794 | |
|---|
| 1795 | def setStartSegment(self, segmentIndex): |
|---|
| 1796 | """set the first node on the contour""" |
|---|
| 1797 | self._nakedParent.SetStartNode(self._startNodeIndex + segmentIndex) |
|---|
| 1798 | self.getParent()._invalidateContours() |
|---|
| 1799 | self.getParent()._buildContours() |
|---|
| 1800 | |
|---|
| 1801 | def copy(self, aParent=None): |
|---|
| 1802 | """Copy this object -- result is an ObjectsRF flavored object. |
|---|
| 1803 | There is no way to make this work using FontLab objects. |
|---|
| 1804 | Copy is mainly used for glyphmath. |
|---|
| 1805 | """ |
|---|
| 1806 | raise RoboFabError, "copy() for objectsFL.RContour is not implemented." |
|---|
| 1807 | |
|---|
| 1808 | |
|---|
| 1809 | |
|---|
| 1810 | class RSegment(BaseSegment): |
|---|
| 1811 | |
|---|
| 1812 | _title = "FLSegment" |
|---|
| 1813 | |
|---|
| 1814 | def __init__(self, flNodeIndex): |
|---|
| 1815 | BaseSegment.__init__(self) |
|---|
| 1816 | self._nodeIndex = flNodeIndex |
|---|
| 1817 | self._looseOffCurve = [] #a list of indexes to loose off curve nodes |
|---|
| 1818 | |
|---|
| 1819 | def _get__node(self): |
|---|
| 1820 | glyph = self.getParent()._nakedParent |
|---|
| 1821 | return glyph.nodes[self._nodeIndex] |
|---|
| 1822 | |
|---|
| 1823 | _node = property(_get__node, doc="") |
|---|
| 1824 | |
|---|
| 1825 | def _get_qOffCurve(self): |
|---|
| 1826 | nodes = self.getParent()._nakedParent.nodes |
|---|
| 1827 | off = [] |
|---|
| 1828 | for x in self._looseOffCurve: |
|---|
| 1829 | off.append(nodes[x]) |
|---|
| 1830 | return off |
|---|
| 1831 | |
|---|
| 1832 | _qOffCurve = property(_get_qOffCurve, doc="free floating off curve nodes in the segment") |
|---|
| 1833 | |
|---|
| 1834 | def _get_index(self): |
|---|
| 1835 | contour = self.getParent() |
|---|
| 1836 | return self._nodeIndex - contour._startNodeIndex |
|---|
| 1837 | |
|---|
| 1838 | index = property(_get_index, doc="") |
|---|
| 1839 | |
|---|
| 1840 | def _isQCurve(self): |
|---|
| 1841 | # loose off curves only appear in q curves |
|---|
| 1842 | if len(self._looseOffCurve) != 0: |
|---|
| 1843 | return True |
|---|
| 1844 | return False |
|---|
| 1845 | |
|---|
| 1846 | def _get_type(self): |
|---|
| 1847 | if self._isQCurve(): |
|---|
| 1848 | return QCURVE |
|---|
| 1849 | return _flToRFSegmentType(self._node.type) |
|---|
| 1850 | |
|---|
| 1851 | def _set_type(self, segmentType): |
|---|
| 1852 | if self._isQCurve(): |
|---|
| 1853 | raise RoboFabError, 'qcurve point types cannot be changed' |
|---|
| 1854 | oldNode = self._node |
|---|
| 1855 | oldType = oldNode.type |
|---|
| 1856 | oldPointType = _flToRFSegmentType(oldType) |
|---|
| 1857 | if oldPointType == MOVE: |
|---|
| 1858 | raise RoboFabError, '%s point types cannot be changed'%oldPointType |
|---|
| 1859 | if segmentType == MOVE or segmentType == OFFCURVE: |
|---|
| 1860 | raise RoboFabError, '%s point types cannot be assigned'%oldPointType |
|---|
| 1861 | if oldPointType == segmentType: |
|---|
| 1862 | return |
|---|
| 1863 | oldNode.type = _rfToFLSegmentType(segmentType) |
|---|
| 1864 | |
|---|
| 1865 | type = property(_get_type, _set_type, doc="") |
|---|
| 1866 | |
|---|
| 1867 | def _get_smooth(self): |
|---|
| 1868 | alignment = self._node.alignment |
|---|
| 1869 | if alignment == flSMOOTH or alignment == flFIXED: |
|---|
| 1870 | return True |
|---|
| 1871 | return False |
|---|
| 1872 | |
|---|
| 1873 | def _set_smooth(self, value): |
|---|
| 1874 | if value: |
|---|
| 1875 | self._node.alignment = flSMOOTH |
|---|
| 1876 | else: |
|---|
| 1877 | self._node.alignment = flSHARP |
|---|
| 1878 | |
|---|
| 1879 | smooth = property(_get_smooth, _set_smooth, doc="") |
|---|
| 1880 | |
|---|
| 1881 | def _get_points(self): |
|---|
| 1882 | points = [] |
|---|
| 1883 | node = self._node |
|---|
| 1884 | # gather the off curves |
|---|
| 1885 | # |
|---|
| 1886 | # are we dealing with a qCurve? ugh. |
|---|
| 1887 | # gather the loose off curves |
|---|
| 1888 | if self._isQCurve(): |
|---|
| 1889 | off = self._qOffCurve |
|---|
| 1890 | x = 0 |
|---|
| 1891 | for n in off: |
|---|
| 1892 | p = RPoint(0) |
|---|
| 1893 | p.setParent(self) |
|---|
| 1894 | p._qOffIndex = x |
|---|
| 1895 | points.append(p) |
|---|
| 1896 | x = x + 1 |
|---|
| 1897 | # otherwise get the points associated with the node |
|---|
| 1898 | else: |
|---|
| 1899 | index = 1 |
|---|
| 1900 | for point in node.points[1:]: |
|---|
| 1901 | p = RPoint(index) |
|---|
| 1902 | p.setParent(self) |
|---|
| 1903 | points.append(p) |
|---|
| 1904 | index = index + 1 |
|---|
| 1905 | # the last point should always be the on curve |
|---|
| 1906 | p = RPoint(0) |
|---|
| 1907 | p.setParent(self) |
|---|
| 1908 | points.append(p) |
|---|
| 1909 | return points |
|---|
| 1910 | |
|---|
| 1911 | points = property(_get_points, doc="") |
|---|
| 1912 | |
|---|
| 1913 | def _get_selected(self): |
|---|
| 1914 | return self._node.selected |
|---|
| 1915 | |
|---|
| 1916 | def _set_selected(self, value): |
|---|
| 1917 | self._node.selected = value |
|---|
| 1918 | |
|---|
| 1919 | selected = property(_get_selected, _set_selected, doc="") |
|---|
| 1920 | |
|---|
| 1921 | def move(self, (x, y)): |
|---|
| 1922 | x, y = roundPt((x, y)) |
|---|
| 1923 | self._node.Shift(Point(x, y)) |
|---|
| 1924 | if self._isQCurve(): |
|---|
| 1925 | qOff = self._qOffCurve |
|---|
| 1926 | for node in qOff: |
|---|
| 1927 | node.Shift(Point(x, y)) |
|---|
| 1928 | |
|---|
| 1929 | def copy(self, aParent=None): |
|---|
| 1930 | """Copy this object -- result is an ObjectsRF flavored object. |
|---|
| 1931 | There is no way to make this work using FontLab objects. |
|---|
| 1932 | Copy is mainly used for glyphmath. |
|---|
| 1933 | """ |
|---|
| 1934 | raise RoboFabError, "copy() for objectsFL.RSegment is not implemented." |
|---|
| 1935 | |
|---|
| 1936 | |
|---|
| 1937 | |
|---|
| 1938 | class RPoint(BasePoint): |
|---|
| 1939 | |
|---|
| 1940 | _title = "FLPoint" |
|---|
| 1941 | |
|---|
| 1942 | def __init__(self, pointIndex): |
|---|
| 1943 | #BasePoint.__init__(self) |
|---|
| 1944 | self._pointIndex = pointIndex |
|---|
| 1945 | self._qOffIndex = None |
|---|
| 1946 | |
|---|
| 1947 | def _get__parentGlyph(self): |
|---|
| 1948 | return self._parentContour.getParent() |
|---|
| 1949 | |
|---|
| 1950 | _parentGlyph = property(_get__parentGlyph, doc="") |
|---|
| 1951 | |
|---|
| 1952 | def _get__parentContour(self): |
|---|
| 1953 | return self._parentSegment.getParent() |
|---|
| 1954 | |
|---|
| 1955 | _parentContour = property(_get__parentContour, doc="") |
|---|
| 1956 | |
|---|
| 1957 | def _get__parentSegment(self): |
|---|
| 1958 | return self.getParent() |
|---|
| 1959 | |
|---|
| 1960 | _parentSegment = property(_get__parentSegment, doc="") |
|---|
| 1961 | |
|---|
| 1962 | def _get__node(self): |
|---|
| 1963 | if self._qOffIndex is not None: |
|---|
| 1964 | return self.getParent()._qOffCurve[self._qOffIndex] |
|---|
| 1965 | return self.getParent()._node |
|---|
| 1966 | |
|---|
| 1967 | _node = property(_get__node, doc="") |
|---|
| 1968 | |
|---|
| 1969 | def _get__point(self): |
|---|
| 1970 | return self._node.points[self._pointIndex] |
|---|
| 1971 | |
|---|
| 1972 | _point = property(_get__point, doc="") |
|---|
| 1973 | |
|---|
| 1974 | def _get_x(self): |
|---|
| 1975 | return self._point.x |
|---|
| 1976 | |
|---|
| 1977 | def _set_x(self, value): |
|---|
| 1978 | value = int(round(value)) |
|---|
| 1979 | self._point.x = value |
|---|
| 1980 | |
|---|
| 1981 | x = property(_get_x, _set_x, doc="") |
|---|
| 1982 | |
|---|
| 1983 | def _get_y(self): |
|---|
| 1984 | return self._point.y |
|---|
| 1985 | |
|---|
| 1986 | def _set_y(self, value): |
|---|
| 1987 | value = int(round(value)) |
|---|
| 1988 | self._point.y = value |
|---|
| 1989 | |
|---|
| 1990 | y = property(_get_y, _set_y, doc="") |
|---|
| 1991 | |
|---|
| 1992 | def _get_type(self): |
|---|
| 1993 | if self._pointIndex == 0: |
|---|
| 1994 | # FL store quad contour data as a list of off curves and lines |
|---|
| 1995 | # (see note in RContour._buildSegments). So, we need to do |
|---|
| 1996 | # a bit of trickery to return a decent point type. |
|---|
| 1997 | # if the straight FL node type is off curve, it is a loose |
|---|
| 1998 | # quad off curve. return that. |
|---|
| 1999 | tp = _flToRFSegmentType(self._node.type) |
|---|
| 2000 | if tp == OFFCURVE: |
|---|
| 2001 | return OFFCURVE |
|---|
| 2002 | # otherwise we are dealing with an on curve. in this case, |
|---|
| 2003 | # we attempt to get the parent segment type and return it. |
|---|
| 2004 | segment = self.getParent() |
|---|
| 2005 | if segment is not None: |
|---|
| 2006 | return segment.type |
|---|
| 2007 | # we must not have a segment, fall back to straight conversion |
|---|
| 2008 | return tp |
|---|
| 2009 | return OFFCURVE |
|---|
| 2010 | |
|---|
| 2011 | type = property(_get_type, doc="") |
|---|
| 2012 | |
|---|
| 2013 | def _set_selected(self, value): |
|---|
| 2014 | if self._pointIndex == 0: |
|---|
| 2015 | self._node.selected = value |
|---|
| 2016 | |
|---|
| 2017 | def _get_selected(self): |
|---|
| 2018 | if self._pointIndex == 0: |
|---|
| 2019 | return self._node.selected |
|---|
| 2020 | return False |
|---|
| 2021 | |
|---|
| 2022 | selected = property(_get_selected, _set_selected, doc="") |
|---|
| 2023 | |
|---|
| 2024 | def move(self, (x, y)): |
|---|
| 2025 | x, y = roundPt((x, y)) |
|---|
| 2026 | self._point.Shift(Point(x, y)) |
|---|
| 2027 | |
|---|
| 2028 | def scale(self, (x, y), center=(0, 0)): |
|---|
| 2029 | centerX, centerY = roundPt(center) |
|---|
| 2030 | point = self._point |
|---|
| 2031 | point.x, point.y = _scalePointFromCenter((point.x, point.y), (x, y), (centerX, centerY)) |
|---|
| 2032 | |
|---|
| 2033 | def copy(self, aParent=None): |
|---|
| 2034 | """Copy this object -- result is an ObjectsRF flavored object. |
|---|
| 2035 | There is no way to make this work using FontLab objects. |
|---|
| 2036 | Copy is mainly used for glyphmath. |
|---|
| 2037 | """ |
|---|
| 2038 | raise RoboFabError, "copy() for objectsFL.RPoint is not implemented." |
|---|
| 2039 | |
|---|
| 2040 | |
|---|
| 2041 | class RBPoint(BaseBPoint): |
|---|
| 2042 | |
|---|
| 2043 | _title = "FLBPoint" |
|---|
| 2044 | |
|---|
| 2045 | def __init__(self, segmentIndex): |
|---|
| 2046 | #BaseBPoint.__init__(self) |
|---|
| 2047 | self._segmentIndex = segmentIndex |
|---|
| 2048 | |
|---|
| 2049 | def _get__parentSegment(self): |
|---|
| 2050 | return self.getParent().segments[self._segmentIndex] |
|---|
| 2051 | |
|---|
| 2052 | _parentSegment = property(_get__parentSegment, doc="") |
|---|
| 2053 | |
|---|
| 2054 | def _get_index(self): |
|---|
| 2055 | return self._segmentIndex |
|---|
| 2056 | |
|---|
| 2057 | index = property(_get_index, doc="") |
|---|
| 2058 | |
|---|
| 2059 | def _get_selected(self): |
|---|
| 2060 | return self._parentSegment.selected |
|---|
| 2061 | |
|---|
| 2062 | def _set_selected(self, value): |
|---|
| 2063 | self._parentSegment.selected = value |
|---|
| 2064 | |
|---|
| 2065 | selected = property(_get_selected, _set_selected, doc="") |
|---|
| 2066 | |
|---|
| 2067 | def copy(self, aParent=None): |
|---|
| 2068 | """Copy this object -- result is an ObjectsRF flavored object. |
|---|
| 2069 | There is no way to make this work using FontLab objects. |
|---|
| 2070 | Copy is mainly used for glyphmath. |
|---|
| 2071 | """ |
|---|
| 2072 | raise RoboFabError, "copy() for objectsFL.RBPoint is not implemented." |
|---|
| 2073 | |
|---|
| 2074 | |
|---|
| 2075 | class RComponent(BaseComponent): |
|---|
| 2076 | |
|---|
| 2077 | """RoboFab wrapper for FL Component object""" |
|---|
| 2078 | |
|---|
| 2079 | _title = "FLComponent" |
|---|
| 2080 | |
|---|
| 2081 | def __init__(self, flComponent, index): |
|---|
| 2082 | BaseComponent.__init__(self) |
|---|
| 2083 | self._object = flComponent |
|---|
| 2084 | self._index=index |
|---|
| 2085 | |
|---|
| 2086 | def _get_index(self): |
|---|
| 2087 | return self._index |
|---|
| 2088 | |
|---|
| 2089 | index = property(_get_index, doc="index of component") |
|---|
| 2090 | |
|---|
| 2091 | def _get_baseGlyph(self): |
|---|
| 2092 | return self._object.parent.parent[self._object.index].name |
|---|
| 2093 | |
|---|
| 2094 | baseGlyph = property(_get_baseGlyph, doc="") |
|---|
| 2095 | |
|---|
| 2096 | def _get_offset(self): |
|---|
| 2097 | return (int(self._object.delta.x), int(self._object.delta.y)) |
|---|
| 2098 | |
|---|
| 2099 | def _set_offset(self, value): |
|---|
| 2100 | value = roundPt((value[0], value[1])) |
|---|
| 2101 | self._object.delta=Point(value[0], value[1]) |
|---|
| 2102 | |
|---|
| 2103 | offset = property(_get_offset, _set_offset, doc="the offset of the component") |
|---|
| 2104 | |
|---|
| 2105 | def _get_scale(self): |
|---|
| 2106 | return (self._object.scale.x, self._object.scale.y) |
|---|
| 2107 | |
|---|
| 2108 | def _set_scale(self, (x, y)): |
|---|
| 2109 | self._object.scale=Point(x, y) |
|---|
| 2110 | |
|---|
| 2111 | scale = property(_get_scale, _set_scale, doc="the scale of the component") |
|---|
| 2112 | |
|---|
| 2113 | def move(self, (x, y)): |
|---|
| 2114 | """Move the component""" |
|---|
| 2115 | x, y = roundPt((x, y)) |
|---|
| 2116 | self._object.delta=Point(self._object.delta.x+x, self._object.delta.y+y) |
|---|
| 2117 | |
|---|
| 2118 | def decompose(self): |
|---|
| 2119 | """Decompose the component""" |
|---|
| 2120 | self._object.Paste() |
|---|
| 2121 | |
|---|
| 2122 | def copy(self, aParent=None): |
|---|
| 2123 | """Copy this object -- result is an ObjectsRF flavored object. |
|---|
| 2124 | There is no way to make this work using FontLab objects. |
|---|
| 2125 | Copy is mainly used for glyphmath. |
|---|
| 2126 | """ |
|---|
| 2127 | raise RoboFabError, "copy() for objectsFL.RComponent is not implemented." |
|---|
| 2128 | |
|---|
| 2129 | |
|---|
| 2130 | |
|---|
| 2131 | class RAnchor(BaseAnchor): |
|---|
| 2132 | """RoboFab wrapper for FL Anchor object""" |
|---|
| 2133 | |
|---|
| 2134 | _title = "FLAnchor" |
|---|
| 2135 | |
|---|
| 2136 | def __init__(self, flAnchor, index): |
|---|
| 2137 | BaseAnchor.__init__(self) |
|---|
| 2138 | self._object = flAnchor |
|---|
| 2139 | self._index = index |
|---|
| 2140 | |
|---|
| 2141 | def _get_y(self): |
|---|
| 2142 | return self._object.y |
|---|
| 2143 | |
|---|
| 2144 | def _set_y(self, value): |
|---|
| 2145 | self._object.y = int(round(value)) |
|---|
| 2146 | |
|---|
| 2147 | y = property(_get_y, _set_y, doc="y") |
|---|
| 2148 | |
|---|
| 2149 | def _get_x(self): |
|---|
| 2150 | return self._object.x |
|---|
| 2151 | |
|---|
| 2152 | def _set_x(self, value): |
|---|
| 2153 | self._object.x = int(round(value)) |
|---|
| 2154 | |
|---|
| 2155 | x = property(_get_x, _set_x, doc="x") |
|---|
| 2156 | |
|---|
| 2157 | def _get_name(self): |
|---|
| 2158 | return self._object.name |
|---|
| 2159 | |
|---|
| 2160 | def _set_name(self, value): |
|---|
| 2161 | self._object.name = value |
|---|
| 2162 | |
|---|
| 2163 | name = property(_get_name, _set_name, doc="name") |
|---|
| 2164 | |
|---|
| 2165 | def _get_mark(self): |
|---|
| 2166 | return self._object.mark |
|---|
| 2167 | |
|---|
| 2168 | def _set_mark(self, value): |
|---|
| 2169 | self._object.mark = value |
|---|
| 2170 | |
|---|
| 2171 | mark = property(_get_mark, _set_mark, doc="mark") |
|---|
| 2172 | |
|---|
| 2173 | def _get_index(self): |
|---|
| 2174 | return self._index |
|---|
| 2175 | |
|---|
| 2176 | index = property(_get_index, doc="index of the anchor") |
|---|
| 2177 | |
|---|
| 2178 | def _get_position(self): |
|---|
| 2179 | return (self._object.x, self._object.y) |
|---|
| 2180 | |
|---|
| 2181 | def _set_position(self, value): |
|---|
| 2182 | value = roundPt((value[0], value[1])) |
|---|
| 2183 | self._object.x=value[0] |
|---|
| 2184 | self._object.y=value[1] |
|---|
| 2185 | |
|---|
| 2186 | position = property(_get_position, _set_position, doc="position of the anchor") |
|---|
| 2187 | |
|---|
| 2188 | |
|---|
| 2189 | |
|---|
| 2190 | class RGuide(BaseGuide): |
|---|
| 2191 | |
|---|
| 2192 | """RoboFab wrapper for FL Guide object""" |
|---|
| 2193 | |
|---|
| 2194 | _title = "FLGuide" |
|---|
| 2195 | |
|---|
| 2196 | def __init__(self, flGuide, index): |
|---|
| 2197 | BaseGuide.__init__(self) |
|---|
| 2198 | self._object = flGuide |
|---|
| 2199 | self._index = index |
|---|
| 2200 | |
|---|
| 2201 | def __repr__(self): |
|---|
| 2202 | # this is a doozy! |
|---|
| 2203 | parent = "unknown_parent" |
|---|
| 2204 | parentObject = self.getParent() |
|---|
| 2205 | if parentObject is not None: |
|---|
| 2206 | # do we have a font? |
|---|
| 2207 | try: |
|---|
| 2208 | parent = parentObject.info.fullName |
|---|
| 2209 | except AttributeError: |
|---|
| 2210 | # or do we have a glyph? |
|---|
| 2211 | try: |
|---|
| 2212 | parent = parentObject.name |
|---|
| 2213 | # we must be an orphan |
|---|
| 2214 | except AttributeError: pass |
|---|
| 2215 | return "<Robofab guide wrapper for %s>"%parent |
|---|
| 2216 | |
|---|
| 2217 | def _get_position(self): |
|---|
| 2218 | return self._object.position |
|---|
| 2219 | |
|---|
| 2220 | def _set_position(self, value): |
|---|
| 2221 | self._object.position = value |
|---|
| 2222 | |
|---|
| 2223 | position = property(_get_position, _set_position, doc="position") |
|---|
| 2224 | |
|---|
| 2225 | def _get_angle(self): |
|---|
| 2226 | return self._object.angle |
|---|
| 2227 | |
|---|
| 2228 | def _set_angle(self, value): |
|---|
| 2229 | self._object.angle = value |
|---|
| 2230 | |
|---|
| 2231 | angle = property(_get_angle, _set_angle, doc="angle") |
|---|
| 2232 | |
|---|
| 2233 | def _get_index(self): |
|---|
| 2234 | return self._index |
|---|
| 2235 | |
|---|
| 2236 | index = property(_get_index, doc="index of the guide") |
|---|
| 2237 | |
|---|
| 2238 | |
|---|
| 2239 | class RGroups(BaseGroups): |
|---|
| 2240 | |
|---|
| 2241 | """RoboFab wrapper for FL group data""" |
|---|
| 2242 | |
|---|
| 2243 | _title = "FLGroups" |
|---|
| 2244 | |
|---|
| 2245 | def __init__(self, aDict): |
|---|
| 2246 | self.update(aDict) |
|---|
| 2247 | |
|---|
| 2248 | def __setitem__(self, key, value): |
|---|
| 2249 | # override baseclass so that data is stored in FL classes |
|---|
| 2250 | if not isinstance(key, str): |
|---|
| 2251 | raise RoboFabError, 'key must be a string' |
|---|
| 2252 | if not isinstance(value, list): |
|---|
| 2253 | raise RoboFabError, 'group must be a list' |
|---|
| 2254 | super(RGroups, self).__setitem__(key, value) |
|---|
| 2255 | self._setFLGroups() |
|---|
| 2256 | |
|---|
| 2257 | def __delitem__(self, key): |
|---|
| 2258 | # override baseclass so that data is stored in FL classes |
|---|
| 2259 | super(RGroups, self).__delitem__(key, value) |
|---|
| 2260 | self._setFLGroups() |
|---|
| 2261 | |
|---|
| 2262 | def _setFLGroups(self): |
|---|
| 2263 | # set the group data into the font. |
|---|
| 2264 | if self.getParent() is not None: |
|---|
| 2265 | groups = [] |
|---|
| 2266 | for i in self.keys(): |
|---|
| 2267 | value = ' '.join(self[i]) |
|---|
| 2268 | groups.append(': '.join([i, value])) |
|---|
| 2269 | groups.sort() |
|---|
| 2270 | self.getParent().naked().classes = groups |
|---|
| 2271 | |
|---|
| 2272 | def update(self, aDict): |
|---|
| 2273 | # override baseclass so that data is stored in FL classes |
|---|
| 2274 | super(RGroups, self).update(aDict) |
|---|
| 2275 | self._setFLGroups() |
|---|
| 2276 | |
|---|
| 2277 | def clear(self): |
|---|
| 2278 | # override baseclass so that data is stored in FL classes |
|---|
| 2279 | super(RGroups, self).clear() |
|---|
| 2280 | self._setFLGroups() |
|---|
| 2281 | |
|---|
| 2282 | def pop(self, key): |
|---|
| 2283 | # override baseclass so that data is stored in FL classes |
|---|
| 2284 | i = super(RGroups, self).pop(key) |
|---|
| 2285 | self._setFLGroups() |
|---|
| 2286 | return i |
|---|
| 2287 | |
|---|
| 2288 | def popitem(self): |
|---|
| 2289 | # override baseclass so that data is stored in FL classes |
|---|
| 2290 | i = super(RGroups, self).popitem() |
|---|
| 2291 | self._setFLGroups() |
|---|
| 2292 | return i |
|---|
| 2293 | |
|---|
| 2294 | def setdefault(self, key, value=None): |
|---|
| 2295 | # override baseclass so that data is stored in FL classes |
|---|
| 2296 | i = super(RGroups, self).setdefault(key, value) |
|---|
| 2297 | self._setFLGroups() |
|---|
| 2298 | return i |
|---|
| 2299 | |
|---|
| 2300 | |
|---|
| 2301 | class RKerning(BaseKerning): |
|---|
| 2302 | |
|---|
| 2303 | """RoboFab wrapper for FL Kerning data""" |
|---|
| 2304 | |
|---|
| 2305 | _title = "FLKerning" |
|---|
| 2306 | |
|---|
| 2307 | def __setitem__(self, pair, value): |
|---|
| 2308 | if not isinstance(pair, tuple): |
|---|
| 2309 | raise RoboFabError, 'kerning pair must be a tuple: (left, right)' |
|---|
| 2310 | else: |
|---|
| 2311 | if len(pair) != 2: |
|---|
| 2312 | raise RoboFabError, 'kerning pair must be a tuple: (left, right)' |
|---|
| 2313 | else: |
|---|
| 2314 | if value == 0: |
|---|
| 2315 | if self._kerning.get(pair) is not None: |
|---|
| 2316 | #see note about setting kerning values to 0 below |
|---|
| 2317 | self._setFLKerning(pair, 0) |
|---|
| 2318 | del self._kerning[pair] |
|---|
| 2319 | else: |
|---|
| 2320 | #self._kerning[pair] = value |
|---|
| 2321 | self._setFLKerning(pair, value) |
|---|
| 2322 | |
|---|
| 2323 | def _setFLKerning(self, pair, value): |
|---|
| 2324 | # write a pair back into the font |
|---|
| 2325 | # |
|---|
| 2326 | # this is fairly speedy, but setting a pair to 0 is roughly |
|---|
| 2327 | # 2-3 times slower than setting a real value. this is because |
|---|
| 2328 | # of all the hoops that must be jumped through to keep FL |
|---|
| 2329 | # from storing kerning pairs with a value of 0. |
|---|
| 2330 | parentFont = self.getParent().naked() |
|---|
| 2331 | left = parentFont[pair[0]] |
|---|
| 2332 | right = parentFont.FindGlyph(pair[1]) |
|---|
| 2333 | # the left glyph doesn not exist |
|---|
| 2334 | if left is None: |
|---|
| 2335 | return |
|---|
| 2336 | # the right glyph doesn not exist |
|---|
| 2337 | if right == -1: |
|---|
| 2338 | return |
|---|
| 2339 | self._kerning[pair] = value |
|---|
| 2340 | leftName = pair[0] |
|---|
| 2341 | value = int(round(value)) |
|---|
| 2342 | # pairs set to 0 need to be handled carefully. FL will allow |
|---|
| 2343 | # for pairs to have a value of 0 (!?), so we must catch them |
|---|
| 2344 | # when they pop up and make sure that the pair is actually |
|---|
| 2345 | # removed from the font. |
|---|
| 2346 | if value == 0: |
|---|
| 2347 | foundPair = False |
|---|
| 2348 | # if the value is 0, we don't need to construct a pair |
|---|
| 2349 | # we just need to make sure that the pair is not in the list |
|---|
| 2350 | pairs = [] |
|---|
| 2351 | # so, go through all the pairs and add them to a new list |
|---|
| 2352 | for flPair in left.kerning: |
|---|
| 2353 | # we have found the pair. flag it. |
|---|
| 2354 | if flPair.key == right: |
|---|
| 2355 | foundPair = True |
|---|
| 2356 | # not the pair. add it to the list. |
|---|
| 2357 | else: |
|---|
| 2358 | pairs.append((flPair.key, flPair.value)) |
|---|
| 2359 | # if we found it, write it back to the glyph. |
|---|
| 2360 | if foundPair: |
|---|
| 2361 | left.kerning = [] |
|---|
| 2362 | for p in pairs: |
|---|
| 2363 | new = KerningPair(p[0], p[1]) |
|---|
| 2364 | left.kerning.append(new) |
|---|
| 2365 | else: |
|---|
| 2366 | # non-zero pairs are a bit easier to handle |
|---|
| 2367 | # we just need to look to see if the pair exists |
|---|
| 2368 | # if so, change the value and stop the loop. |
|---|
| 2369 | # if not, add a new pair to the glyph |
|---|
| 2370 | self._kerning[pair] = value |
|---|
| 2371 | foundPair = False |
|---|
| 2372 | for flPair in left.kerning: |
|---|
| 2373 | if flPair.key == right: |
|---|
| 2374 | flPair.value = value |
|---|
| 2375 | foundPair = True |
|---|
| 2376 | break |
|---|
| 2377 | if not foundPair: |
|---|
| 2378 | p = KerningPair(right, value) |
|---|
| 2379 | left.kerning.append(p) |
|---|
| 2380 | |
|---|
| 2381 | def update(self, kerningDict): |
|---|
| 2382 | """replace kerning data with the data in the given kerningDict""" |
|---|
| 2383 | # override base class here for speed |
|---|
| 2384 | parentFont = self.getParent().naked() |
|---|
| 2385 | # add existing data to the new kerning dict is not being replaced |
|---|
| 2386 | for pair in self.keys(): |
|---|
| 2387 | if not kerningDict.has_key(pair): |
|---|
| 2388 | kerningDict[pair] = self._kerning[pair] |
|---|
| 2389 | # now clear the existing kerning to make sure that |
|---|
| 2390 | # all the kerning in residing in the glyphs is gone |
|---|
| 2391 | self.clear() |
|---|
| 2392 | self._kerning = kerningDict |
|---|
| 2393 | kDict = {} |
|---|
| 2394 | # nest the pairs into a dict keyed by the left glyph |
|---|
| 2395 | # {'A':{'A':-10, 'B':20, ...}, 'B':{...}, ...} |
|---|
| 2396 | for left, right in kerningDict.keys(): |
|---|
| 2397 | value = kerningDict[left, right] |
|---|
| 2398 | if not left in kDict: |
|---|
| 2399 | kDict[left] = {} |
|---|
| 2400 | kDict[left][right] = value |
|---|
| 2401 | for left in kDict.keys(): |
|---|
| 2402 | leftGlyph = parentFont[left] |
|---|
| 2403 | if leftGlyph is not None: |
|---|
| 2404 | for right in kDict[left].keys(): |
|---|
| 2405 | value = kDict[left][right] |
|---|
| 2406 | if value != 0: |
|---|
| 2407 | rightIndex = parentFont.FindGlyph(right) |
|---|
| 2408 | if rightIndex != -1: |
|---|
| 2409 | p = KerningPair(rightIndex, value) |
|---|
| 2410 | leftGlyph.kerning.append(p) |
|---|
| 2411 | |
|---|
| 2412 | def clear(self): |
|---|
| 2413 | """clear all kerning""" |
|---|
| 2414 | # override base class here for speed |
|---|
| 2415 | self._kerning = {} |
|---|
| 2416 | for glyph in self.getParent().naked().glyphs: |
|---|
| 2417 | glyph.kerning = [] |
|---|
| 2418 | |
|---|
| 2419 | def __add__(self, other): |
|---|
| 2420 | """Math operations on FL Kerning objects return RF Kerning objects |
|---|
| 2421 | as they need to be orphaned objects and FL can't deal with that.""" |
|---|
| 2422 | from sets import Set |
|---|
| 2423 | from robofab.objects.objectsRF import RKerning as _RKerning |
|---|
| 2424 | new = _RKerning() |
|---|
| 2425 | k = Set(self.keys()) | Set(other.keys()) |
|---|
| 2426 | for key in k: |
|---|
| 2427 | new[key] = self.get(key, 0) + other.get(key, 0) |
|---|
| 2428 | return new |
|---|
| 2429 | |
|---|
| 2430 | def __sub__(self, other): |
|---|
| 2431 | """Math operations on FL Kerning objects return RF Kerning objects |
|---|
| 2432 | as they need to be orphaned objects and FL can't deal with that.""" |
|---|
| 2433 | from sets import Set |
|---|
| 2434 | from robofab.objects.objectsRF import RKerning as _RKerning |
|---|
| 2435 | new = _RKerning() |
|---|
| 2436 | k = Set(self.keys()) | Set(other.keys()) |
|---|
| 2437 | for key in k: |
|---|
| 2438 | new[key] = self.get(key, 0) - other.get(key, 0) |
|---|
| 2439 | return new |
|---|
| 2440 | |
|---|
| 2441 | def __mul__(self, factor): |
|---|
| 2442 | """Math operations on FL Kerning objects return RF Kerning objects |
|---|
| 2443 | as they need to be orphaned objects and FL can't deal with that.""" |
|---|
| 2444 | from robofab.objects.objectsRF import RKerning as _RKerning |
|---|
| 2445 | new = _RKerning() |
|---|
| 2446 | for name, value in self.items(): |
|---|
| 2447 | new[name] = value * factor |
|---|
| 2448 | return new |
|---|
| 2449 | |
|---|
| 2450 | __rmul__ = __mul__ |
|---|
| 2451 | |
|---|
| 2452 | def __div__(self, factor): |
|---|
| 2453 | """Math operations on FL Kerning objects return RF Kerning objects |
|---|
| 2454 | as they need to be orphaned objects and FL can't deal with that.""" |
|---|
| 2455 | if factor == 0: |
|---|
| 2456 | raise ZeroDivisionError |
|---|
| 2457 | return self.__mul__(1.0/factor) |
|---|
| 2458 | |
|---|
| 2459 | |
|---|
| 2460 | class RLib(BaseLib): |
|---|
| 2461 | |
|---|
| 2462 | """RoboFab wrapper for FL lib""" |
|---|
| 2463 | |
|---|
| 2464 | # XXX: As of FL 4.6 the customdata field in glyph objects is busted. |
|---|
| 2465 | # storing anything there causes the glyph to become uneditable. |
|---|
| 2466 | # however, the customdata field in font objects is stable. |
|---|
| 2467 | |
|---|
| 2468 | def __init__(self, aDict): |
|---|
| 2469 | self.update(aDict) |
|---|
| 2470 | |
|---|
| 2471 | def __setitem__(self, key, value): |
|---|
| 2472 | # override baseclass so that data is stored in customdata field |
|---|
| 2473 | super(RLib, self).__setitem__(key, value) |
|---|
| 2474 | self._stashLib() |
|---|
| 2475 | |
|---|
| 2476 | def __delitem__(self, key): |
|---|
| 2477 | # override baseclass so that data is stored in customdata field |
|---|
| 2478 | super(RLib, self).__delitem__(key) |
|---|
| 2479 | self._stashLib() |
|---|
| 2480 | |
|---|
| 2481 | def _stashLib(self): |
|---|
| 2482 | # write the plist into the customdata field of the FL object |
|---|
| 2483 | if self.getParent() is None: |
|---|
| 2484 | return |
|---|
| 2485 | if not self: |
|---|
| 2486 | data = None |
|---|
| 2487 | elif len(self) == 1 and "org.robofab.fontlab.customdata" in self: |
|---|
| 2488 | data = self["org.robofab.fontlab.customdata"].data |
|---|
| 2489 | else: |
|---|
| 2490 | f = StringIO() |
|---|
| 2491 | writePlist(self, f) |
|---|
| 2492 | data = f.getvalue() |
|---|
| 2493 | f.close() |
|---|
| 2494 | parent = self.getParent() |
|---|
| 2495 | parent.naked().customdata = data |
|---|
| 2496 | |
|---|
| 2497 | def update(self, aDict): |
|---|
| 2498 | # override baseclass so that data is stored in customdata field |
|---|
| 2499 | super(RLib, self).update(aDict) |
|---|
| 2500 | self._stashLib() |
|---|
| 2501 | |
|---|
| 2502 | def clear(self): |
|---|
| 2503 | # override baseclass so that data is stored in customdata field |
|---|
| 2504 | super(RLib, self).clear() |
|---|
| 2505 | self._stashLib() |
|---|
| 2506 | |
|---|
| 2507 | def pop(self, key): |
|---|
| 2508 | # override baseclass so that data is stored in customdata field |
|---|
| 2509 | i = super(RLib, self).pop(key) |
|---|
| 2510 | self._stashLib() |
|---|
| 2511 | return i |
|---|
| 2512 | |
|---|
| 2513 | def popitem(self): |
|---|
| 2514 | # override baseclass so that data is stored in customdata field |
|---|
| 2515 | i = super(RLib, self).popitem() |
|---|
| 2516 | self._stashLib() |
|---|
| 2517 | return i |
|---|
| 2518 | |
|---|
| 2519 | def setdefault(self, key, value=None): |
|---|
| 2520 | # override baseclass so that data is stored in customdata field |
|---|
| 2521 | i = super(RLib, self).setdefault(key, value) |
|---|
| 2522 | self._stashLib() |
|---|
| 2523 | return i |
|---|
| 2524 | |
|---|
| 2525 | |
|---|
| 2526 | class RInfo(BaseInfo): |
|---|
| 2527 | |
|---|
| 2528 | """RoboFab wrapper for FL Font Info""" |
|---|
| 2529 | |
|---|
| 2530 | _title = "FLInfo" |
|---|
| 2531 | |
|---|
| 2532 | def __init__(self, font): |
|---|
| 2533 | BaseInfo.__init__(self) |
|---|
| 2534 | self._object = font |
|---|
| 2535 | |
|---|
| 2536 | def _get_familyName(self): |
|---|
| 2537 | return self._object.family_name |
|---|
| 2538 | |
|---|
| 2539 | def _set_familyName(self, value): |
|---|
| 2540 | self._object.family_name = value |
|---|
| 2541 | |
|---|
| 2542 | familyName = property(_get_familyName, _set_familyName, doc="family_name") |
|---|
| 2543 | |
|---|
| 2544 | def _get_styleName(self): |
|---|
| 2545 | return self._object.style_name |
|---|
| 2546 | |
|---|
| 2547 | def _set_styleName(self, value): |
|---|
| 2548 | self._object.style_name = value |
|---|
| 2549 | |
|---|
| 2550 | styleName = property(_get_styleName, _set_styleName, doc="style_name") |
|---|
| 2551 | |
|---|
| 2552 | def _get_fullName(self): |
|---|
| 2553 | return self._object.full_name |
|---|
| 2554 | |
|---|
| 2555 | def _set_fullName(self, value): |
|---|
| 2556 | self._object.full_name = value |
|---|
| 2557 | |
|---|
| 2558 | fullName = property(_get_fullName, _set_fullName, doc="full_name") |
|---|
| 2559 | |
|---|
| 2560 | def _get_fontName(self): |
|---|
| 2561 | return self._object.font_name |
|---|
| 2562 | |
|---|
| 2563 | def _set_fontName(self, value): |
|---|
| 2564 | self._object.font_name = value |
|---|
| 2565 | |
|---|
| 2566 | fontName = property(_get_fontName, _set_fontName, doc="font_name") |
|---|
| 2567 | |
|---|
| 2568 | def _get_menuName(self): |
|---|
| 2569 | return self._object.menu_name |
|---|
| 2570 | |
|---|
| 2571 | def _set_menuName(self, value): |
|---|
| 2572 | self._object.menu_name = value |
|---|
| 2573 | |
|---|
| 2574 | menuName = property(_get_menuName, _set_menuName, doc="menu_name") |
|---|
| 2575 | |
|---|
| 2576 | def _get_fondName(self): |
|---|
| 2577 | return self._object.apple_name |
|---|
| 2578 | |
|---|
| 2579 | def _set_fondName(self, value): |
|---|
| 2580 | self._object.apple_name = value |
|---|
| 2581 | |
|---|
| 2582 | fondName = property(_get_fondName, _set_fondName, doc="apple_name") |
|---|
| 2583 | |
|---|
| 2584 | def _get_otFamilyName(self): |
|---|
| 2585 | return self._object.pref_family_name |
|---|
| 2586 | |
|---|
| 2587 | def _set_otFamilyName(self, value): |
|---|
| 2588 | self._object.pref_family_name = value |
|---|
| 2589 | |
|---|
| 2590 | otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") |
|---|
| 2591 | |
|---|
| 2592 | def _get_otStyleName(self): |
|---|
| 2593 | return self._object.pref_style_name |
|---|
| 2594 | |
|---|
| 2595 | def _set_otStyleName(self, value): |
|---|
| 2596 | self._object.pref_style_name = value |
|---|
| 2597 | |
|---|
| 2598 | otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") |
|---|
| 2599 | |
|---|
| 2600 | def _get_otMacName(self): |
|---|
| 2601 | return self._object.mac_compatible |
|---|
| 2602 | |
|---|
| 2603 | def _set_otMacName(self, value): |
|---|
| 2604 | self._object.mac_compatible = value |
|---|
| 2605 | |
|---|
| 2606 | otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") |
|---|
| 2607 | |
|---|
| 2608 | def _get_weightValue(self): |
|---|
| 2609 | return self._object.weight_code |
|---|
| 2610 | |
|---|
| 2611 | def _set_weightValue(self, value): |
|---|
| 2612 | value = int(round(value)) # FL can't take float - 28/8/07 / evb |
|---|
| 2613 | self._object.weight_code = value |
|---|
| 2614 | |
|---|
| 2615 | weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") |
|---|
| 2616 | |
|---|
| 2617 | def _get_weightName(self): |
|---|
| 2618 | return self._object.weight |
|---|
| 2619 | |
|---|
| 2620 | def _set_weightName(self, value): |
|---|
| 2621 | self._object.weight = value |
|---|
| 2622 | |
|---|
| 2623 | weightName = property(_get_weightName, _set_weightName, doc="weight name") |
|---|
| 2624 | |
|---|
| 2625 | def _get_widthName(self): |
|---|
| 2626 | return self._object.width |
|---|
| 2627 | |
|---|
| 2628 | def _set_widthName(self, value): |
|---|
| 2629 | self._object.width = value |
|---|
| 2630 | |
|---|
| 2631 | widthName = property(_get_widthName, _set_widthName, doc="width name") |
|---|
| 2632 | |
|---|
| 2633 | def _get_fontStyle(self): |
|---|
| 2634 | return self._object.font_style |
|---|
| 2635 | |
|---|
| 2636 | def _set_fontStyle(self, value): |
|---|
| 2637 | self._object.font_style = value |
|---|
| 2638 | |
|---|
| 2639 | fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") |
|---|
| 2640 | |
|---|
| 2641 | def _get_msCharSet(self): |
|---|
| 2642 | return self._object.ms_charset |
|---|
| 2643 | |
|---|
| 2644 | def _set_msCharSet(self, value): |
|---|
| 2645 | self._object.ms_charset = value |
|---|
| 2646 | |
|---|
| 2647 | msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") |
|---|
| 2648 | |
|---|
| 2649 | def _get_fondID(self): |
|---|
| 2650 | return self._object.fond_id |
|---|
| 2651 | |
|---|
| 2652 | def _set_fondID(self, value): |
|---|
| 2653 | self._object.fond_id = value |
|---|
| 2654 | |
|---|
| 2655 | fondID = property(_get_fondID, _set_fondID, doc="fond_id") |
|---|
| 2656 | |
|---|
| 2657 | def _get_uniqueID(self): |
|---|
| 2658 | return self._object.unique_id |
|---|
| 2659 | |
|---|
| 2660 | def _set_uniqueID(self, value): |
|---|
| 2661 | self._object.unique_id = value |
|---|
| 2662 | |
|---|
| 2663 | uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") |
|---|
| 2664 | |
|---|
| 2665 | def _get_versionMajor(self): |
|---|
| 2666 | return self._object.version_major |
|---|
| 2667 | |
|---|
| 2668 | def _set_versionMajor(self, value): |
|---|
| 2669 | self._object.version_major = value |
|---|
| 2670 | |
|---|
| 2671 | versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") |
|---|
| 2672 | |
|---|
| 2673 | def _get_versionMinor(self): |
|---|
| 2674 | return self._object.version_minor |
|---|
| 2675 | |
|---|
| 2676 | def _set_versionMinor(self, value): |
|---|
| 2677 | self._object.version_minor = value |
|---|
| 2678 | |
|---|
| 2679 | versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") |
|---|
| 2680 | |
|---|
| 2681 | def _get_year(self): |
|---|
| 2682 | return self._object.year |
|---|
| 2683 | |
|---|
| 2684 | def _set_year(self, value): |
|---|
| 2685 | self._object.year = value |
|---|
| 2686 | |
|---|
| 2687 | year = property(_get_year, _set_year, doc="year") |
|---|
| 2688 | |
|---|
| 2689 | def _get_note(self): |
|---|
| 2690 | s = self._object.note |
|---|
| 2691 | if s is None: |
|---|
| 2692 | return s |
|---|
| 2693 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2694 | |
|---|
| 2695 | def _set_note(self, value): |
|---|
| 2696 | if value is not None: |
|---|
| 2697 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2698 | self._object.note = value |
|---|
| 2699 | |
|---|
| 2700 | note = property(_get_note, _set_note, doc="note") |
|---|
| 2701 | |
|---|
| 2702 | def _get_copyright(self): |
|---|
| 2703 | s = self._object.copyright |
|---|
| 2704 | if s is None: |
|---|
| 2705 | return s |
|---|
| 2706 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2707 | |
|---|
| 2708 | def _set_copyright(self, value): |
|---|
| 2709 | if value is not None: |
|---|
| 2710 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2711 | self._object.copyright = value |
|---|
| 2712 | |
|---|
| 2713 | copyright = property(_get_copyright, _set_copyright, doc="copyright") |
|---|
| 2714 | |
|---|
| 2715 | def _get_notice(self): |
|---|
| 2716 | s = self._object.notice |
|---|
| 2717 | if s is None: |
|---|
| 2718 | return s |
|---|
| 2719 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2720 | |
|---|
| 2721 | def _set_notice(self, value): |
|---|
| 2722 | if value is not None: |
|---|
| 2723 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2724 | self._object.notice = value |
|---|
| 2725 | |
|---|
| 2726 | notice = property(_get_notice, _set_notice, doc="notice") |
|---|
| 2727 | |
|---|
| 2728 | def _get_trademark(self): |
|---|
| 2729 | s = self._object.trademark |
|---|
| 2730 | if s is None: |
|---|
| 2731 | return s |
|---|
| 2732 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2733 | |
|---|
| 2734 | def _set_trademark(self, value): |
|---|
| 2735 | if value is not None: |
|---|
| 2736 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2737 | self._object.trademark = value |
|---|
| 2738 | |
|---|
| 2739 | trademark = property(_get_trademark, _set_trademark, doc="trademark") |
|---|
| 2740 | |
|---|
| 2741 | def _get_license(self): |
|---|
| 2742 | s = self._object.license |
|---|
| 2743 | if s is None: |
|---|
| 2744 | return s |
|---|
| 2745 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2746 | |
|---|
| 2747 | def _set_license(self, value): |
|---|
| 2748 | if value is not None: |
|---|
| 2749 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2750 | self._object.license = value |
|---|
| 2751 | |
|---|
| 2752 | license = property(_get_license, _set_license, doc="license") |
|---|
| 2753 | |
|---|
| 2754 | def _get_licenseURL(self): |
|---|
| 2755 | return self._object.license_url |
|---|
| 2756 | |
|---|
| 2757 | def _set_licenseURL(self, value): |
|---|
| 2758 | self._object.license_url = value |
|---|
| 2759 | |
|---|
| 2760 | licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") |
|---|
| 2761 | |
|---|
| 2762 | def _get_createdBy(self): |
|---|
| 2763 | s = self._object.source |
|---|
| 2764 | if s is None: |
|---|
| 2765 | return s |
|---|
| 2766 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2767 | |
|---|
| 2768 | def _set_createdBy(self, value): |
|---|
| 2769 | if value is not None: |
|---|
| 2770 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2771 | self._object.source = value |
|---|
| 2772 | |
|---|
| 2773 | createdBy = property(_get_createdBy, _set_createdBy, doc="source") |
|---|
| 2774 | |
|---|
| 2775 | def _get_designer(self): |
|---|
| 2776 | s = self._object.designer |
|---|
| 2777 | if s is None: |
|---|
| 2778 | return s |
|---|
| 2779 | return unicode(s, LOCAL_ENCODING) |
|---|
| 2780 | |
|---|
| 2781 | def _set_designer(self, value): |
|---|
| 2782 | if value is not None: |
|---|
| 2783 | value = value.encode(LOCAL_ENCODING) |
|---|
| 2784 | self._object.designer = value |
|---|
| 2785 | |
|---|
| 2786 | designer = property(_get_designer, _set_designer, doc="designer") |
|---|
| 2787 | |
|---|
| 2788 | def _get_designerURL(self): |
|---|
| 2789 | return self._object.designer_url |
|---|
| 2790 | |
|---|
| 2791 | def _set_designerURL(self, value): |
|---|
| 2792 | self._object.designer_url = value |
|---|
| 2793 | |
|---|
| 2794 | designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") |
|---|
| 2795 | |
|---|
| 2796 | def _get_vendorURL(self): |
|---|
| 2797 | return self._object.vendor_url |
|---|
| 2798 | |
|---|
| 2799 | def _set_vendorURL(self, value): |
|---|
| 2800 | self._object.vendor_url = value |
|---|
| 2801 | |
|---|
| 2802 | vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") |
|---|
| 2803 | |
|---|
| 2804 | def _get_ttVendor(self): |
|---|
| 2805 | return self._object.vendor |
|---|
| 2806 | |
|---|
| 2807 | def _set_ttVendor(self, value): |
|---|
| 2808 | self._object.vendor = value |
|---|
| 2809 | |
|---|
| 2810 | ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") |
|---|
| 2811 | |
|---|
| 2812 | def _get_ttUniqueID(self): |
|---|
| 2813 | return self._object.tt_u_id |
|---|
| 2814 | |
|---|
| 2815 | def _set_ttUniqueID(self, value): |
|---|
| 2816 | self._object.tt_u_id = value |
|---|
| 2817 | |
|---|
| 2818 | ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") |
|---|
| 2819 | |
|---|
| 2820 | def _get_ttVersion(self): |
|---|
| 2821 | return self._object.tt_version |
|---|
| 2822 | |
|---|
| 2823 | def _set_ttVersion(self, value): |
|---|
| 2824 | self._object.tt_version = value |
|---|
| 2825 | |
|---|
| 2826 | ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") |
|---|
| 2827 | |
|---|
| 2828 | def _get_unitsPerEm(self): |
|---|
| 2829 | return self._object.upm |
|---|
| 2830 | |
|---|
| 2831 | def _set_unitsPerEm(self, value): |
|---|
| 2832 | self._object.upm = int(round(value)) |
|---|
| 2833 | |
|---|
| 2834 | unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") |
|---|
| 2835 | |
|---|
| 2836 | def _get_ascender(self): |
|---|
| 2837 | return self._object.ascender[0] |
|---|
| 2838 | |
|---|
| 2839 | def _set_ascender(self, value): |
|---|
| 2840 | value = int(round(value)) |
|---|
| 2841 | self._object.ascender[0] = value |
|---|
| 2842 | |
|---|
| 2843 | ascender = property(_get_ascender, _set_ascender, doc="ascender value") |
|---|
| 2844 | |
|---|
| 2845 | def _get_descender(self): |
|---|
| 2846 | return self._object.descender[0] |
|---|
| 2847 | |
|---|
| 2848 | def _set_descender(self, value): |
|---|
| 2849 | value = int(round(value)) |
|---|
| 2850 | self._object.descender[0] = value |
|---|
| 2851 | |
|---|
| 2852 | descender = property(_get_descender, _set_descender, doc="descender value") |
|---|
| 2853 | |
|---|
| 2854 | def _get_capHeight(self): |
|---|
| 2855 | return self._object.cap_height[0] |
|---|
| 2856 | |
|---|
| 2857 | def _set_capHeight(self, value): |
|---|
| 2858 | value = int(round(value)) |
|---|
| 2859 | self._object.cap_height[0] = value |
|---|
| 2860 | |
|---|
| 2861 | capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") |
|---|
| 2862 | |
|---|
| 2863 | def _get_xHeight(self): |
|---|
| 2864 | return self._object.x_height[0] |
|---|
| 2865 | |
|---|
| 2866 | def _set_xHeight(self, value): |
|---|
| 2867 | value = int(round(value)) |
|---|
| 2868 | self._object.x_height[0] = value |
|---|
| 2869 | |
|---|
| 2870 | xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") |
|---|
| 2871 | |
|---|
| 2872 | def _get_defaultWidth(self): |
|---|
| 2873 | return self._object.default_width[0] |
|---|
| 2874 | |
|---|
| 2875 | def _set_defaultWidth(self, value): |
|---|
| 2876 | value = int(round(value)) |
|---|
| 2877 | self._object.default_width[0] = value |
|---|
| 2878 | |
|---|
| 2879 | defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") |
|---|
| 2880 | |
|---|
| 2881 | def _get_italicAngle(self): |
|---|
| 2882 | return self._object.italic_angle |
|---|
| 2883 | |
|---|
| 2884 | def _set_italicAngle(self, value): |
|---|
| 2885 | try: |
|---|
| 2886 | self._object.italic_angle = float(value) |
|---|
| 2887 | except TypeError: |
|---|
| 2888 | print "robofab.objects.objectsFL: can't set italic angle, possibly a FontLab API limitation" |
|---|
| 2889 | |
|---|
| 2890 | italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") |
|---|
| 2891 | |
|---|
| 2892 | def _get_slantAngle(self): |
|---|
| 2893 | return self._object.slant_angle |
|---|
| 2894 | |
|---|
| 2895 | def _set_slantAngle(self, value): |
|---|
| 2896 | try: |
|---|
| 2897 | self._object.slant_angle = float(value) |
|---|
| 2898 | except TypeError: |
|---|
| 2899 | print "robofab.objects.objectsFL: can't set slant angle, possibly a FontLab API limitation" |
|---|
| 2900 | |
|---|
| 2901 | slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") |
|---|
| 2902 | |
|---|
| 2903 | #is this still needed? |
|---|
| 2904 | def _get_full_name(self): |
|---|
| 2905 | return self._object.full_name |
|---|
| 2906 | |
|---|
| 2907 | def _set_full_name(self, value): |
|---|
| 2908 | self._object.full_name = value |
|---|
| 2909 | |
|---|
| 2910 | full_name = property(_get_full_name, _set_full_name, doc="FL: full_name") |
|---|
| 2911 | |
|---|
| 2912 | #is this still needed? |
|---|
| 2913 | def _get_ms_charset(self): |
|---|
| 2914 | return self._object.ms_charset |
|---|
| 2915 | |
|---|
| 2916 | def _set_ms_charset(self, value): |
|---|
| 2917 | self._object.ms_charset = value |
|---|
| 2918 | |
|---|
| 2919 | ms_charset = property(_get_ms_charset, _set_ms_charset, doc="FL: ms_charset") |
|---|