Changeset 171
- Timestamp:
- 02/28/09 09:47:24 (3 years ago)
- Files:
-
- trunk/Data/frequency.txt (deleted)
- trunk/Lib/robofab/__init__.py (modified) (1 diff)
- trunk/Lib/robofab/objects/family.py (deleted)
- trunk/Lib/robofab/objects/featureLib.py (deleted)
- trunk/Lib/robofab/objects/objectsBase.py (modified) (39 diffs)
- trunk/Lib/robofab/objects/objectsFL.py (modified) (15 diffs)
- trunk/Lib/robofab/objects/objectsRF.py (modified) (11 diffs)
- trunk/Lib/robofab/test/runAll.py (modified) (1 diff)
- trunk/Lib/robofab/test/testSupport.py (modified) (1 diff)
- trunk/Lib/robofab/test/test_RInfoFL.py (copied) (copied from branches/ufo2/Lib/robofab/test/test_RInfoFL.py)
- trunk/Lib/robofab/test/test_RInfoRF.py (copied) (copied from branches/ufo2/Lib/robofab/test/test_RInfoRF.py)
- trunk/Lib/robofab/test/test_fontLabUFOReadWrite.py (copied) (copied from branches/ufo2/Lib/robofab/test/test_fontLabUFOReadWrite.py)
- trunk/Lib/robofab/test/test_noneLabUFOReadWrite.py (copied) (copied from branches/ufo2/Lib/robofab/test/test_noneLabUFOReadWrite.py)
- trunk/Lib/robofab/test/test_objectsFL.py (modified) (1 diff)
- trunk/Lib/robofab/test/test_psHints.py (modified) (1 diff)
- trunk/Lib/robofab/test/test_ufoLib.py (copied) (copied from branches/ufo2/Lib/robofab/test/test_ufoLib.py)
- trunk/Lib/robofab/tools/fontlabFeatureSplitter.py (copied) (copied from branches/ufo2/Lib/robofab/tools/fontlabFeatureSplitter.py)
- trunk/Lib/robofab/tools/nameTable.py (deleted)
- trunk/Lib/robofab/tools/toolsAll.py (modified) (2 diffs)
- trunk/Lib/robofab/ufoLib.py (modified) (7 diffs)
- trunk/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py (modified) (1 diff)
- trunk/Scripts/RoboFabIntro/intro_FontObject.py (modified) (2 diffs)
- trunk/Scripts/RoboFabIntro/intro_FoundrySettings.py (modified) (1 diff)
- trunk/Scripts/RoboFabIntro/intro_Kerning.py (modified) (1 diff)
- trunk/Scripts/RoboFabUtils/RobustBatchGenerate.py (modified) (1 diff)
- trunk/Scripts/RoboFabUtils/TestFontEquality.py (modified) (2 diffs)
- trunk/TestData (copied) (copied from branches/ufo2/TestData)
- trunk/TestData/TestFont1 (UFO1).ufo (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo)
- trunk/TestData/TestFont1 (UFO1).ufo/fontinfo.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/fontinfo.plist)
- trunk/TestData/TestFont1 (UFO1).ufo/glyphs (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/glyphs)
- trunk/TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/glyphs/A_.glif)
- trunk/TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/glyphs/B_.glif)
- trunk/TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/glyphs/contents.plist)
- trunk/TestData/TestFont1 (UFO1).ufo/groups.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/groups.plist)
- trunk/TestData/TestFont1 (UFO1).ufo/kerning.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/kerning.plist)
- trunk/TestData/TestFont1 (UFO1).ufo/lib.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/lib.plist)
- trunk/TestData/TestFont1 (UFO1).ufo/metainfo.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO1).ufo/metainfo.plist)
- trunk/TestData/TestFont1 (UFO2).ufo (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo)
- trunk/TestData/TestFont1 (UFO2).ufo/features.fea (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/features.fea)
- trunk/TestData/TestFont1 (UFO2).ufo/fontinfo.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/fontinfo.plist)
- trunk/TestData/TestFont1 (UFO2).ufo/glyphs (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/glyphs)
- trunk/TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/glyphs/A_.glif)
- trunk/TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/glyphs/B_.glif)
- trunk/TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/glyphs/contents.plist)
- trunk/TestData/TestFont1 (UFO2).ufo/groups.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/groups.plist)
- trunk/TestData/TestFont1 (UFO2).ufo/kerning.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/kerning.plist)
- trunk/TestData/TestFont1 (UFO2).ufo/lib.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/lib.plist)
- trunk/TestData/TestFont1 (UFO2).ufo/metainfo.plist (copied) (copied from branches/ufo2/TestData/TestFont1 (UFO2).ufo/metainfo.plist)
- trunk/TestData/TestFont1.vfb (copied) (copied from branches/ufo2/TestData/TestFont1.vfb)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/Lib/robofab/__init__.py
r72 r171 76 76 77 77 78 numberVersion = (1, 1, "develop", 3)79 version = "1. 1.3"78 numberVersion = (1, 2, "develop", 0) 79 version = "1.2.0d" trunk/Lib/robofab/objects/objectsBase.py
r59 r171 19 19 from __future__ import division 20 20 21 21 from warnings import warn 22 import math 23 import copy 24 25 from robofab import ufoLib 22 26 from robofab import RoboFabError 23 27 from fontTools.misc.arrayTools import updateBounds, pointInRect, unionRect, sectRect 24 28 from fontTools.pens.basePen import AbstractPen 25 import math 26 import copy 29 27 30 28 31 #constants for dealing with segments, points and bPoints … … 147 150 return n 148 151 149 # math operations for psHint object150 # Note: math operations can change integers to floats.151 def __add__(self, other):152 assert isinstance(other, BasePostScriptHintValues)153 copied = self.copy()154 self._processMathOne(copied, other, add)155 return copied156 157 def __sub__(self, other):158 assert isinstance(other, BasePostScriptHintValues)159 copied = self.copy()160 self._processMathOne(copied, other, sub)161 return copied162 163 def __mul__(self, factor):164 #if isinstance(factor, tuple):165 # factor = factor[0]166 copiedInfo = self.copy()167 self._processMathTwo(copiedInfo, factor, mul)168 return copiedInfo169 170 __rmul__ = __mul__171 172 def __div__(self, factor):173 #if isinstance(factor, tuple):174 # factor = factor[0]175 copiedInfo = self.copy()176 self._processMathTwo(copiedInfo, factor, div)177 return copiedInfo178 179 __rdiv__ = __div__180 181 182 152 class BasePostScriptGlyphHintValues(BasePostScriptHintValues): 183 153 """ Base class for glyph-level postscript hinting information. … … 212 182 new.append((int(round(n[0])), int(round(n[1])))) 213 183 setattr(self, name, new) 184 185 # math operations for psHint object 186 # Note: math operations can change integers to floats. 187 def __add__(self, other): 188 assert isinstance(other, BasePostScriptHintValues) 189 copied = self.copy() 190 self._processMathOne(copied, other, add) 191 return copied 192 193 def __sub__(self, other): 194 assert isinstance(other, BasePostScriptHintValues) 195 copied = self.copy() 196 self._processMathOne(copied, other, sub) 197 return copied 198 199 def __mul__(self, factor): 200 #if isinstance(factor, tuple): 201 # factor = factor[0] 202 copiedInfo = self.copy() 203 self._processMathTwo(copiedInfo, factor, mul) 204 return copiedInfo 205 206 __rmul__ = __mul__ 207 208 def __div__(self, factor): 209 #if isinstance(factor, tuple): 210 # factor = factor[0] 211 copiedInfo = self.copy() 212 self._processMathTwo(copiedInfo, factor, div) 213 return copiedInfo 214 215 __rdiv__ = __div__ 214 216 215 217 def _processMathOne(self, copied, other, funct): … … 288 290 if data is not None: 289 291 self.fromDict(data) 290 else:291 for name in self._attributeNames.keys():292 setattr(self, name, self._attributeNames[name]['default'])293 292 294 293 def __repr__(self): 295 294 return "<PostScript Font Hints Values>" 295 296 # route attribute calls to info object 297 298 def _bluesToPairs(self, values): 299 values.sort() 300 finalValues = [] 301 for value in values: 302 if not finalValues or len(finalValues[-1]) == 2: 303 finalValues.append([]) 304 finalValues[-1].append(value) 305 return finalValues 306 307 def _bluesFromPairs(self, values): 308 finalValues = [] 309 for value1, value2 in values: 310 finalValues.append(value1) 311 finalValues.append(value2) 312 finalValues.sort() 313 return finalValues 314 315 def _get_blueValues(self): 316 values = self.getParent().info.postscriptBlueValues 317 if values is None: 318 values = [] 319 values = self._bluesToPairs(values) 320 return values 321 322 def _set_blueValues(self, values): 323 if values is None: 324 values = [] 325 values = self._bluesFromPairs(values) 326 self.getParent().info.postscriptBlueValues = values 327 328 blueValues = property(_get_blueValues, _set_blueValues) 329 330 def _get_otherBlues(self): 331 values = self.getParent().info.postscriptOtherBlues 332 if values is None: 333 values = [] 334 values = self._bluesToPairs(values) 335 return values 336 337 def _set_otherBlues(self, values): 338 if values is None: 339 values = [] 340 values = self._bluesFromPairs(values) 341 self.getParent().info.postscriptOtherBlues = values 342 343 otherBlues = property(_get_otherBlues, _set_otherBlues) 344 345 def _get_familyBlues(self): 346 values = self.getParent().info.postscriptFamilyBlues 347 if values is None: 348 values = [] 349 values = self._bluesToPairs(values) 350 return values 351 352 def _set_familyBlues(self, values): 353 if values is None: 354 values = [] 355 values = self._bluesFromPairs(values) 356 self.getParent().info.postscriptFamilyBlues = values 357 358 familyBlues = property(_get_familyBlues, _set_familyBlues) 359 360 def _get_familyOtherBlues(self): 361 values = self.getParent().info.postscriptFamilyOtherBlues 362 if values is None: 363 values = [] 364 values = self._bluesToPairs(values) 365 return values 366 367 def _set_familyOtherBlues(self, values): 368 if values is None: 369 values = [] 370 values = self._bluesFromPairs(values) 371 self.getParent().info.postscriptFamilyOtherBlues = values 372 373 familyOtherBlues = property(_get_familyOtherBlues, _set_familyOtherBlues) 374 375 def _get_vStems(self): 376 return self.getParent().info.postscriptStemSnapV 377 378 def _set_vStems(self, value): 379 if value is None: 380 value = [] 381 self.getParent().info.postscriptStemSnapV = list(value) 382 383 vStems = property(_get_vStems, _set_vStems) 384 385 def _get_hStems(self): 386 return self.getParent().info.postscriptStemSnapH 387 388 def _set_hStems(self, value): 389 if value is None: 390 value = [] 391 self.getParent().info.postscriptStemSnapH = list(value) 392 393 hStems = property(_get_hStems, _set_hStems) 394 395 def _get_blueScale(self): 396 return self.getParent().info.postscriptBlueScale 397 398 def _set_blueScale(self, value): 399 self.getParent().info.postscriptBlueScale = value 400 401 blueScale = property(_get_blueScale, _set_blueScale) 402 403 def _get_blueShift(self): 404 return self.getParent().info.postscriptBlueShift 405 406 def _set_blueShift(self, value): 407 self.getParent().info.postscriptBlueShift = value 408 409 blueShift = property(_get_blueShift, _set_blueShift) 410 411 def _get_blueFuzz(self): 412 return self.getParent().info.postscriptBlueFuzz 413 414 def _set_blueFuzz(self, value): 415 self.getParent().info.postscriptBlueFuzz = value 416 417 blueFuzz = property(_get_blueFuzz, _set_blueFuzz) 418 419 def _get_forceBold(self): 420 return self.getParent().info.postscriptForceBold 421 422 def _set_forceBold(self, value): 423 self.getParent().info.postscriptForceBold = value 424 425 forceBold = property(_get_forceBold, _set_forceBold) 296 426 297 427 def round(self): … … 335 465 new.append([int(round(m)) for m in n]) 336 466 setattr(self, name, new) 337 338 339 def _processMathOne(self, copied, other, funct):340 for name, values in self._attributeNames.items():341 a = None342 b = None343 v = None344 if hasattr(copied, name):345 a = getattr(copied, name)346 if hasattr(other, name):347 b = getattr(other, name)348 if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:349 # process single values350 if a is not None and b is not None:351 v = funct(a, b)352 elif a is not None and b is None:353 v = a354 elif b is not None and a is None:355 v = b356 if v is not None:357 setattr(copied, name, v)358 elif name in ['hStems', 'vStems']:359 if a is not None and b is not None:360 if len(a) != len(b):361 # can't do math with non matching zones362 continue363 l = len(a)364 v = [funct(a[i], b[i]) for i in range(l)]365 if v is not None:366 setattr(copied, name, v)367 else:368 if a is not None and b is not None:369 if len(a) != len(b):370 # can't do math with non matching zones371 continue372 l = len(a)373 for i in range(l):374 if v is None:375 v = []376 ai = a[i]377 bi = b[i]378 l2 = min(len(ai), len(bi))379 v2 = [funct(ai[j], bi[j]) for j in range(l2)]380 v.append(v2)381 if v is not None:382 setattr(copied, name, v)383 384 def _processMathTwo(self, copied, factor, funct):385 for name, values in self._attributeNames.items():386 a = None387 b = None388 v = None389 if hasattr(copied, name):390 a = getattr(copied, name)391 splitFactor = factor392 isVertical = self._attributeNames[name]['isVertical']393 if isinstance(factor, tuple):394 if isVertical:395 splitFactor = factor[1]396 else:397 splitFactor = factor[0]398 if name in ['blueFuzz', 'blueScale', 'blueShift', 'forceBold']:399 # process single values400 if a is not None:401 v = funct(a, splitFactor)402 if v is not None:403 setattr(copied, name, v)404 elif name in ['hStems', 'vStems']:405 if a is not None:406 v = [funct(a[i], splitFactor) for i in range(len(a))]407 if v is not None:408 setattr(copied, name, v)409 else:410 if a is not None:411 for i in range(len(a)):412 if v is None:413 v = []414 v2 = [funct(a[i][j], splitFactor) for j in range(len(a[i]))]415 v.append(v2)416 if v is not None:417 setattr(copied, name, v)418 467 419 468 … … 592 641 def __repr__(self): 593 642 try: 594 name = self.info. fullName643 name = self.info.postscriptFullName 595 644 except AttributeError: 596 645 name = "unnamed_font" … … 644 693 return self.getGlyph(glyphName) 645 694 695 def __contains__(self, glyphName): 696 return self.has_key(glyphName) 697 646 698 def _hasChanged(self): 647 699 #mark the object as changed … … 730 782 accent = self[accentName] 731 783 except IndexError: 732 errors["glyph '%s' is missing in font %s"%(accentName, self. fullName)] = 1784 errors["glyph '%s' is missing in font %s"%(accentName, self.postscriptFullName)] = 1 733 785 continue 734 786 localAnchors = {} … … 753 805 baseX, baseY = anchors[foundAnchor[1:]] 754 806 except KeyError: 755 errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info. fullName)]=1807 errors["anchor '%s' not found in glyph '%s' of font %s"%(foundAnchor[1:], baseName, self.info.postscriptFullName)]=1 756 808 continue 757 809 #calculate the accent componet offset values … … 832 884 if not errors.has_key('Missing Glyphs'): 833 885 errors['Missing Glyphs'] = [] 834 errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info. fullName))886 errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, minFont.info.postscriptFullName)) 835 887 if glyphName not in maxGlyphNames: 836 888 fatalError = True 837 889 if not errors.has_key('Missing Glyphs'): 838 890 errors['Missing Glyphs'] = [] 839 errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info. fullName))891 errors['Missing Glyphs'].append('Interpolation Error: %s not in %s'%(glyphName, maxFont.info.postscriptFullName)) 840 892 # if no major problems, proceed. 841 893 if not fatalError: … … 881 933 item = getattr(item, sub) 882 934 except (ImportError, AttributeError): 883 from warnings import warn884 935 warn("Can't find glyph name to file name converter function, " 885 936 "falling back to default scheme (%s)" % funcName, RoboFabWarning) … … 910 961 if fontParent is not None: 911 962 try: 912 font = fontParent.info. fullName963 font = fontParent.info.postscriptFullName 913 964 except AttributeError: 914 965 pass … … 1776 1827 if fontParent is not None: 1777 1828 try: 1778 font = fontParent.info. fullName1829 font = fontParent.info.postscriptFullName 1779 1830 except AttributeError: pass 1780 1831 try: … … 1789 1840 1790 1841 def __mul__(self, factor): 1791 from warnings import warn1792 1842 warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) 1793 1843 n = self.copy() … … 1801 1851 1802 1852 def __add__(self, other): 1803 from warnings import warn1804 1853 warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) 1805 1854 n = self.copy() … … 1811 1860 1812 1861 def __sub__(self, other): 1813 from warnings import warn1814 1862 warn("Contour math has been deprecated and is slated for removal.", DeprecationWarning) 1815 1863 n = self.copy() … … 2186 2234 if fontParent is not None: 2187 2235 try: 2188 font = fontParent.info. fullName2236 font = fontParent.info.postscriptFullName 2189 2237 except AttributeError: pass 2190 2238 try: … … 2195 2243 2196 2244 def __mul__(self, factor): 2197 from warnings import warn2198 2245 warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) 2199 2246 n = self.copy() … … 2207 2254 2208 2255 def __add__(self, other): 2209 from warnings import warn2210 2256 warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) 2211 2257 n = self.copy() … … 2216 2262 2217 2263 def __sub__(self, other): 2218 from warnings import warn2219 2264 warn("Segment math has been deprecated and is slated for removal.", DeprecationWarning) 2220 2265 n = self.copy() … … 2325 2370 if fontParent is not None: 2326 2371 try: 2327 font = fontParent.info. fullName2372 font = fontParent.info.postscriptFullName 2328 2373 except AttributeError: pass 2329 2374 return "<RPoint for %s.%s[%s][%s]>"%(font, glyph, contourIndex, segmentIndex) 2330 2375 2331 2376 def __add__(self, other): 2332 from warnings import warn2333 2377 warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) 2334 2378 #Add one point to another … … 2338 2382 2339 2383 def __sub__(self, other): 2340 from warnings import warn2341 2384 warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) 2342 2385 #Subtract one point from another … … 2346 2389 2347 2390 def __mul__(self, factor): 2348 from warnings import warn2349 2391 warn("Point math has been deprecated and is slated for removal.", DeprecationWarning) 2350 2392 #Multiply the point with factor. Factor can be a tuple of 2 *(f1, f2) … … 2441 2483 if fontParent is not None: 2442 2484 try: 2443 font = fontParent.info. fullName2485 font = fontParent.info.postscriptFullName 2444 2486 except AttributeError: pass 2445 2487 return "<RBPoint for %s.%s[%s][%s][%s]>"%(font, glyph, contourIndex, segmentIndex, `self.index`) … … 2447 2489 2448 2490 def __add__(self, other): 2449 from warnings import warn2450 2491 warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) 2451 2492 #Add one bPoint to another … … 2457 2498 2458 2499 def __sub__(self, other): 2459 from warnings import warn2460 2500 warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) 2461 2501 #Subtract one bPoint from another … … 2467 2507 2468 2508 def __mul__(self, factor): 2469 from warnings import warn2470 2509 warn("BPoint math has been deprecated and is slated for removal.", DeprecationWarning) 2471 2510 #Multiply the bPoint with factor. Factor can be a tuple of 2 *(f1, f2) … … 2680 2719 if fontParent is not None: 2681 2720 try: 2682 font = fontParent.info. fullName2721 font = fontParent.info.postscriptFullName 2683 2722 except AttributeError: pass 2684 2723 return "<RComponent for %s.%s.components[%s]>"%(font, glyph, `self.index`) … … 2709 2748 2710 2749 def __add__(self, other): 2711 from warnings import warn2712 2750 warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) 2713 2751 #Add one Component to another … … 2718 2756 2719 2757 def __sub__(self, other): 2720 from warnings import warn2721 2758 warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) 2722 2759 #Subtract one Component from another … … 2727 2764 2728 2765 def __mul__(self, factor): 2729 from warnings import warn2730 2766 warn("Component math has been deprecated and is slated for removal.", DeprecationWarning) 2731 2767 #Multiply the Component with factor. Factor can be a tuple of 2 *(f1, f2) … … 2799 2835 if fontParent is not None: 2800 2836 try: 2801 font = fontParent.info. fullName2837 font = fontParent.info.postscriptFullName 2802 2838 except AttributeError: pass 2803 2839 return "<RAnchor for %s.%s.anchors[%s]>"%(font, glyph, `self.index`) 2804 2840 2805 2841 def __add__(self, other): 2806 from warnings import warn2807 2842 warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) 2808 2843 #Add one anchor to another … … 2812 2847 2813 2848 def __sub__(self, other): 2814 from warnings import warn2815 2849 warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) 2816 2850 #Substract one anchor from another … … 2820 2854 2821 2855 def __mul__(self, factor): 2822 from warnings import warn2823 2856 warn("Anchor math has been deprecated and is slated for removal.", DeprecationWarning) 2824 2857 #Multiply the anchor with factor. Factor can be a tuple of 2 *(f1, f2) … … 2899 2932 self.selected = False 2900 2933 2901 2902 2903 2934 2904 2935 class BaseInfo(RBaseObject): 2905 2906 """Base class for all font.info objects.""" 2907 2936 2937 _baseAttributes = ["_object", "changed", "selected", "getParent"] 2938 _deprecatedAttributes = ufoLib.deprecatedFontInfoAttributesVersion2 2939 _infoAttributes = ufoLib.fontInfoAttributesVersion2 2940 # subclasses may define a list of environment 2941 # specific attributes that can be retrieved or set. 2942 _environmentAttributes = [] 2943 # subclasses may define a list of attributes 2944 # that should not follow the standard get/set 2945 # order provided by __setattr__ and __getattr__. 2946 # for these attributes, the environment specific 2947 # set and get methods must handle this value 2948 # without any pre-call validation. 2949 # (yeah. this is because of some FontLab dumbness.) 2950 _environmentOverrides = [] 2951 2952 def __setattr__(self, attr, value): 2953 # check to see if the attribute has been 2954 # deprecated. if so, warn the caller and 2955 # update the attribute and value. 2956 if attr in self._deprecatedAttributes: 2957 newAttr, newValue = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) 2958 note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) 2959 warn(note, DeprecationWarning) 2960 attr = newAttr 2961 value = newValue 2962 # setting a known attribute 2963 if attr in self._infoAttributes or attr in self._environmentAttributes: 2964 # lightly test the validity of the value 2965 if value is not None: 2966 isValidValue = ufoLib.validateFontInfoVersion2ValueForAttribute(attr, value) 2967 if not isValidValue: 2968 raise RoboFabError("Invalid value (%s) for attribute (%s)." % (repr(value), attr)) 2969 # use the environment specific info attr set 2970 # method if it is defined. 2971 if hasattr(self, "_environmentSetAttr"): 2972 self._environmentSetAttr(attr, value) 2973 # fallback to super 2974 else: 2975 super(BaseInfo, self).__setattr__(attr, value) 2976 # unknown attribute, test to see if it is a python attr 2977 elif attr in self.__dict__ or attr in self._baseAttributes: 2978 super(BaseInfo, self).__setattr__(attr, value) 2979 # raise an attribute error 2980 else: 2981 raise AttributeError("Unknown attribute %s." % attr) 2982 2983 # subclasses with environment specific attr setting can 2984 # implement this method. __setattr__ will call it if present. 2985 # def _environmentSetAttr(self, attr, value): 2986 # pass 2987 2988 def __getattr__(self, attr): 2989 if attr in self._environmentOverrides: 2990 return self._environmentGetAttr(attr) 2991 # check to see if the attribute has been 2992 # deprecated. if so, warn the caller and 2993 # flag the value as needing conversion. 2994 needValueConversionTo1 = False 2995 if attr in self._deprecatedAttributes: 2996 oldAttr = attr 2997 oldValue = attr 2998 newAttr, x = ufoLib.convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, None) 2999 note = "The %s attribute has been deprecated. Use the new %s attribute." % (attr, newAttr) 3000 warn(note, DeprecationWarning) 3001 attr = newAttr 3002 needValueConversionTo1 = True 3003 # getting a known attribute 3004 if attr in self._infoAttributes or attr in self._environmentAttributes: 3005 # use the environment specific info attr get 3006 # method if it is defined. 3007 if hasattr(self, "_environmentGetAttr"): 3008 value = self._environmentGetAttr(attr) 3009 # fallback to super 3010 else: 3011 try: 3012 value = super(BaseInfo, self).__getattribute__(attr) 3013 except AttributeError: 3014 return None 3015 if needValueConversionTo1: 3016 oldAttr, value = ufoLib.convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) 3017 return value 3018 # raise an attribute error 3019 else: 3020 raise AttributeError("Unknown attribute %s." % attr) 3021 3022 # subclasses with environment specific attr retrieval can 3023 # implement this method. __getattr__ will call it if present. 3024 # it should return the requested value. 3025 # def _environmentGetAttr(self, attr): 3026 # pass 3027 3028 class BaseFeatures(RBaseObject): 3029 2908 3030 def __init__(self): 2909 3031 RBaseObject.__init__(self) 2910 2911 def __repr__(self): 2912 font = self.fullName 2913 return "<RInfo for %s>"%font 2914 2915 def _get_familyName(self): 2916 raise NotImplementedError 2917 2918 def _set_familyName(self, value): 2919 raise NotImplementedError 2920 2921 familyName = property(_get_familyName, _set_familyName, doc="family name") 2922 2923 def _get_styleName(self): 2924 raise NotImplementedError 2925 2926 def _set_styleName(self, value): 2927 raise NotImplementedError 2928 2929 styleName = property(_get_styleName, _set_styleName, doc="style name") 2930 2931 def _get_fullName(self): 2932 raise NotImplementedError 2933 2934 def _set_fullName(self, value): 2935 raise NotImplementedError 2936 2937 fullName = property(_get_fullName, _set_fullName, doc="full name") 2938 2939 def _get_fontName(self): 2940 raise NotImplementedError 2941 2942 def _set_fontName(self, value): 2943 raise NotImplementedError 2944 2945 fontName = property(_get_fontName, _set_fontName, doc="font name") 2946 2947 def _get_menuName(self): 2948 raise NotImplementedError 2949 2950 def _set_menuName(self, value): 2951 raise NotImplementedError 2952 2953 menuName = property(_get_menuName, _set_menuName, doc="menu name") 2954 2955 def _get_fondName(self): 2956 raise NotImplementedError 2957 2958 def _set_fondName(self, value): 2959 raise NotImplementedError 2960 2961 fondName = property(_get_fondName, _set_fondName, doc="fond name") 2962 2963 def _get_otFamilyName(self): 2964 raise NotImplementedError 2965 2966 def _set_otFamilyName(self, value): 2967 raise NotImplementedError 2968 2969 otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="OpenType family name") 2970 2971 def _get_otStyleName(self): 2972 raise NotImplementedError 2973 2974 def _set_otStyleName(self, value): 2975 raise NotImplementedError 2976 2977 otStyleName = property(_get_otStyleName, _set_otStyleName, doc="OpenType style name") 2978 2979 def _get_otMacName(self): 2980 raise NotImplementedError 2981 2982 def _set_otMacName(self, value): 2983 raise NotImplementedError 2984 2985 otMacName = property(_get_otMacName, _set_otMacName, doc="Mac specific OpenType name") 2986 2987 def _get_weightValue(self): 2988 raise NotImplementedError 2989 2990 def _set_weightValue(self, value): 2991 raise NotImplementedError 2992 2993 weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") 2994 2995 def _get_weightName(self): 2996 raise NotImplementedError 2997 2998 def _set_weightName(self, value): 2999 raise NotImplementedError 3000 3001 weightName = property(_get_weightName, _set_weightName, doc="weight name") 3002 3003 def _get_widthName(self): 3004 raise NotImplementedError 3005 3006 def _set_widthName(self, value): 3007 raise NotImplementedError 3008 3009 widthName = property(_get_widthName, _set_widthName, doc="width name") 3010 3011 def _get_fontStyle(self): 3012 raise NotImplementedError 3013 3014 def _set_fontStyle(self, value): 3015 raise NotImplementedError 3016 3017 fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font style") 3018 3019 def _get_msCharSet(self): 3020 raise NotImplementedError 3021 3022 def _set_msCharSet(self, value): 3023 raise NotImplementedError 3024 3025 msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms charset") 3026 3027 def _get_note(self): 3028 raise NotImplementedError 3029 3030 def _set_note(self, value): 3031 raise NotImplementedError 3032 3033 note = property(_get_note, _set_note, doc="note") 3034 3035 def _get_fondID(self): 3036 raise NotImplementedError 3037 3038 def _set_fondID(self, value): 3039 raise NotImplementedError 3040 3041 fondID = property(_get_fondID, _set_fondID, doc="fond id number") 3042 3043 def _get_uniqueID(self): 3044 raise NotImplementedError 3045 3046 def _set_uniqueID(self, value): 3047 raise NotImplementedError 3048 3049 uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique id number") 3050 3051 def _get_versionMajor(self): 3052 raise NotImplementedError 3053 3054 def _set_versionMajor(self, value): 3055 raise NotImplementedError 3056 3057 versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version major") 3058 3059 def _get_versionMinor(self): 3060 raise NotImplementedError 3061 3062 def _set_versionMinor(self, value): 3063 raise NotImplementedError 3064 3065 versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version minor") 3066 3067 def _get_year(self): 3068 raise NotImplementedError 3069 3070 def _set_year(self, value): 3071 raise NotImplementedError 3072 3073 year = property(_get_year, _set_year, doc="year") 3074 3075 def _get_copyright(self): 3076 raise NotImplementedError 3077 3078 def _set_copyright(self, value): 3079 raise NotImplementedError 3080 3081 copyright = property(_get_copyright, _set_copyright, doc="copyright") 3082 3083 def _get_notice(self): 3084 raise NotImplementedError 3085 3086 def _set_notice(self, value): 3087 raise NotImplementedError 3088 3089 notice = property(_get_notice, _set_notice, doc="notice") 3090 3091 def _get_trademark(self): 3092 raise NotImplementedError 3093 3094 def _set_trademark(self, value): 3095 raise NotImplementedError 3096 3097 trademark = property(_get_trademark, _set_trademark, doc="trademark") 3098 3099 def _get_license(self): 3100 raise NotImplementedError 3101 3102 def _set_license(self, value): 3103 raise NotImplementedError 3104 3105 license = property(_get_license, _set_license, doc="license") 3106 3107 def _get_licenseURL(self): 3108 raise NotImplementedError 3109 3110 def _set_licenseURL(self, value): 3111 raise NotImplementedError 3112 3113 licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license url") 3114 3115 def _get_createdBy(self): 3116 raise NotImplementedError 3117 3118 def _set_createdBy(self, value): 3119 raise NotImplementedError 3120 3121 createdBy = property(_get_createdBy, _set_createdBy, doc="source") 3122 3123 def _get_designer(self): 3124 raise NotImplementedError 3125 3126 def _set_designer(self, value): 3127 raise NotImplementedError 3128 3129 designer = property(_get_designer, _set_designer, doc="designer") 3130 3131 def _get_designerURL(self): 3132 raise NotImplementedError 3133 3134 def _set_designerURL(self, value): 3135 raise NotImplementedError 3136 3137 designerURL = property(_get_designerURL, _set_designerURL, doc="designer url") 3138 3139 def _get_vendorURL(self): 3140 raise NotImplementedError 3141 3142 def _set_vendorURL(self, value): 3143 raise NotImplementedError 3144 3145 vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor url") 3146 3147 def _get_ttVendor(self): 3148 raise NotImplementedError 3149 3150 def _set_ttVendor(self, value): 3151 raise NotImplementedError 3152 3153 ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") 3154 3155 def _get_ttUniqueID(self): 3156 raise NotImplementedError 3157 3158 def _set_ttUniqueID(self, value): 3159 raise NotImplementedError 3160 3161 ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="TrueType unique id number") 3162 3163 def _get_ttVersion(self): 3164 raise NotImplementedError 3165 3166 def _set_ttVersion(self, value): 3167 raise NotImplementedError 3168 3169 ttVersion = property(_get_ttVersion, _set_ttVersion, doc="TrueType version") 3170 3171 def _get_unitsPerEm(self): 3172 raise NotImplementedError 3173 3174 def _set_unitsPerEm(self): 3175 raise NotImplementedError 3176 3177 unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="unitsPerEm value") 3178 3179 def _get_ascender(self): 3180 raise NotImplementedError 3181 3182 def _set_ascender(self, value): 3183 raise NotImplementedError 3184 3185 ascender = property(_get_ascender, _set_ascender, doc="ascender value") 3186 3187 def _get_descender(self): 3188 raise NotImplementedError 3189 3190 def _set_descender(self, value): 3191 raise NotImplementedError 3192 3193 descender = property(_get_descender, _set_descender, doc="descender value") 3194 3195 def _get_capHeight(self): 3196 raise NotImplementedError 3197 3198 def _set_capHeight(self, value): 3199 raise NotImplementedError 3200 3201 capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") 3202 3203 def _get_xHeight(self): 3204 raise NotImplementedError 3205 3206 def _set_xHeight(self, value): 3207 raise NotImplementedError 3208 3209 xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") 3210 3211 def _get_defaultWidth(self): 3212 raise NotImplementedError 3213 3214 def _set_defaultWidth(self, value): 3215 raise NotImplementedError 3216 3217 defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") 3218 3219 def _get_italicAngle(self): 3220 raise NotImplementedError 3221 3222 def _set_italicAngle(self, value): 3223 raise NotImplementedError 3224 3225 italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") 3226 3227 def _get_slantAngle(self): 3228 raise NotImplementedError 3229 3230 def _set_slantAngle(self, value): 3231 raise NotImplementedError 3232 3233 slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") 3234 3235 def autoNaming(self, familyName=None, styleName=None): 3236 """Automatically set the font naming info based on family and style names.""" 3237 3238 if familyName is None: 3239 if not self.familyName: 3240 raise RoboFabError, "Family name and style name must be complete" 3241 else: 3242 self.familyName = familyName 3243 if styleName is None: 3244 if not self.styleName: 3245 raise RoboFabError, "Family name and style name must be complete" 3246 else: 3247 self.styleName = styleName 3248 family = self.familyName 3249 style = self.styleName 3250 self.fullName = ' '.join((family, style)) 3251 self.fontName = '-'.join((family, style)).replace(' ', '') 3252 self.fondName = family 3253 self.menuName = ' '.join((family, style)) 3254 self.otFamilyName = family 3255 self.otStyleName = style 3256 self.otMacName = ' '.join((family, style)) 3257 #self._hasChanged() 3258 3032 self._text = "" 3033 3034 def _get_text(self): 3035 return self._text 3036 3037 def _set_text(self, value): 3038 assert isinstance(value, basestring) 3039 self._text = value 3040 3041 text = property(_get_text, _set_text, doc="raw feature text.") 3042 3043 3259 3044 class BaseGroups(dict): 3260 3045 … … 3269 3054 if fontParent is not None: 3270 3055 try: 3271 font = fontParent.info. fullName3056 font = fontParent.info.postscriptFullName 3272 3057 except AttributeError: pass 3273 3058 return "<RGroups for %s>"%font … … 3313 3098 #do we have a font? 3314 3099 try: 3315 parent = parentObject.info. fullName3100 parent = parentObject.info.postscriptFullName 3316 3101 except AttributeError: 3317 3102 #or do we have a glyph? … … 3358 3143 if fontParent is not None: 3359 3144 try: 3360 font = fontParent.info. fullName3145 font = fontParent.info.postscriptFullName 3361 3146 except AttributeError: pass 3362 3147 return "<RKerning for %s>"%font … … 3706 3491 return self.__mul__(1.0/factor) 3707 3492 3708 trunk/Lib/robofab/objects/objectsFL.py
r163 r171 6 6 AllFonts, NewGlyph 7 7 from robofab.objects.objectsBase import BaseFont, BaseGlyph, BaseContour, BaseSegment,\ 8 BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, Base Groups, BaseLib,\8 BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, BaseKerning, BaseInfo, BaseFeatures, BaseGroups, BaseLib,\ 9 9 roundPt, addPt, _box,\ 10 10 MOVE, LINE, CORNER, CURVE, QCURVE, OFFCURVE,\ … … 17 17 from robofab.plistlib import Data, Dict, readPlist, writePlist 18 18 from StringIO import StringIO 19 from robofab import ufoLib 20 from warnings import warn 21 import datetime 22 from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab 23 24 25 try: 26 set 27 except NameError: 28 from sets import Set as set 19 29 20 30 # local encoding … … 23 33 else: 24 34 LOCAL_ENCODING = "latin-1" 35 36 _IN_UFO_EXPORT = False 25 37 26 38 # a list of attributes that are to be copied when copying a glyph. … … 106 118 stem_snap_v_num(integer) 107 119 stem_snap_v 108 109 110 111 120 """ 112 121 … … 122 131 from robofab.objects.objectsRF import PostScriptFontHintValues as _PostScriptFontHintValues 123 132 return _PostScriptFontHintValues(data=self.asDict()) 124 125 def _getBlueFuzz(self): 126 return self._object.blue_fuzz[self._masterIndex] 127 def _setBlueFuzz(self, value): 128 self._object.blue_fuzz[self._masterIndex] = value 129 130 def _getBlueScale(self): 131 return self._object.blue_scale[self._masterIndex] 132 def _setBlueScale(self, value): 133 self._object.blue_scale[self._masterIndex] = float(value) 134 135 def _getBlueShift(self): 136 return self._object.blue_shift[self._masterIndex] 137 def _setBlueShift(self, value): 138 self._object.blue_shift[self._masterIndex] = value 139 140 def _getForceBold(self): 141 return self._object.force_bold[self._masterIndex] == 1 142 143 def _setForceBold(self, value): 144 if value: 145 value = 1 146 else: 147 value = 0 148 self._object.force_bold[self._masterIndex] = value 149 150 # Note: these attributes are wrapppers for lists, 151 # but regular list operatons won't have any effect. 152 # you really have to _get_ and _set_ a list. 153 154 def _asPairs(self, l): 155 """Split a list of numbers into a list of pairs""" 156 if not len(l)%2 == 0: 157 l = l[:-1] 158 n = [[l[i], l[i+1]] for i in range(0, len(l), 2)] 159 n.sort() 160 return n 161 162 def _flattenPairs(self, l): 163 """The reverse of _asPairs""" 164 n = [] 165 l.sort() 166 for i in l: 167 assert len(i) == 2, "Each entry must consist of two numbers" 168 n.append(i[0]) 169 n.append(i[1]) 170 return n 171 172 def _checkForFontLabSanity(self, attribute, values): 173 """Function to handle problems with FontLab not allowing the max number of 174 alignment zones to be set to the max number. 175 Input: the name of the zones and the values to be set 176 Output: a warning when there are too many values to be set 177 and the max values which FontLab will allow. 178 """ 179 warn = False 180 if attribute in ['vStems', 'hStems']: 181 # the number of items to drop from the list if the list is too long, 182 # stems are single values, but the zones are pairs. 183 skip = 1 184 total = min(self._attributeNames[attribute]['max'], len(values)) 185 if total == self._attributeNames[attribute]['max']: 186 total = self._attributeNames[attribute]['max'] - skip 187 warn = True 188 else: 189 skip = 2 190 values = self._flattenPairs(values) 191 total = min(self._attributeNames[attribute]['max']*2, len(values)) 192 if total == self._attributeNames[attribute]['max']*2: 193 total = self._attributeNames[attribute]['max']*2 - skip 194 warn = True 195 if warn: 196 print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s."%(self._attributeNames[attribute]['max']-1, attribute, `values[total:]`) 197 return total, values[:total] 198 199 200 def _getBlueValues(self): 201 return self._asPairs(self._object.blue_values[self._masterIndex]) 202 def _setBlueValues(self, values): 203 total, values = self._checkForFontLabSanity('blueValues', values) 204 self._object.blue_values_num = total 205 for i in range(self._object.blue_values_num): 206 self._object.blue_values[self._masterIndex][i] = values[i] 207 208 def _getOtherBlues(self): 209 return self._asPairs(self._object.other_blues[self._masterIndex]) 210 def _setOtherBlues(self, values): 211 total, values = self._checkForFontLabSanity('otherBlues', values) 212 self._object.other_blues_num = total 213 for i in range(self._object.other_blues_num): 214 self._object.other_blues[self._masterIndex][i] = values[i] 215 216 def _getFamilyBlues(self): 217 return self._asPairs(self._object.family_blues[self._masterIndex]) 218 def _setFamilyBlues(self, values): 219 total, values = self._checkForFontLabSanity('familyBlues', values) 220 self._object.family_blues_num = total 221 for i in range(self._object.family_blues_num): 222 self._object.family_blues[self._masterIndex][i] = values[i] 223 224 def _getFamilyOtherBlues(self): 225 return self._asPairs(self._object.family_other_blues[self._masterIndex]) 226 def _setFamilyOtherBlues(self, values): 227 total, values = self._checkForFontLabSanity('familyOtherBlues', values) 228 self._object.family_other_blues_num = total 229 for i in range(self._object.family_other_blues_num): 230 self._object.family_other_blues[self._masterIndex][i] = values[i] 231 232 def _getVStems(self): 233 return list(self._object.stem_snap_v[self._masterIndex]) 234 def _setVStems(self, values): 235 total, values = self._checkForFontLabSanity('vStems', values) 236 self._object.stem_snap_v_num = total 237 for i in range(self._object.stem_snap_v_num): 238 self._object.stem_snap_v[self._masterIndex][i] = values[i] 239 240 def _getHStems(self): 241 return list(self._object.stem_snap_h[self._masterIndex]) 242 def _setHStems(self, values): 243 total, values = self._checkForFontLabSanity('hStems', values) 244 self._object.stem_snap_h_num = total 245 for i in range(self._object.stem_snap_h_num): 246 self._object.stem_snap_h[self._masterIndex][i] = values[i] 247 248 blueFuzz = property(_getBlueFuzz, _setBlueFuzz, doc="postscript hints: bluefuzz value") 249 blueScale = property(_getBlueScale, _setBlueScale, doc="postscript hints: bluescale value") 250 blueShift = property(_getBlueShift, _setBlueShift, doc="postscript hints: blueshift value") 251 forceBold = property(_getForceBold, _setForceBold, doc="postscript hints: force bold value") 252 blueValues = property(_getBlueValues, _setBlueValues, doc="postscript hints: blue values") 253 otherBlues = property(_getOtherBlues, _setOtherBlues, doc="postscript hints: other blue values") 254 familyBlues = property(_getFamilyBlues, _setFamilyBlues, doc="postscript hints: family blue values") 255 familyOtherBlues = property(_getFamilyOtherBlues, _setFamilyOtherBlues, doc="postscript hints: family other blue values") 256 vStems = property(_getVStems, _setVStems, doc="postscript hints: vertical stem values") 257 hStems = property(_getHStems, _setHStems, doc="postscript hints: horizontal stem values") 258 133 259 134 260 135 class PostScriptGlyphHintValues(BasePostScriptGlyphHintValues): … … 503 378 fl.Add(f) 504 379 rf = RFont(f) 505 rf.info.familyName = familyName 506 rf.info.styleName = styleName 380 if familyName is not None: 381 rf.info.familyName = familyName 382 if styleName is not None: 383 rf.info.styleName = styleName 507 384 return rf 508 385 … … 560 437 self._lib = {} 561 438 self._supportHints = True 439 self.psHints = PostScriptFontHintValues(self) 440 self.psHints.setParent(self) 562 441 563 442 def keys(self): … … 601 480 602 481 603 def _get_psHints(self): 604 return PostScriptFontHintValues(self) 605 606 psHints = property(_get_psHints, doc="font level postscript hint data") 482 # def _get_psHints(self): 483 # h = PostScriptFontHintValues(self) 484 # h.setParent(self) 485 # return h 486 # 487 # psHints = property(_get_psHints, doc="font level postscript hint data") 607 488 608 489 def _get_info(self): … … 610 491 611 492 info = property(_get_info, doc="font info object") 612 493 494 def _get_features(self): 495 return RFeatures(self._object) 496 497 features = property(_get_features, doc="features object") 498 613 499 def _get_kerning(self): 614 500 kerning = {} … … 1026 912 fl.ifont = self.fontIndex 1027 913 fl.GenerateFont(flOutputType, finalPath) 1028 914 915 def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, 916 doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None, formatVersion=2): 917 from robofab.interface.all.dialogs import ProgressBar, Message 918 # special glyph name to file name conversion 919 if glyphNameToFileNameFunc is None: 920 glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() 921 if glyphNameToFileNameFunc is None: 922 from robofab.tools.glyphNameSchemes import glyphNameToShortFileName 923 glyphNameToFileNameFunc = glyphNameToShortFileName 924 # get a valid path 925 if not path: 926 if self.path is None: 927 Message("Please save this font first before exporting to UFO...") 928 return 929 else: 930 path = ufoLib.makeUFOPath(self.path) 931 # get the glyphs to export 932 if glyphs is None: 933 glyphs = self.keys() 934 # if the file exists, check the format version. 935 # if the format version being written is different 936 # from the format version of the existing UFO 937 # and only some files are set to be written 938 # raise an error. 939 if os.path.exists(path): 940 if os.path.exists(os.path.join(path, "metainfo.plist")): 941 reader = ufoLib.UFOReader(path) 942 existingFormatVersion = reader.formatVersion 943 if formatVersion != existingFormatVersion: 944 if False in [doInfo, doKerning, doGroups, doLib, doFeatures, set(glyphs) == set(self.keys())]: 945 Message("When overwriting an existing UFO with a different format version all files must be written.") 946 return 947 # the lib must be written if format version is 1 948 if not doLib and formatVersion == 1: 949 Message("The lib must be written when exporting format version 1.") 950 return 951 # set up the progress bar 952 nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) 953 bar = None 954 if doProgress: 955 bar = ProgressBar("Exporting UFO", nonGlyphCount + len(glyphs)) 956 # try writing 957 try: 958 writer = ufoLib.UFOWriter(path, formatVersion=formatVersion) 959 ## We make a shallow copy if lib, since we add some stuff for export 960 ## that doesn't need to be retained in memory. 961 fontLib = dict(self.lib) 962 # write the font info 963 if doInfo: 964 global _IN_UFO_EXPORT 965 _IN_UFO_EXPORT = True 966 writer.writeInfo(self.info) 967 _IN_UFO_EXPORT = False 968 if bar: 969 bar.tick() 970 # write the kerning 971 if doKerning: 972 writer.writeKerning(self.kerning.asDict()) 973 if bar: 974 bar.tick() 975 # write the groups 976 if doGroups: 977 writer.writeGroups(self.groups) 978 if bar: 979 bar.tick() 980 # write the features 981 if doFeatures: 982 if formatVersion == 2: 983 writer.writeFeatures(self.features.text) 984 else: 985 self._writeOpenTypeFeaturesToLib(fontLib) 986 if bar: 987 bar.tick() 988 # write the lib 989 if doLib: 990 ## Always export the postscript font hint values to the lib in format version 1 991 if formatVersion == 1: 992 d = self.psHints.asDict() 993 fontLib[postScriptHintDataLibKey] = d 994 ## Export the glyph order to the lib 995 glyphOrder = [nakedGlyph.name for nakedGlyph in self.naked().glyphs] 996 fontLib["org.robofab.glyphOrder"] = glyphOrder 997 ## export the features 998 if doFeatures and formatVersion == 1: 999 self._writeOpenTypeFeaturesToLib(fontLib) 1000 if bar: 1001 bar.tick() 1002 writer.writeLib(fontLib) 1003 if bar: 1004 bar.tick() 1005 # write the glyphs 1006 if glyphs: 1007 glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) 1008 count = nonGlyphCount 1009 for nakedGlyph in self.naked().glyphs: 1010 if nakedGlyph.name not in glyphs: 1011 continue 1012 glyph = RGlyph(nakedGlyph) 1013 if doHints: 1014 hintStuff = _glyphHintsToDict(glyph.naked()) 1015 if hintStuff: 1016 glyph.lib[postScriptHintDataLibKey] = hintStuff 1017 glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) 1018 # remove the hint dict from the lib 1019 if doHints and glyph.lib.has_key(postScriptHintDataLibKey): 1020 del glyph.lib[postScriptHintDataLibKey] 1021 if bar and not count % 10: 1022 bar.tick(count) 1023 count = count + 1 1024 glyphSet.writeContents() 1025 # only blindly stop if the user says to 1026 except KeyboardInterrupt: 1027 if bar: 1028 bar.close() 1029 bar = None 1030 # kill the bar 1031 if bar: 1032 bar.close() 1033 1029 1034 def _writeOpenTypeFeaturesToLib(self, fontLib): 1035 # this should only be used for UFO format version 1 1030 1036 flFont = self.naked() 1031 if flFont.ot_classes: 1032 fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings( 1033 flFont.ot_classes) 1037 fontLib["org.robofab.opentype.classes"] = _normalizeLineEndings(flFont.ot_classes).rstrip() + "\n" 1034 1038 if flFont.features: 1035 1039 features = {} … … 1037 1041 for feature in flFont.features: 1038 1042 order.append(feature.tag) 1039 features[feature.tag] = _normalizeLineEndings(feature.value) 1043 features[feature.tag] = _normalizeLineEndings(feature.value).rstrip() + "\n" 1040 1044 fontLib["org.robofab.opentype.features"] = features 1041 1045 fontLib["org.robofab.opentype.featureorder"] = order 1042 1043 def writeUFO(self, path=None, doProgress=False, glyphNameToFileNameFunc=None, doHints=False): 1044 """write a font to .ufo""" 1045 from robofab.ufoLib import makeUFOPath, UFOWriter 1046 1047 def readUFO(self, path, doProgress=False, 1048 doHints=False, doInfo=True, doKerning=True, doGroups=True, doLib=True, doFeatures=True, glyphs=None): 1049 """read a .ufo into the font""" 1050 from robofab.pens.flPen import FLPointPen 1046 1051 from robofab.interface.all.dialogs import ProgressBar 1047 if glyphNameToFileNameFunc is None: 1048 glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() 1049 if glyphNameToFileNameFunc is None: 1050 from robofab.tools.glyphNameSchemes import glyphNameToShortFileName 1051 glyphNameToFileNameFunc = glyphNameToShortFileName 1052 if not path: 1053 if self.path is None: 1054 # XXX this should really raise an exception instead 1055 from robofab.interface.all.dialogs import Message 1056 Message("Please save this font first before exporting to UFO...") 1057 return 1058 else: 1059 path = makeUFOPath(self.path) 1060 nonGlyphCount = 4 1052 # start up the reader 1053 reader = ufoLib.UFOReader(path) 1054 glyphSet = reader.getGlyphSet() 1055 # get a list of glyphs that should be imported 1056 if glyphs is None: 1057 glyphs = glyphSet.keys() 1058 # set up the progress bar 1059 nonGlyphCount = [doInfo, doKerning, doGroups, doLib, doFeatures].count(True) 1061 1060 bar = None 1062 1061 if doProgress: 1063 bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self.glyphs)) 1062 bar = ProgressBar("Importing UFO", nonGlyphCount + len(glyphs)) 1063 # start reading 1064 1064 try: 1065 u = UFOWriter(path) 1066 u.writeInfo(self.info) 1067 if bar: 1068 bar.tick() 1069 u.writeKerning(self.kerning.asDict()) 1070 if bar: 1071 bar.tick() 1072 u.writeGroups(self.groups) 1073 if bar: 1074 bar.tick() 1075 count = nonGlyphCount 1076 glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) 1077 glyphOrder = [] 1078 for nakedGlyph in self.naked().glyphs: 1079 glyph = RGlyph(nakedGlyph) 1080 glyphOrder.append(glyph.name) 1065 fontLib = reader.readLib() 1066 # info 1067 if doInfo: 1068 reader.readInfo(self.info) 1069 if bar: 1070 bar.tick() 1071 # glyphs 1072 count = 1 1073 glyphOrder = self._getGlyphOrderFromLib(fontLib, glyphSet) 1074 for glyphName in glyphOrder: 1075 if glyphName not in glyphs: 1076 continue 1077 glyph = self.newGlyph(glyphName, clear=True) 1078 pen = FLPointPen(glyph.naked()) 1079 glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) 1081 1080 if doHints: 1082 hintStuff = _glyphHintsToDict(glyph.naked()) 1083 if hintStuff: 1084 glyph.lib[postScriptHintDataLibKey] = hintStuff 1085 glyphSet.writeGlyph(glyph.name, glyph, glyph.drawPoints) 1086 # remove the hint dict from the lib 1087 if doHints and glyph.lib.has_key(postScriptHintDataLibKey): 1088 del glyph.lib[postScriptHintDataLibKey] 1081 hintData = glyph.lib.get(postScriptHintDataLibKey) 1082 if hintData: 1083 _dictHintsToGlyph(glyph.naked(), hintData) 1084 # now that the hints have been extracted from the glyph 1085 # there is no reason to keep the location in the lib. 1086 if glyph.lib.has_key(postScriptHintDataLibKey): 1087 del glyph.lib[postScriptHintDataLibKey] 1088 glyph.update() 1089 1089 if bar and not count % 10: 1090 1090 bar.tick(count) 1091 1091 count = count + 1 1092 assert None not in glyphOrder, glyphOrder 1093 glyphSet.writeContents() 1094 # We make a shallow copy if lib, since we add some stuff for export 1095 # that doesn't need to be retained in memory. 1096 fontLib = dict(self.lib) 1097 # Always export the postscript font hint values 1098 psh = PostScriptFontHintValues(self) 1099 d = psh.asDict() 1100 fontLib[postScriptHintDataLibKey] = d 1101 # Export the glyph order 1102 fontLib["org.robofab.glyphOrder"] = glyphOrder 1103 self._writeOpenTypeFeaturesToLib(fontLib) 1104 u.writeLib(fontLib) 1105 if bar: 1106 bar.tick() 1092 # features 1093 if doFeatures: 1094 if reader.formatVersion == 1: 1095 self._readOpenTypeFeaturesFromLib(fontLib) 1096 else: 1097 featureText = reader.readFeatures() 1098 self.features.text = featureText 1099 if bar: 1100 bar.tick() 1101 else: 1102 # remove features stored in the lib 1103 self._readOpenTypeFeaturesFromLib(fontLib, setFeatures=False) 1104 # kerning 1105 if doKerning: 1106 self.kerning.clear() 1107 self.kerning.update(reader.readKerning()) 1108 if bar: 1109 bar.tick() 1110 # groups 1111 if doGroups: 1112 self.groups.clear() 1113 self.groups.update(reader.readGroups()) 1114 if bar: 1115 bar.tick() 1116 # hints in format version 1 1117 if doHints and reader.formatVersion == 1: 1118 self.psHints._loadFromLib(fontLib) 1119 else: 1120 # remove hint data stored in the lib 1121 if fontLib.has_key(postScriptHintDataLibKey): 1122 del fontLib[postScriptHintDataLibKey] 1123 # lib 1124 if doLib: 1125 self.lib.clear() 1126 self.lib.update(fontLib) 1127 if bar: 1128 bar.tick() 1129 # only blindly stop if the user says to 1107 1130 except KeyboardInterrupt: 1108 if bar: 1109 bar.close() 1131 bar.close() 1110 1132 bar = None 1133 # kill the bar 1111 1134 if bar: 1112 1135 bar.close() 1113 1136 1114 1137 def _getGlyphOrderFromLib(self, fontLib, glyphSet): 1115 1138 glyphOrder = fontLib.get("org.robofab.glyphOrder") … … 1130 1153 else: 1131 1154 glyphNames = glyphSet.keys() 1132 # Sort according to unicode would be best, but is really1133 # expensive...1134 1155 glyphNames.sort() 1135 1156 return glyphNames 1136 1157 1137 def _readOpenTypeFeaturesFromLib(self, fontLib): 1158 def _readOpenTypeFeaturesFromLib(self, fontLib, setFeatures=True): 1159 # setFeatures may be False. in this case, this method 1160 # should only clear the data from the lib. 1138 1161 classes = fontLib.get("org.robofab.opentype.classes") 1139 1162 if classes is not None: 1140 1163 del fontLib["org.robofab.opentype.classes"] 1141 self.naked().ot_classes = classes 1164 if setFeatures: 1165 self.naked().ot_classes = classes 1142 1166 features = fontLib.get("org.robofab.opentype.features") 1143 1167 if features is not None: … … 1156 1180 if oneFeature is not None: 1157 1181 orderedFeatures.append((tag, oneFeature)) 1158 self.naked().features.clean() 1159 for tag, src in orderedFeatures: 1160 self.naked().features.append(Feature(tag, src)) 1161 1162 def readUFO(self, path, doProgress=False, doHints=True): 1163 """read a .ufo into the font""" 1164 from robofab.ufoLib import UFOReader 1165 from robofab.pens.flPen import FLPointPen 1166 from robofab.interface.all.dialogs import ProgressBar 1167 nonGlyphCount = 4 1168 bar = None 1169 u = UFOReader(path) 1170 glyphSet = u.getGlyphSet() 1171 fontLib = u.readLib() 1172 glyphNames = self._getGlyphOrderFromLib(fontLib, glyphSet) 1173 if doProgress: 1174 bar = ProgressBar('Importing UFO', nonGlyphCount+len(glyphNames)) 1175 try: 1176 u.readInfo(self.info) 1177 if bar: 1178 bar.tick() 1179 self._readOpenTypeFeaturesFromLib(fontLib) 1180 self.lib.clear() 1181 self.lib = fontLib 1182 if bar: 1183 bar.tick() 1184 count = 2 1185 for glyphName in glyphNames: 1186 glyph = self.newGlyph(glyphName, clear=True) 1187 pen = FLPointPen(glyph.naked()) 1188 glyphSet.readGlyph(glyphName=glyphName, glyphObject=glyph, pointPen=pen) 1189 if doHints: 1190 hintData = glyph.lib.get(postScriptHintDataLibKey) 1191 if hintData: 1192 _dictHintsToGlyph(glyph.naked(), hintData) 1193 # now that the hints have been extracted from the glyph 1194 # there is no reason to keep the location in the lib. 1195 if glyph.lib.has_key(postScriptHintDataLibKey): 1196 del glyph.lib[postScriptHintDataLibKey] 1197 glyph.update() 1198 if bar and not count % 10: 1199 bar.tick(count) 1200 count = count + 1 1201 # import postscript font hint data 1202 self.psHints._loadFromLib(fontLib) 1203 self.kerning.clear() 1204 self.kerning.update(u.readKerning()) 1205 if bar: 1206 bar.tick() 1207 self.groups.clear() 1208 self.groups = u.readGroups() 1209 except KeyboardInterrupt: 1210 bar.close() 1211 bar = None 1212 if bar: 1213 bar.close() 1182 if setFeatures: 1183 self.naked().features.clean() 1184 for tag, src in orderedFeatures: 1185 self.naked().features.append(Feature(tag, src)) 1186 1214 1187 1215 1188 … … 2292 2265 # do we have a font? 2293 2266 try: 2294 parent = parentObject.info. fullName2267 parent = parentObject.info.postscriptFullName 2295 2268 except AttributeError: 2296 2269 # or do we have a glyph? … … 2609 2582 return i 2610 2583 2611 2584 2585 def _infoMapDict(**kwargs): 2586 default = dict( 2587 nakedAttribute=None, 2588 type=None, 2589 requiresSetNum=False, 2590 masterSpecific=False, 2591 libLocation=None, 2592 specialGetSet=False 2593 ) 2594 default.update(kwargs) 2595 return default 2596 2597 def _flipDict(d): 2598 f = {} 2599 for k, v in d.items(): 2600 f[v] = k 2601 return f 2602 2603 _styleMapStyleName_fromFL = { 2604 64 : "regular", 2605 1 : "italic", 2606 32 : "bold", 2607 33 : "bold italic" 2608 } 2609 _styleMapStyleName_toFL = _flipDict(_styleMapStyleName_fromFL) 2610 2611 _postscriptWindowsCharacterSet_fromFL = { 2612 0 : 1, 2613 1 : 2, 2614 2 : 3, 2615 77 : 4, 2616 128 : 5, 2617 129 : 6, 2618 130 : 7, 2619 134 : 8, 2620 136 : 9, 2621 161 : 10, 2622 162 : 11, 2623 163 : 12, 2624 177 : 13, 2625 178 : 14, 2626 186 : 15, 2627 200 : 16, 2628 204 : 17, 2629 222 : 18, 2630 238 : 19, 2631 255 : 20, 2632 } 2633 _postscriptWindowsCharacterSet_toFL = _flipDict(_postscriptWindowsCharacterSet_fromFL) 2634 2635 _openTypeOS2Type_toFL = { 2636 1 : 0x0002, 2637 2 : 0x0004, 2638 3 : 0x0008, 2639 8 : 0x0100, 2640 9 : 0x0200, 2641 } 2642 _openTypeOS2Type_fromFL = _flipDict(_openTypeOS2Type_toFL) 2643 2644 _openTypeOS2WidthClass_fromFL = { 2645 "Ultra-condensed" : 1, 2646 "Extra-condensed" : 2, 2647 "Condensed" : 3, 2648 "Semi-condensed" : 4, 2649 "Medium (normal)" : 5, 2650 "Semi-expanded" : 6, 2651 "Expanded" : 7, 2652 "Extra-expanded" : 8, 2653 "Ultra-expanded" : 9, 2654 } 2655 _openTypeOS2WidthClass_toFL = _flipDict(_openTypeOS2WidthClass_fromFL) 2656 2657 _postscriptHintAttributes = set(( 2658 "postscriptBlueValues", 2659 "postscriptOtherBlues", 2660 "postscriptFamilyBlues", 2661 "postscriptFamilyOtherBlues", 2662 "postscriptStemSnapH", 2663 "postscriptStemSnapV", 2664 )) 2665 2666 2612 2667 class RInfo(BaseInfo): 2613 2668 2614 2669 """RoboFab wrapper for FL Font Info""" 2615 2670 2616 2671 _title = "FLInfo" 2617 2672 2673 _ufoToFLAttrMapping = { 2674 "familyName" : _infoMapDict(valueType=str, nakedAttribute="family_name"), 2675 "styleName" : _infoMapDict(valueType=str, nakedAttribute="style_name"), 2676 "styleMapFamilyName" : _infoMapDict(valueType=str, nakedAttribute="menu_name"), 2677 "styleMapStyleName" : _infoMapDict(valueType=str, nakedAttribute="font_style", specialGetSet=True), 2678 "versionMajor" : _infoMapDict(valueType=int, nakedAttribute="version_major"), 2679 "versionMinor" : _infoMapDict(valueType=int, nakedAttribute="version_minor"), 2680 "year" : _infoMapDict(valueType=int, nakedAttribute="year"), 2681 "copyright" : _infoMapDict(valueType=str, nakedAttribute="copyright"), 2682 "trademark" : _infoMapDict(valueType=str, nakedAttribute="trademark"), 2683 "unitsPerEm" : _infoMapDict(valueType=int, nakedAttribute="upm"), 2684 "descender" : _infoMapDict(valueType=int, nakedAttribute="descender", masterSpecific=True), 2685 "xHeight" : _infoMapDict(valueType=int, nakedAttribute="x_height", masterSpecific=True), 2686 "capHeight" : _infoMapDict(valueType=int, nakedAttribute="cap_height", masterSpecific=True), 2687 "ascender" : _infoMapDict(valueType=int, nakedAttribute="ascender", masterSpecific=True), 2688 "italicAngle" : _infoMapDict(valueType=float, nakedAttribute="italic_angle"), 2689 "note" : _infoMapDict(valueType=str, nakedAttribute="note"), 2690 "openTypeHeadCreated" : _infoMapDict(valueType=str, nakedAttribute=None, specialGetSet=True), # i can't figure out the ttinfo.head_creation values 2691 "openTypeHeadLowestRecPPEM" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.head_lowest_rec_ppem"), 2692 "openTypeHeadFlags" : _infoMapDict(valueType="intList", nakedAttribute=None), # There is an attribute (ttinfo.head_flags), but no user interface. 2693 "openTypeHheaAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_ascender"), 2694 "openTypeHheaDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_descender"), 2695 "openTypeHheaLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.hhea_line_gap"), 2696 "openTypeHheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), 2697 "openTypeHheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), 2698 "openTypeHheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), 2699 "openTypeNameDesigner" : _infoMapDict(valueType=str, nakedAttribute="designer"), 2700 "openTypeNameDesignerURL" : _infoMapDict(valueType=str, nakedAttribute="designer_url"), 2701 "openTypeNameManufacturer" : _infoMapDict(valueType=str, nakedAttribute="source"), 2702 "openTypeNameManufacturerURL" : _infoMapDict(valueType=str, nakedAttribute="vendor_url"), 2703 "openTypeNameLicense" : _infoMapDict(valueType=str, nakedAttribute="license"), 2704 "openTypeNameLicenseURL" : _infoMapDict(valueType=str, nakedAttribute="license_url"), 2705 "openTypeNameVersion" : _infoMapDict(valueType=str, nakedAttribute="tt_version"), 2706 "openTypeNameUniqueID" : _infoMapDict(valueType=str, nakedAttribute="tt_u_id"), 2707 "openTypeNameDescription" : _infoMapDict(valueType=str, nakedAttribute="notice"), 2708 "openTypeNamePreferredFamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_family_name"), 2709 "openTypeNamePreferredSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute="pref_style_name"), 2710 "openTypeNameCompatibleFullName" : _infoMapDict(valueType=str, nakedAttribute="mac_compatible"), 2711 "openTypeNameSampleText" : _infoMapDict(valueType=str, nakedAttribute=None), 2712 "openTypeNameWWSFamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), 2713 "openTypeNameWWSSubfamilyName" : _infoMapDict(valueType=str, nakedAttribute=None), 2714 "openTypeOS2WidthClass" : _infoMapDict(valueType=int, nakedAttribute="width"), 2715 "openTypeOS2WeightClass" : _infoMapDict(valueType=int, nakedAttribute="weight_code", specialGetSet=True), 2716 "openTypeOS2Selection" : _infoMapDict(valueType="intList", nakedAttribute=None), # ttinfo.os2_fs_selection only returns 0 2717 "openTypeOS2VendorID" : _infoMapDict(valueType=str, nakedAttribute="vendor"), 2718 "openTypeOS2Panose" : _infoMapDict(valueType="intList", nakedAttribute="panose", specialGetSet=True), 2719 "openTypeOS2FamilyClass" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_s_family_class", specialGetSet=True), 2720 "openTypeOS2UnicodeRanges" : _infoMapDict(valueType="intList", nakedAttribute="unicoderanges"), 2721 "openTypeOS2CodePageRanges" : _infoMapDict(valueType="intList", nakedAttribute="codepages"), 2722 "openTypeOS2TypoAscender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_ascender"), 2723 "openTypeOS2TypoDescender" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_descender"), 2724 "openTypeOS2TypoLineGap" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_s_typo_line_gap"), 2725 "openTypeOS2WinAscent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_ascent"), 2726 "openTypeOS2WinDescent" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_us_win_descent", specialGetSet=True), 2727 "openTypeOS2Type" : _infoMapDict(valueType="intList", nakedAttribute="ttinfo.os2_fs_type", specialGetSet=True), 2728 "openTypeOS2SubscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_size"), 2729 "openTypeOS2SubscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_size"), 2730 "openTypeOS2SubscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_x_offset"), 2731 "openTypeOS2SubscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_subscript_y_offset"), 2732 "openTypeOS2SuperscriptXSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_size"), 2733 "openTypeOS2SuperscriptYSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_size"), 2734 "openTypeOS2SuperscriptXOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_x_offset"), 2735 "openTypeOS2SuperscriptYOffset" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_superscript_y_offset"), 2736 "openTypeOS2StrikeoutSize" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_size"), 2737 "openTypeOS2StrikeoutPosition" : _infoMapDict(valueType=int, nakedAttribute="ttinfo.os2_y_strikeout_position"), 2738 "openTypeVheaVertTypoAscender" : _infoMapDict(valueType=int, nakedAttribute=None), 2739 "openTypeVheaVertTypoDescender" : _infoMapDict(valueType=int, nakedAttribute=None), 2740 "openTypeVheaVertTypoLineGap" : _infoMapDict(valueType=int, nakedAttribute=None), 2741 "openTypeVheaCaretSlopeRise" : _infoMapDict(valueType=int, nakedAttribute=None), 2742 "openTypeVheaCaretSlopeRun" : _infoMapDict(valueType=int, nakedAttribute=None), 2743 "openTypeVheaCaretOffset" : _infoMapDict(valueType=int, nakedAttribute=None), 2744 "postscriptFontName" : _infoMapDict(valueType=str, nakedAttribute="font_name"), 2745 "postscriptFullName" : _infoMapDict(valueType=str, nakedAttribute="full_name"), 2746 "postscriptSlantAngle" : _infoMapDict(valueType=float, nakedAttribute="slant_angle"), 2747 "postscriptUniqueID" : _infoMapDict(valueType=int, nakedAttribute="unique_id"), 2748 "postscriptUnderlineThickness" : _infoMapDict(valueType=int, nakedAttribute="underline_thickness"), 2749 "postscriptUnderlinePosition" : _infoMapDict(valueType=int, nakedAttribute="underline_position"), 2750 "postscriptIsFixedPitch" : _infoMapDict(valueType=bool, nakedAttribute="is_fixed_pitch"), 2751 "postscriptBlueValues" : _infoMapDict(valueType="intList", nakedAttribute="blue_values", masterSpecific=True, requiresSetNum=True), 2752 "postscriptOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="other_blues", masterSpecific=True, requiresSetNum=True), 2753 "postscriptFamilyBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_blues", masterSpecific=True, requiresSetNum=True), 2754 "postscriptFamilyOtherBlues" : _infoMapDict(valueType="intList", nakedAttribute="family_other_blues", masterSpecific=True, requiresSetNum=True), 2755 "postscriptStemSnapH" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_h", masterSpecific=True, requiresSetNum=True), 2756 "postscriptStemSnapV" : _infoMapDict(valueType="intList", nakedAttribute="stem_snap_v", masterSpecific=True, requiresSetNum=True), 2757 "postscriptBlueFuzz" : _infoMapDict(valueType=int, nakedAttribute="blue_fuzz", masterSpecific=True), 2758 "postscriptBlueShift" : _infoMapDict(valueType=int, nakedAttribute="blue_shift", masterSpecific=True), 2759 "postscriptBlueScale" : _infoMapDict(valueType=float, nakedAttribute="blue_scale", masterSpecific=True), 2760 "postscriptForceBold" : _infoMapDict(valueType=bool, nakedAttribute="force_bold", masterSpecific=True), 2761 "postscriptDefaultWidthX" : _infoMapDict(valueType=int, nakedAttribute="default_width", masterSpecific=True), 2762 "postscriptNominalWidthX" : _infoMapDict(valueType=int, nakedAttribute=None), 2763 "postscriptWeightName" : _infoMapDict(valueType=str, nakedAttribute="weight"), 2764 "postscriptDefaultCharacter" : _infoMapDict(valueType=str, nakedAttribute="default_character"), 2765 "postscriptWindowsCharacterSet" : _infoMapDict(valueType=int, nakedAttribute="ms_charset", specialGetSet=True), 2766 "macintoshFONDFamilyID" : _infoMapDict(valueType=int, nakedAttribute="fond_id"), 2767 "macintoshFONDName" : _infoMapDict(valueType=str, nakedAttribute="apple_name"), 2768 } 2769 _environmentOverrides = ["width", "openTypeOS2WidthClass"] # ugh. 2770 2618 2771 def __init__(self, font): 2619 BaseInfo.__init__(self)2772 super(RInfo, self).__init__() 2620 2773 self._object = font 2621 2622 def _get_familyName(self): 2623 return self._object.family_name 2624 2625 def _set_familyName(self, value): 2626 self._object.family_name = value 2627 2628 familyName = property(_get_familyName, _set_familyName, doc="family_name") 2629 2630 def _get_styleName(self): 2631 return self._object.style_name 2632 2633 def _set_styleName(self, value): 2634 self._object.style_name = value 2635 2636 styleName = property(_get_styleName, _set_styleName, doc="style_name") 2637 2638 def _get_fullName(self): 2639 return self._object.full_name 2640 2641 def _set_fullName(self, value): 2642 self._object.full_name = value 2643 2644 fullName = property(_get_fullName, _set_fullName, doc="full_name") 2645 2646 def _get_fontName(self): 2647 return self._object.font_name 2648 2649 def _set_fontName(self, value): 2650 self._object.font_name = value 2651 2652 fontName = property(_get_fontName, _set_fontName, doc="font_name") 2653 2654 def _get_menuName(self): 2655 return self._object.menu_name 2656 2657 def _set_menuName(self, value): 2658 self._object.menu_name = value 2659 2660 menuName = property(_get_menuName, _set_menuName, doc="menu_name") 2661 2662 def _get_fondName(self): 2663 return self._object.apple_name 2664 2665 def _set_fondName(self, value): 2666 self._object.apple_name = value 2667 2668 fondName = property(_get_fondName, _set_fondName, doc="apple_name") 2669 2670 def _get_otFamilyName(self): 2671 return self._object.pref_family_name 2672 2673 def _set_otFamilyName(self, value): 2674 self._object.pref_family_name = value 2675 2676 otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") 2677 2678 def _get_otStyleName(self): 2679 return self._object.pref_style_name 2680 2681 def _set_otStyleName(self, value): 2682 self._object.pref_style_name = value 2683 2684 otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") 2685 2686 def _get_otMacName(self): 2687 return self._object.mac_compatible 2688 2689 def _set_otMacName(self, value): 2690 self._object.mac_compatible = value 2691 2692 otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") 2693 2694 def _get_weightValue(self): 2695 return self._object.weight_code 2696 2697 def _set_weightValue(self, value): 2698 value = int(round(value)) # FL can't take float - 28/8/07 / evb 2774 2775 def _environmentSetAttr(self, attr, value): 2776 # special fontlab workarounds 2777 if attr == "width": 2778 warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) 2779 attr = "openTypeOS2WidthClass" 2780 if attr == "openTypeOS2WidthClass": 2781 if isinstance(value, basestring) and value not in _openTypeOS2WidthClass_toFL: 2782 print "The openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification. The value will be set into the FontLab file for now." % value 2783 self._object.width = value 2784 else: 2785 self._object.width = _openTypeOS2WidthClass_toFL[value] 2786 return 2787 # get the attribute data 2788 data = self._ufoToFLAttrMapping[attr] 2789 flAttr = data["nakedAttribute"] 2790 valueType = data["valueType"] 2791 masterSpecific = data["masterSpecific"] 2792 requiresSetNum = data["requiresSetNum"] 2793 specialGetSet = data["specialGetSet"] 2794 # warn about setting attributes not supported by FL 2795 if flAttr is None: 2796 print "The attribute %s is not supported by FontLab. This data will not be set." % attr 2797 return 2798 # make sure that the value is the proper type for FL 2799 if valueType == "intList": 2800 value = [int(i) for i in value] 2801 elif valueType == str: 2802 if value is None: 2803 value = "" 2804 value = value.encode(LOCAL_ENCODING) 2805 elif valueType == int and not isinstance(value, int): 2806 value = int(round(value)) 2807 elif not isinstance(value, valueType): 2808 value = valueType(value) 2809 # handle postscript hint bug in FL 2810 if attr in _postscriptHintAttributes: 2811 value = self._handlePSHintBug(attr, value) 2812 # handle special cases 2813 if specialGetSet: 2814 attr = "_set_%s" % attr 2815 method = getattr(self, attr) 2816 return method(value) 2817 # set the value 2818 obj = self._object 2819 if len(flAttr.split(".")) > 1: 2820 flAttrList = flAttr.split(".") 2821 for i in flAttrList[:-1]: 2822 obj = getattr(obj, i) 2823 flAttr = flAttrList[-1] 2824 ## set the foo_num attribute if necessary 2825 if requiresSetNum: 2826 numAttr = flAttr + "_num" 2827 setattr(obj, numAttr, len(value)) 2828 ## set master 0 if the data is master specific 2829 if masterSpecific: 2830 subObj = getattr(obj, flAttr) 2831 if valueType == "intList": 2832 for index, v in enumerate(value): 2833 subObj[0][index] = v 2834 else: 2835 subObj[0] = value 2836 ## otherwise use a regular set 2837 else: 2838 setattr(obj, flAttr, value) 2839 2840 def _environmentGetAttr(self, attr): 2841 # special fontlab workarounds 2842 if attr == "width": 2843 warn("The width attribute has been deprecated. Use the new openTypeOS2WidthClass attribute.", DeprecationWarning) 2844 attr = "openTypeOS2WidthClass" 2845 if attr == "openTypeOS2WidthClass": 2846 value = self._object.width 2847 if value not in _openTypeOS2WidthClass_fromFL: 2848 print "The existing openTypeOS2WidthClass value \"%s\" cannot be found in the OpenType OS/2 usWidthClass specification." % value 2849 return 2850 else: 2851 return _openTypeOS2WidthClass_fromFL[value] 2852 # get the attribute data 2853 data = self._ufoToFLAttrMapping[attr] 2854 flAttr = data["nakedAttribute"] 2855 valueType = data["valueType"] 2856 masterSpecific = data["masterSpecific"] 2857 specialGetSet = data["specialGetSet"] 2858 # warn about setting attributes not supported by FL 2859 if flAttr is None: 2860 if not _IN_UFO_EXPORT: 2861 print "The attribute %s is not supported by FontLab." % attr 2862 return 2863 # handle special cases 2864 if specialGetSet: 2865 attr = "_get_%s" % attr 2866 method = getattr(self, attr) 2867 return method() 2868 # get the value 2869 if len(flAttr.split(".")) > 1: 2870 flAttrList = flAttr.split(".") 2871 obj = self._object 2872 for i in flAttrList: 2873 obj = getattr(obj, i) 2874 value = obj 2875 else: 2876 value = getattr(self._object, flAttr) 2877 # grab the first master value if necessary 2878 if masterSpecific: 2879 value = value[0] 2880 # convert if necessary 2881 if valueType == "intList": 2882 value = [int(i) for i in value] 2883 elif valueType == str: 2884 if value is None: 2885 pass 2886 else: 2887 value = unicode(value, LOCAL_ENCODING) 2888 elif not isinstance(value, valueType): 2889 value = valueType(value) 2890 return value 2891 2892 # ------------------------------ 2893 # individual attribute overrides 2894 # ------------------------------ 2895 2896 # styleMapStyleName 2897 2898 def _get_styleMapStyleName(self): 2899 return _styleMapStyleName_fromFL[self._object.font_style] 2900 2901 def _set_styleMapStyleName(self, value): 2902 value = _styleMapStyleName_toFL[value] 2903 self._object.font_style = value 2904 2905 # # openTypeHeadCreated 2906 # 2907 # # fontlab epoch: 1969-12-31 19:00:00 2908 # 2909 # def _get_openTypeHeadCreated(self): 2910 # value = self._object.ttinfo.head_creation 2911 # epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) 2912 # delta = datetime.timedelta(seconds=value[0]) 2913 # t = epoch - delta 2914 # string = "%s-%s-%s %s:%s:%s" % (str(t.year).zfill(4), str(t.month).zfill(2), str(t.day).zfill(2), str(t.hour).zfill(2), str(t.minute).zfill(2), str(t.second).zfill(2)) 2915 # return string 2916 # 2917 # def _set_openTypeHeadCreated(self, value): 2918 # date, time = value.split(" ") 2919 # year, month, day = [int(i) for i in date.split("-")] 2920 # hour, minute, second = [int(i) for i in time.split(":")] 2921 # value = datetime.datetime(year, month, day, hour, minute, second) 2922 # epoch = datetime.datetime(1969, 12, 31, 19, 0, 0) 2923 # delta = epoch - value 2924 # seconds = delta.seconds 2925 # self._object.ttinfo.head_creation[0] = seconds 2926 2927 # openTypeOS2WeightClass 2928 2929 def _get_openTypeOS2WeightClass(self): 2930 value = self._object.weight_code 2931 if value == -1: 2932 value = None 2933 return value 2934 2935 def _set_openTypeOS2WeightClass(self, value): 2699 2936 self._object.weight_code = value 2700 2701 weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") 2702 2703 def _get_weightName(self): 2704 return self._object.weight 2705 2706 def _set_weightName(self, value): 2707 self._object.weight = value 2708 2709 weightName = property(_get_weightName, _set_weightName, doc="weight name") 2710 2711 def _get_widthName(self): 2712 return self._object.width 2713 2714 def _set_widthName(self, value): 2715 self._object.width = value 2716 2717 widthName = property(_get_widthName, _set_widthName, doc="width name") 2718 2719 def _get_fontStyle(self): 2720 return self._object.font_style 2721 2722 def _set_fontStyle(self, value): 2723 self._object.font_style = value 2724 2725 fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") 2726 2727 def _get_msCharSet(self): 2728 return self._object.ms_charset 2729 2730 def _set_msCharSet(self, value): 2937 2938 # openTypeOS2WinDescent 2939 2940 def _get_openTypeOS2WinDescent(self): 2941 return -self._object.ttinfo.os2_us_win_descent 2942 2943 def _set_openTypeOS2WinDescent(self, value): 2944 if value > 0: 2945 raise ValueError("FontLab can only handle negative values for openTypeOS2WinDescent.") 2946 self._object.ttinfo.os2_us_win_descent = abs(value) 2947 2948 # openTypeOS2Type 2949 2950 def _get_openTypeOS2Type(self): 2951 value = self._object.ttinfo.os2_fs_type 2952 intList = [] 2953 for bit, bitNumber in _openTypeOS2Type_fromFL.items(): 2954 if value & bit: 2955 intList.append(bitNumber) 2956 return intList 2957 2958 def _set_openTypeOS2Type(self, values): 2959 value = 0 2960 for bitNumber in values: 2961 bit = _openTypeOS2Type_toFL[bitNumber] 2962 value = value | bit 2963 self._object.ttinfo.os2_fs_type = value 2964 2965 # openTypeOS2Panose 2966 2967 def _get_openTypeOS2Panose(self): 2968 return [i for i in self._object.panose] 2969 2970 def _set_openTypeOS2Panose(self, values): 2971 for index, value in enumerate(values): 2972 self._object.panose[index] = value 2973 2974 # openTypeOS2FamilyClass 2975 2976 def _get_openTypeOS2FamilyClass(self): 2977 value = self._object.ttinfo.os2_s_family_class 2978 for classID in range(15): 2979 classValue = classID * 256 2980 if classValue > value: 2981 classID -= 1 2982 classValue = classID * 256 2983 break 2984 subclassID = value - classValue 2985 return [classID, subclassID] 2986 2987 def _set_openTypeOS2FamilyClass(self, values): 2988 classID, subclassID = values 2989 classID = classID * 256 2990 value = classID + subclassID 2991 self._object.ttinfo.os2_s_family_class = value 2992 2993 # postscriptWindowsCharacterSet 2994 2995 def _get_postscriptWindowsCharacterSet(self): 2996 value = self._object.ms_charset 2997 value = _postscriptWindowsCharacterSet_fromFL[value] 2998 return value 2999 3000 def _set_postscriptWindowsCharacterSet(self, value): 3001 value = _postscriptWindowsCharacterSet_toFL[value] 2731 3002 self._object.ms_charset = value 2732 3003 2733 msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") 2734 2735 def _get_fondID(self): 2736 return self._object.fond_id 2737 2738 def _set_fondID(self, value): 2739 self._object.fond_id = value 2740 2741 fondID = property(_get_fondID, _set_fondID, doc="fond_id") 2742 2743 def _get_uniqueID(self): 2744 return self._object.unique_id 2745 2746 def _set_uniqueID(self, value): 2747 self._object.unique_id = value 2748 2749 uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") 2750 2751 def _get_versionMajor(self): 2752 return self._object.version_major 2753 2754 def _set_versionMajor(self, value): 2755 self._object.version_major = value 2756 2757 versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") 2758 2759 def _get_versionMinor(self): 2760 return self._object.version_minor 2761 2762 def _set_versionMinor(self, value): 2763 self._object.version_minor = value 2764 2765 versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") 2766 2767 def _get_year(self): 2768 return self._object.year 2769 2770 def _set_year(self, value): 2771 self._object.year = value 2772 2773 year = property(_get_year, _set_year, doc="year") 2774 2775 def _get_note(self): 2776 s = self._object.note 2777 if s is None: 2778 return s 2779 return unicode(s, LOCAL_ENCODING) 2780 2781 def _set_note(self, value): 2782 if value is not None: 2783 value = value.encode(LOCAL_ENCODING) 2784 self._object.note = value 2785 2786 note = property(_get_note, _set_note, doc="note") 2787 2788 def _get_copyright(self): 2789 s = self._object.copyright 2790 if s is None: 2791 return s 2792 return unicode(s, LOCAL_ENCODING) 2793 2794 def _set_copyright(self, value): 2795 if value is not None: 2796 value = value.encode(LOCAL_ENCODING) 2797 self._object.copyright = value 2798 2799 copyright = property(_get_copyright, _set_copyright, doc="copyright") 2800 2801 def _get_notice(self): 2802 s = self._object.notice 2803 if s is None: 2804 return s 2805 return unicode(s, LOCAL_ENCODING) 2806 2807 def _set_notice(self, value): 2808 if value is not None: 2809 value = value.encode(LOCAL_ENCODING) 2810 self._object.notice = value 2811 2812 notice = property(_get_notice, _set_notice, doc="notice") 2813 2814 def _get_trademark(self): 2815 s = self._object.trademark 2816 if s is None: 2817 return s 2818 return unicode(s, LOCAL_ENCODING) 2819 2820 def _set_trademark(self, value): 2821 if value is not None: 2822 value = value.encode(LOCAL_ENCODING) 2823 self._object.trademark = value 2824 2825 trademark = property(_get_trademark, _set_trademark, doc="trademark") 2826 2827 def _get_license(self): 2828 s = self._object.license 2829 if s is None: 2830 return s 2831 return unicode(s, LOCAL_ENCODING) 2832 2833 def _set_license(self, value): 2834 if value is not None: 2835 value = value.encode(LOCAL_ENCODING) 2836 self._object.license = value 2837 2838 license = property(_get_license, _set_license, doc="license") 2839 2840 def _get_licenseURL(self): 2841 return self._object.license_url 2842 2843 def _set_licenseURL(self, value): 2844 self._object.license_url = value 2845 2846 licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") 2847 2848 def _get_createdBy(self): 2849 s = self._object.source 2850 if s is None: 2851 return s 2852 return unicode(s, LOCAL_ENCODING) 2853 2854 def _set_createdBy(self, value): 2855 if value is not None: 2856 value = value.encode(LOCAL_ENCODING) 2857 self._object.source = value 2858 2859 createdBy = property(_get_createdBy, _set_createdBy, doc="source") 2860 2861 def _get_designer(self): 2862 s = self._object.designer 2863 if s is None: 2864 return s 2865 return unicode(s, LOCAL_ENCODING) 2866 2867 def _set_designer(self, value): 2868 if value is not None: 2869 value = value.encode(LOCAL_ENCODING) 2870 self._object.designer = value 2871 2872 designer = property(_get_designer, _set_designer, doc="designer") 2873 2874 def _get_designerURL(self): 2875 return self._object.designer_url 2876 2877 def _set_designerURL(self, value): 2878 self._object.designer_url = value 2879 2880 designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") 2881 2882 def _get_vendorURL(self): 2883 return self._object.vendor_url 2884 2885 def _set_vendorURL(self, value): 2886 self._object.vendor_url = value 2887 2888 vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") 2889 2890 def _get_ttVendor(self): 2891 return self._object.vendor 2892 2893 def _set_ttVendor(self, value): 2894 self._object.vendor = value 2895 2896 ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") 2897 2898 def _get_ttUniqueID(self): 2899 return self._object.tt_u_id 2900 2901 def _set_ttUniqueID(self, value): 2902 self._object.tt_u_id = value 2903 2904 ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") 2905 2906 def _get_ttVersion(self): 2907 return self._object.tt_version 2908 2909 def _set_ttVersion(self, value): 2910 self._object.tt_version = value 2911 2912 ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") 2913 2914 def _get_unitsPerEm(self): 2915 return self._object.upm 2916 2917 def _set_unitsPerEm(self, value): 2918 self._object.upm = int(round(value)) 2919 2920 unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") 2921 2922 def _get_ascender(self): 2923 return self._object.ascender[0] 2924 2925 def _set_ascender(self, value): 2926 value = int(round(value)) 2927 self._object.ascender[0] = value 2928 2929 ascender = property(_get_ascender, _set_ascender, doc="ascender value") 2930 2931 def _get_descender(self): 2932 return self._object.descender[0] 2933 2934 def _set_descender(self, value): 2935 value = int(round(value)) 2936 self._object.descender[0] = value 2937 2938 descender = property(_get_descender, _set_descender, doc="descender value") 2939 2940 def _get_capHeight(self): 2941 return self._object.cap_height[0] 2942 2943 def _set_capHeight(self, value): 2944 value = int(round(value)) 2945 self._object.cap_height[0] = value 2946 2947 capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") 2948 2949 def _get_xHeight(self): 2950 return self._object.x_height[0] 2951 2952 def _set_xHeight(self, value): 2953 value = int(round(value)) 2954 self._object.x_height[0] = value 2955 2956 xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") 2957 2958 def _get_defaultWidth(self): 2959 return self._object.default_width[0] 2960 2961 def _set_defaultWidth(self, value): 2962 value = int(round(value)) 2963 self._object.default_width[0] = value 2964 2965 defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") 2966 2967 def _get_italicAngle(self): 2968 return self._object.italic_angle 2969 2970 def _set_italicAngle(self, value): 2971 try: 2972 self._object.italic_angle = float(value) 2973 except TypeError: 2974 print "robofab.objects.objectsFL: can't set italic angle, possibly a FontLab API limitation" 2975 2976 italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") 2977 2978 def _get_slantAngle(self): 2979 return self._object.slant_angle 2980 2981 def _set_slantAngle(self, value): 2982 try: 2983 self._object.slant_angle = float(value) 2984 except TypeError: 2985 print "robofab.objects.objectsFL: can't set slant angle, possibly a FontLab API limitation" 2986 2987 slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") 2988 2989 #is this still needed? 2990 def _get_full_name(self): 2991 return self._object.full_name 2992 2993 def _set_full_name(self, value): 2994 self._object.full_name = value 2995 2996 full_name = property(_get_full_name, _set_full_name, doc="FL: full_name") 2997 2998 #is this still needed? 2999 def _get_ms_charset(self): 3000 return self._object.ms_charset 3001 3002 def _set_ms_charset(self, value): 3003 self._object.ms_charset = value 3004 3005 ms_charset = property(_get_ms_charset, _set_ms_charset, doc="FL: ms_charset") 3004 # ----------------- 3005 # FL bug workaround 3006 # ----------------- 3007 3008 def _handlePSHintBug(self, attribute, values): 3009 """Function to handle problems with FontLab not allowing the max number of 3010 alignment zones to be set to the max number. 3011 Input: the name of the zones and the values to be set 3012 Output: a warning when there are too many values to be set 3013 and the max values which FontLab will allow. 3014 """ 3015 originalValues = values 3016 truncatedLength = None 3017 if attribute in ("postscriptStemSnapH", "postscriptStemSnapV"): 3018 if len(values) > 10: 3019 values = values[:10] 3020 truncatedLength = 10 3021 elif attribute in ("postscriptBlueValues", "postscriptFamilyBlues"): 3022 if len(values) > 12: 3023 values = values[:12] 3024 truncatedLength = 12 3025 elif attribute in ("postscriptOtherBlues", "postscriptFamilyOtherBlues"): 3026 if len(values) > 8: 3027 values = values[:8] 3028 truncatedLength = 8 3029 if truncatedLength is not None: 3030 print "* * * WARNING: FontLab will only accept %d %s items maximum from Python. Dropping values: %s." % (truncatedLength, attribute, str(originalValues[truncatedLength:])) 3031 return values 3032 3033 3034 class RFeatures(BaseFeatures): 3035 3036 _title = "FLFeatures" 3037 3038 def __init__(self, font): 3039 super(RFeatures, self).__init__() 3040 self._object = font 3041 3042 def _get_text(self): 3043 naked = self._object 3044 features = [] 3045 if naked.ot_classes: 3046 features.append(_normalizeLineEndings(naked.ot_classes)) 3047 for feature in naked.features: 3048 features.append(_normalizeLineEndings(feature.value)) 3049 return "".join(features) 3050 3051 def _set_text(self, value): 3052 classes, features = splitFeaturesForFontLab(value) 3053 naked = self._object 3054 naked.ot_classes = classes 3055 naked.features.clean() 3056 for featureName, featureText in features: 3057 f = Feature(featureName, featureText) 3058 naked.features.append(f) 3059 3060 text = property(_get_text, _set_text, doc="raw feature text.") 3061 trunk/Lib/robofab/objects/objectsRF.py
r60 r171 2 2 3 3 from robofab import RoboFabError, RoboFabWarning 4 from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, Base Lib,\4 from robofab.objects.objectsBase import BaseFont, BaseKerning, BaseGroups, BaseInfo, BaseFeatures, BaseLib,\ 5 5 BaseGlyph, BaseContour, BaseSegment, BasePoint, BaseBPoint, BaseAnchor, BaseGuide, BaseComponent, \ 6 6 relativeBCPIn, relativeBCPOut, absoluteBCPIn, absoluteBCPOut, _box,\ … … 46 46 """Make a new font""" 47 47 new = RFont() 48 new.info.familyName = familyName 49 new.info.styleName = styleName 48 if familyName is not None: 49 new.info.familyName = familyName 50 if styleName is not None: 51 new.info.styleName = styleName 50 52 return new 51 53 … … 68 70 69 71 def __init__(self, aFont=None, data=None): 70 # read the data from the font.lib, it won't be anywhere else72 self.setParent(aFont) 71 73 BasePostScriptFontHintValues.__init__(self) 72 74 if aFont is not None: 73 self.setParent(aFont) 75 # in version 1, this data was stored in the lib 76 # if it is still there, guess that it is correct 77 # move it to font info and remove it from the lib. 74 78 libData = aFont.lib.get(postScriptHintDataLibKey) 75 79 if libData is not None: 76 80 self.fromDict(libData) 81 del libData[postScriptHintDataLibKey] 77 82 if data is not None: 78 83 self.fromDict(data) … … 124 129 self.info = RInfo() 125 130 self.info.setParent(self) 131 self.features = RFeatures() 132 self.features.setParent(self) 126 133 self.groups = RGroups() 127 134 self.groups.setParent(self) 128 135 self.lib = RLib() 129 136 self.lib.setParent(self) 130 self.psHints = PostScriptFontHintValues(self)131 self.psHints.setParent(self)132 133 137 if path: 134 138 self._loadData(path) 139 else: 140 self.psHints = PostScriptFontHintValues(self) 141 self.psHints.setParent(self) 135 142 136 143 def __setitem__(self, glyphName, glyph): … … 153 160 154 161 def _loadData(self, path): 155 #Load the data into the font156 162 from robofab.ufoLib import UFOReader 157 u = UFOReader(path) 158 u.readInfo(self.info) 159 self.kerning.update(u.readKerning()) 163 reader = UFOReader(path) 164 fontLib = reader.readLib() 165 # info 166 reader.readInfo(self.info) 167 # kerning 168 self.kerning.update(reader.readKerning()) 160 169 self.kerning.setChanged(False) 161 self.groups.update(u.readGroups()) 162 self.lib.update(u.readLib()) 163 # after reading the lib, read hinting data from the lib 170 # groups 171 self.groups.update(reader.readGroups()) 172 # features 173 if reader.formatVersion == 1: 174 # migrate features from the lib 175 features = [] 176 classes = fontLib.get("org.robofab.opentype.classes") 177 if classes is not None: 178 del fontLib["org.robofab.opentype.classes"] 179 features.append(classes) 180 splitFeatures = fontLib.get("org.robofab.opentype.features") 181 if splitFeatures is not None: 182 order = fontLib.get("org.robofab.opentype.featureorder") 183 if order is None: 184 order = splitFeatures.keys() 185 order.sort() 186 else: 187 del fontLib["org.robofab.opentype.featureorder"] 188 del fontLib["org.robofab.opentype.features"] 189 for tag in order: 190 oneFeature = splitFeatures.get(tag) 191 if oneFeature is not None: 192 features.append(oneFeature) 193 features = "\n".join(features) 194 else: 195 features = reader.readFeatures() 196 self.features.text = features 197 # hint data 164 198 self.psHints = PostScriptFontHintValues(self) 165 self._glyphSet = u.getGlyphSet() 199 if postScriptHintDataLibKey in fontLib: 200 del fontLib[postScriptHintDataLibKey] 201 # lib 202 self.lib.update(fontLib) 203 # glyphs 204 self._glyphSet = reader.getGlyphSet() 166 205 self._hasNotChanged(doGlyphs=False) 167 206 168 207 def _loadGlyph(self, glyphName): 169 208 """Load a single glyph from the glyphSet, on request.""" … … 330 369 331 370 332 def save(self, destDir=None, doProgress=False, saveNow=False):371 def save(self, destDir=None, doProgress=False, formatVersion=2): 333 372 """Save the Font in UFO format.""" 334 373 # XXX note that when doing "save as" by specifying the destDir argument … … 338 377 # save. 339 378 from robofab.ufoLib import UFOWriter 379 from robofab.tools.fontlabFeatureSplitter import splitFeaturesForFontLab 340 380 # if no destination is given, or if 341 381 # the given destination is the current … … 346 386 else: 347 387 saveAs = True 348 u = UFOWriter(destDir)388 # start a progress bar 349 389 nonGlyphCount = 5 350 390 bar = None 351 391 if doProgress: 352 392 from robofab.interface.all.dialogs import ProgressBar 353 bar = ProgressBar('Exporting UFO', nonGlyphCount+len(self._object.keys())) 393 bar = ProgressBar("Exporting UFO", nonGlyphCount + len(self._object.keys())) 394 # write 395 writer = UFOWriter(destDir, formatVersion=formatVersion) 354 396 try: 355 #if self.info.changed: 397 # make a shallow copy of the lib. stuff may be added to it. 398 fontLib = dict(self.lib) 399 # info 356 400 if bar: 357 bar.label( 'Saving info...')358 u.writeInfo(self.info)401 bar.label("Saving info...") 402 writer.writeInfo(self.info) 359 403 if bar: 360 404 bar.tick() 405 # kerning 361 406 if self.kerning.changed or saveAs: 362 407 if bar: 363 bar.label('Saving kerning...') 364 u.writeKerning(self.kerning.asDict()) 365 self.kerning.setChanged(False) 408 bar.label("Saving kerning...") 409 writer.writeKerning(self.kerning.asDict()) 410 if bar: 411 bar.tick() 412 # groups 413 if bar: 414 bar.label("Saving groups...") 415 writer.writeGroups(self.groups) 366 416 if bar: 367 417 bar.tick() 368 # if self.groups.changed:418 # features 369 419 if bar: 370 bar.label('Saving groups...') 371 u.writeGroups(self.groups) 420 bar.label("Saving features...") 421 features = self.features.text 422 if features is None: 423 features = "" 424 if formatVersion == 2: 425 writer.writeFeatures(features) 426 elif formatVersion == 1: 427 classes, features = splitFeaturesForFontLab(features) 428 if classes: 429 fontLib["org.robofab.opentype.classes"] = classes.strip() + "\n" 430 if features: 431 featureDict = {} 432 for featureName, featureText in features: 433 featureDict[featureName] = featureText.strip() + "\n" 434 fontLib["org.robofab.opentype.features"] = featureDict 435 fontLib["org.robofab.opentype.featureorder"] = [featureName for featureName, featureText in features] 372 436 if bar: 373 437 bar.tick() 374 375 # save postscript hint data 376 self.lib[postScriptHintDataLibKey] = self.psHints.asDict() 377 378 #if self.lib.changed: 438 # lib 439 if formatVersion == 1: 440 fontLib[postScriptHintDataLibKey] = self.psHints.asDict() 379 441 if bar: 380 bar.label( 'Saving lib...')381 u.writeLib(self.lib)442 bar.label("Saving lib...") 443 writer.writeLib(fontLib) 382 444 if bar: 383 445 bar.tick() 446 # glyphs 384 447 glyphNameToFileNameFunc = self.getGlyphNameToFileNameFunc() 385 glyphSet = u.getGlyphSet(glyphNameToFileNameFunc) 448 449 glyphSet = writer.getGlyphSet(glyphNameToFileNameFunc) 386 450 if len(self._scheduledForDeletion) != 0: 387 451 if bar: 388 bar.label( 'Removing deleted glyphs......')452 bar.label("Removing deleted glyphs...") 389 453 for glyphName in self._scheduledForDeletion: 390 454 if glyphSet.has_key(glyphName): … … 393 457 bar.tick() 394 458 if bar: 395 bar.label( 'Saving glyphs...')459 bar.label("Saving glyphs...") 396 460 count = nonGlyphCount 397 461 if saveAs: … … 408 472 glyphSet.writeContents() 409 473 self._glyphSet = glyphSet 474 # only blindly stop if the user says to 410 475 except KeyboardInterrupt: 411 476 bar.close() 412 477 bar = None 478 # kill the progress bar 413 479 if bar: 414 480 bar.close() 481 # reset internal stuff 415 482 self._path = destDir 416 483 self._scheduledForDeletion = [] 417 484 self.setChanged(False) 418 485 419 486 def newGlyph(self, glyphName, clear=True): 420 487 """Make a new glyph with glyphName … … 1127 1194 class RInfo(BaseInfo): 1128 1195 1129 _title = "RoboFabFonInfo" 1130 1131 def __init__(self): 1132 BaseInfo.__init__(self) 1133 self.selected = False 1134 1135 self._familyName = None 1136 self._styleName = None 1137 self._fullName = None 1138 self._fontName = None 1139 self._menuName = None 1140 self._fondName = None 1141 self._otFamilyName = None 1142 self._otStyleName = None 1143 self._otMacName = None 1144 self._weightValue = None 1145 self._weightName = None 1146 self._widthName = None 1147 self._fontStyle = None 1148 self._msCharSet = None 1149 self._note = None 1150 self._fondID = None 1151 self._uniqueID = None 1152 self._versionMajor = None 1153 self._versionMinor = None 1154 self._year = None 1155 self._copyright = None 1156 self._notice = None 1157 self._trademark = None 1158 self._license = None 1159 self._licenseURL = None 1160 self._createdBy = None 1161 self._designer = None 1162 self._designerURL = None 1163 self._vendorURL = None 1164 self._ttVendor = None 1165 self._ttUniqueID = None 1166 self._ttVersion = None 1167 self._unitsPerEm = None 1168 self._ascender = None 1169 self._descender = None 1170 self._capHeight = None 1171 self._xHeight = None 1172 self._defaultWidth = None 1173 self._italicAngle = None 1174 self._slantAngle = None 1175 1176 def _get_familyName(self): 1177 return self._familyName 1178 1179 def _set_familyName(self, value): 1180 self._familyName = value 1181 1182 familyName = property(_get_familyName, _set_familyName, doc="family_name") 1183 1184 def _get_styleName(self): 1185 return self._styleName 1186 1187 def _set_styleName(self, value): 1188 self._styleName = value 1189 1190 styleName = property(_get_styleName, _set_styleName, doc="style_name") 1191 1192 def _get_fullName(self): 1193 return self._fullName 1194 1195 def _set_fullName(self, value): 1196 self._fullName = value 1197 1198 fullName = property(_get_fullName, _set_fullName, doc="full_name") 1199 1200 def _get_fontName(self): 1201 return self._fontName 1202 1203 def _set_fontName(self, value): 1204 self._fontName = value 1205 1206 fontName = property(_get_fontName, _set_fontName, doc="font_name") 1207 1208 def _get_menuName(self): 1209 return self._menuName 1210 1211 def _set_menuName(self, value): 1212 self._menuName = value 1213 1214 menuName = property(_get_menuName, _set_menuName, doc="menu_name") 1215 1216 def _get_fondName(self): 1217 return self._fondName 1218 1219 def _set_fondName(self, value): 1220 self._fondName = value 1221 1222 fondName = property(_get_fondName, _set_fondName, doc="apple_name") 1223 1224 def _get_otFamilyName(self): 1225 return self._otFamilyName 1226 1227 def _set_otFamilyName(self, value): 1228 self._otFamilyName = value 1229 1230 otFamilyName = property(_get_otFamilyName, _set_otFamilyName, doc="pref_family_name") 1231 1232 def _get_otStyleName(self): 1233 return self._otStyleName 1234 1235 def _set_otStyleName(self, value): 1236 self._otStyleName = value 1237 1238 otStyleName = property(_get_otStyleName, _set_otStyleName, doc="pref_style_name") 1239 1240 def _get_otMacName(self): 1241 return self._otMacName 1242 1243 def _set_otMacName(self, value): 1244 self._otMacName = value 1245 1246 otMacName = property(_get_otMacName, _set_otMacName, doc="mac_compatible") 1247 1248 def _get_weightValue(self): 1249 return self._weightValue 1250 1251 def _set_weightValue(self, value): 1252 self._weightValue = value 1253 1254 weightValue = property(_get_weightValue, _set_weightValue, doc="weight value") 1255 1256 def _get_weightName(self): 1257 return self._weightName 1258 1259 def _set_weightName(self, value): 1260 self._weightName = value 1261 1262 weightName = property(_get_weightName, _set_weightName, doc="weight name") 1263 1264 def _get_widthName(self): 1265 return self._widthName 1266 1267 def _set_widthName(self, value): 1268 self._widthName = value 1269 1270 widthName = property(_get_widthName, _set_widthName, doc="width name") 1271 1272 def _get_fontStyle(self): 1273 return self._fontStyle 1274 1275 def _set_fontStyle(self, value): 1276 self._fontStyle = value 1277 1278 fontStyle = property(_get_fontStyle, _set_fontStyle, doc="font_style") 1279 1280 def _get_msCharSet(self): 1281 return self._msCharSet 1282 1283 def _set_msCharSet(self, value): 1284 self._msCharSet = value 1285 1286 msCharSet = property(_get_msCharSet, _set_msCharSet, doc="ms_charset") 1287 1288 def _get_note(self): 1289 return self._note 1290 1291 def _set_note(self, value): 1292 self._note = value 1293 1294 note = property(_get_note, _set_note, doc="note") 1295 1296 def _get_fondID(self): 1297 return self._fondID 1298 1299 def _set_fondID(self, value): 1300 self._fondID = value 1301 1302 fondID = property(_get_fondID, _set_fondID, doc="fond_id") 1303 1304 def _get_uniqueID(self): 1305 return self._uniqueID 1306 1307 def _set_uniqueID(self, value): 1308 self._uniqueID = value 1309 1310 uniqueID = property(_get_uniqueID, _set_uniqueID, doc="unique_id") 1311 1312 def _get_versionMajor(self): 1313 return self._versionMajor 1314 1315 def _set_versionMajor(self, value): 1316 self._versionMajor = value 1317 1318 versionMajor = property(_get_versionMajor, _set_versionMajor, doc="version_major") 1319 1320 def _get_versionMinor(self): 1321 return self._versionMinor 1322 1323 def _set_versionMinor(self, value): 1324 self._versionMinor = value 1325 1326 versionMinor = property(_get_versionMinor, _set_versionMinor, doc="version_minor") 1327 1328 def _get_year(self): 1329 return self._year 1330 1331 def _set_year(self, value): 1332 self._year = value 1333 1334 year = property(_get_year, _set_year, doc="year") 1335 1336 def _get_copyright(self): 1337 return self._copyright 1338 1339 def _set_copyright(self, value): 1340 self._copyright = value 1341 1342 copyright = property(_get_copyright, _set_copyright, doc="copyright") 1343 1344 def _get_notice(self): 1345 return self._notice 1346 1347 def _set_notice(self, value): 1348 self._notice = value 1349 1350 notice = property(_get_notice, _set_notice, doc="notice") 1351 1352 def _get_trademark(self): 1353 return self._trademark 1354 1355 def _set_trademark(self, value): 1356 self._trademark = value 1357 1358 trademark = property(_get_trademark, _set_trademark, doc="trademark") 1359 1360 def _get_license(self): 1361 return self._license 1362 1363 def _set_license(self, value): 1364 self._license = value 1365 1366 license = property(_get_license, _set_license, doc="license") 1367 1368 def _get_licenseURL(self): 1369 return self._licenseURL 1370 1371 def _set_licenseURL(self, value): 1372 self._licenseURL = value 1373 1374 licenseURL = property(_get_licenseURL, _set_licenseURL, doc="license_url") 1375 1376 def _get_designer(self): 1377 return self._designer 1378 1379 def _set_designer(self, value): 1380 self._designer = value 1381 1382 designer = property(_get_designer, _set_designer, doc="designer") 1383 1384 def _get_createdBy(self): 1385 return self._createdBy 1386 1387 def _set_createdBy(self, value): 1388 self._createdBy = value 1389 1390 createdBy = property(_get_createdBy, _set_createdBy, doc="source") 1391 1392 def _get_designerURL(self): 1393 return self._designerURL 1394 1395 def _set_designerURL(self, value): 1396 self._designerURL = value 1397 1398 designerURL = property(_get_designerURL, _set_designerURL, doc="designer_url") 1399 1400 def _get_vendorURL(self): 1401 return self._vendorURL 1402 1403 def _set_vendorURL(self, value): 1404 self._vendorURL = value 1405 1406 vendorURL = property(_get_vendorURL, _set_vendorURL, doc="vendor_url") 1407 1408 def _get_ttVendor(self): 1409 return self._ttVendor 1410 1411 def _set_ttVendor(self, value): 1412 self._ttVendor = value 1413 1414 ttVendor = property(_get_ttVendor, _set_ttVendor, doc="vendor") 1415 1416 def _get_ttUniqueID(self): 1417 return self._ttUniqueID 1418 1419 def _set_ttUniqueID(self, value): 1420 self._ttUniqueID = value 1421 1422 ttUniqueID = property(_get_ttUniqueID, _set_ttUniqueID, doc="tt_u_id") 1423 1424 def _get_ttVersion(self): 1425 return self._ttVersion 1426 1427 def _set_ttVersion(self, value): 1428 self._ttVersion = value 1429 1430 ttVersion = property(_get_ttVersion, _set_ttVersion, doc="tt_version") 1431 1432 def _get_unitsPerEm(self): 1433 return self._unitsPerEm 1434 1435 def _set_unitsPerEm(self, value): 1436 self._unitsPerEm = value 1437 1438 unitsPerEm = property(_get_unitsPerEm, _set_unitsPerEm, doc="") 1439 1440 def _get_ascender(self): 1441 return self._ascender 1442 1443 def _set_ascender(self, value): 1444 self._ascender = value 1445 1446 ascender = property(_get_ascender, _set_ascender, doc="ascender value") 1447 1448 def _get_descender(self): 1449 return self._descender 1450 1451 def _set_descender(self, value): 1452 self._descender = value 1453 1454 descender = property(_get_descender, _set_descender, doc="descender value") 1455 1456 def _get_capHeight(self): 1457 return self._capHeight 1458 1459 def _set_capHeight(self, value): 1460 self._capHeight = value 1461 1462 capHeight = property(_get_capHeight, _set_capHeight, doc="cap height value") 1463 1464 def _get_xHeight(self): 1465 return self._xHeight 1466 1467 def _set_xHeight(self, value): 1468 self._xHeight = value 1469 1470 xHeight = property(_get_xHeight, _set_xHeight, doc="x height value") 1471 1472 def _get_defaultWidth(self): 1473 return self._defaultWidth 1474 1475 def _set_defaultWidth(self, value): 1476 self._defaultWidth = value 1477 1478 defaultWidth = property(_get_defaultWidth, _set_defaultWidth, doc="default width value") 1479 1480 def _get_italicAngle(self): 1481 return self._italicAngle 1482 1483 def _set_italicAngle(self, value): 1484 self._italicAngle = value 1485 1486 italicAngle = property(_get_italicAngle, _set_italicAngle, doc="italic_angle") 1487 1488 def _get_slantAngle(self): 1489 return self._slantAngle 1490 1491 def _set_slantAngle(self, value): 1492 self._slantAngle = value 1493 1494 slantAngle = property(_get_slantAngle, _set_slantAngle, doc="slant_angle") 1495 1196 _title = "RoboFabFontInfo" 1197 1198 class RFeatures(BaseFeatures): 1199 1200 _title = "RoboFabFeatures" 1201 trunk/Lib/robofab/test/runAll.py
r1 r171 18 18 except ImportError: 19 19 print "*** skipped", fileName 20 continue 20 21 21 22 suites.append(loader.loadTestsFromModule(mod)) trunk/Lib/robofab/test/testSupport.py
r1 r171 48 48 testRunner.run(testSuite) 49 49 50 # font info values used by several tests 51 52 fontInfoVersion1 = { 53 "familyName" : "Some Font (Family Name)", 54 "styleName" : "Regular (Style Name)", 55 "fullName" : "Some Font-Regular (Postscript Full Name)", 56 "fontName" : "SomeFont-Regular (Postscript Font Name)", 57 "menuName" : "Some Font Regular (Style Map Family Name)", 58 "fontStyle" : 64, 59 "note" : "A note.", 60 "versionMajor" : 1, 61 "versionMinor" : 0, 62 "year" : 2008, 63 "copyright" : "Copyright Some Foundry.", 64 "notice" : "Some Font by Some Designer for Some Foundry.", 65 "trademark" : "Trademark Some Foundry", 66 "license" : "License info for Some Foundry.", 67 "licenseURL" : "http://somefoundry.com/license", 68 "createdBy" : "Some Foundry", 69 "designer" : "Some Designer", 70 "designerURL" : "http://somedesigner.com", 71 "vendorURL" : "http://somefoundry.com", 72 "unitsPerEm" : 1000, 73 "ascender" : 750, 74 "descender" : -250, 75 "capHeight" : 750, 76 "xHeight" : 500, 77 "defaultWidth" : 400, 78 "slantAngle" : -12.5, 79 "italicAngle" : -12.5, 80 "widthName" : "Medium (normal)", 81 "weightName" : "Medium", 82 "weightValue" : 500, 83 "fondName" : "SomeFont Regular (FOND Name)", 84 "otFamilyName" : "Some Font (Preferred Family Name)", 85 "otStyleName" : "Regular (Preferred Subfamily Name)", 86 "otMacName" : "Some Font Regular (Compatible Full Name)", 87 "msCharSet" : 0, 88 "fondID" : 15000, 89 "uniqueID" : 4000000, 90 "ttVendor" : "SOME", 91 "ttUniqueID" : "OpenType name Table Unique ID", 92 "ttVersion" : "OpenType name Table Version", 93 } 94 95 fontInfoVersion2 = { 96 "familyName" : "Some Font (Family Name)", 97 "styleName" : "Regular (Style Name)", 98 "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)", 99 "styleMapStyleName" : "regular", 100 "versionMajor" : 1, 101 "versionMinor" : 0, 102 "year" : 2008, 103 "copyright" : "Copyright Some Foundry.", 104 "trademark" : "Trademark Some Foundry", 105 "unitsPerEm" : 1000, 106 "descender" : -250, 107 "xHeight" : 500, 108 "capHeight" : 750, 109 "ascender" : 750, 110 "italicAngle" : -12.5, 111 "note" : "A note.", 112 "openTypeHeadCreated" : "2000/01/01 00:00:00", 113 "openTypeHeadLowestRecPPEM" : 10, 114 "openTypeHeadFlags" : [0, 1], 115 "openTypeHheaAscender" : 750, 116 "openTypeHheaDescender" : -250, 117 "openTypeHheaLineGap" : 200, 118 "openTypeHheaCaretSlopeRise" : 1, 119 "openTypeHheaCaretSlopeRun" : 0, 120 "openTypeHheaCaretOffset" : 0, 121 "openTypeNameDesigner" : "Some Designer", 122 "openTypeNameDesignerURL" : "http://somedesigner.com", 123 "openTypeNameManufacturer" : "Some Foundry", 124 "openTypeNameManufacturerURL" : "http://somefoundry.com", 125 "openTypeNameLicense" : "License info for Some Foundry.", 126 "openTypeNameLicenseURL" : "http://somefoundry.com/license", 127 "openTypeNameVersion" : "OpenType name Table Version", 128 "openTypeNameUniqueID" : "OpenType name Table Unique ID", 129 "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.", 130 "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)", 131 "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)", 132 "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", 133 "openTypeNameSampleText" : "Sample Text for Some Font.", 134 "openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)", 135 "openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)", 136 "openTypeOS2WidthClass" : 5, 137 "openTypeOS2WeightClass" : 500, 138 "openTypeOS2Selection" : [3], 139 "openTypeOS2VendorID" : "SOME", 140 "openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 141 "openTypeOS2FamilyClass" : [1, 1], 142 "openTypeOS2UnicodeRanges" : [0, 1], 143 "openTypeOS2CodePageRanges" : [0, 1], 144 "openTypeOS2TypoAscender" : 750, 145 "openTypeOS2TypoDescender" : -250, 146 "openTypeOS2TypoLineGap" : 200, 147 "openTypeOS2WinAscent" : 750, 148 "openTypeOS2WinDescent" : -250, 149 "openTypeOS2Type" : [], 150 "openTypeOS2SubscriptXSize" : 200, 151 "openTypeOS2SubscriptYSize" : 400, 152 "openTypeOS2SubscriptXOffset" : 0, 153 "openTypeOS2SubscriptYOffset" : -100, 154 "openTypeOS2SuperscriptXSize" : 200, 155 "openTypeOS2SuperscriptYSize" : 400, 156 "openTypeOS2SuperscriptXOffset" : 0, 157 "openTypeOS2SuperscriptYOffset" : 200, 158 "openTypeOS2StrikeoutSize" : 20, 159 "openTypeOS2StrikeoutPosition" : 300, 160 "openTypeVheaVertTypoAscender" : 750, 161 "openTypeVheaVertTypoDescender" : -250, 162 "openTypeVheaVertTypoLineGap" : 200, 163 "openTypeVheaCaretSlopeRise" : 0, 164 "openTypeVheaCaretSlopeRun" : 1, 165 "openTypeVheaCaretOffset" : 0, 166 "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)", 167 "postscriptFullName" : "Some Font-Regular (Postscript Full Name)", 168 "postscriptSlantAngle" : -12.5, 169 "postscriptUniqueID" : 4000000, 170 "postscriptUnderlineThickness" : 20, 171 "postscriptUnderlinePosition" : -200, 172 "postscriptIsFixedPitch" : False, 173 "postscriptBlueValues" : [500, 510], 174 "postscriptOtherBlues" : [-250, -260], 175 "postscriptFamilyBlues" : [500, 510], 176 "postscriptFamilyOtherBlues" : [-250, -260], 177 "postscriptStemSnapH" : [100, 120], 178 "postscriptStemSnapV" : [80, 90], 179 "postscriptBlueFuzz" : 1, 180 "postscriptBlueShift" : 7, 181 "postscriptBlueScale" : 0.039625, 182 "postscriptForceBold" : True, 183 "postscriptDefaultWidthX" : 400, 184 "postscriptNominalWidthX" : 400, 185 "postscriptWeightName" : "Medium", 186 "postscriptDefaultCharacter" : ".notdef", 187 "postscriptWindowsCharacterSet" : 1, 188 "macintoshFONDFamilyID" : 15000, 189 "macintoshFONDName" : "SomeFont Regular (FOND Name)", 190 } 191 192 expectedFontInfo1To2Conversion = { 193 "familyName" : "Some Font (Family Name)", 194 "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)", 195 "styleMapStyleName" : "regular", 196 "styleName" : "Regular (Style Name)", 197 "unitsPerEm" : 1000, 198 "ascender" : 750, 199 "capHeight" : 750, 200 "xHeight" : 500, 201 "descender" : -250, 202 "italicAngle" : -12.5, 203 "versionMajor" : 1, 204 "versionMinor" : 0, 205 "year" : 2008, 206 "copyright" : "Copyright Some Foundry.", 207 "trademark" : "Trademark Some Foundry", 208 "note" : "A note.", 209 "macintoshFONDFamilyID" : 15000, 210 "macintoshFONDName" : "SomeFont Regular (FOND Name)", 211 "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", 212 "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.", 213 "openTypeNameDesigner" : "Some Designer", 214 "openTypeNameDesignerURL" : "http://somedesigner.com", 215 "openTypeNameLicense" : "License info for Some Foundry.", 216 "openTypeNameLicenseURL" : "http://somefoundry.com/license", 217 "openTypeNameManufacturer" : "Some Foundry", 218 "openTypeNameManufacturerURL" : "http://somefoundry.com", 219 "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)", 220 "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)", 221 "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)", 222 "openTypeNameUniqueID" : "OpenType name Table Unique ID", 223 "openTypeNameVersion" : "OpenType name Table Version", 224 "openTypeOS2VendorID" : "SOME", 225 "openTypeOS2WeightClass" : 500, 226 "openTypeOS2WidthClass" : 5, 227 "postscriptDefaultWidthX" : 400, 228 "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)", 229 "postscriptFullName" : "Some Font-Regular (Postscript Full Name)", 230 "postscriptSlantAngle" : -12.5, 231 "postscriptUniqueID" : 4000000, 232 "postscriptWeightName" : "Medium", 233 "postscriptWindowsCharacterSet" : 1 234 } 235 236 expectedFontInfo2To1Conversion = { 237 "familyName" : "Some Font (Family Name)", 238 "menuName" : "Some Font Regular (Style Map Family Name)", 239 "fontStyle" : 64, 240 "styleName" : "Regular (Style Name)", 241 "unitsPerEm" : 1000, 242 "ascender" : 750, 243 "capHeight" : 750, 244 "xHeight" : 500, 245 "descender" : -250, 246 "italicAngle" : -12.5, 247 "versionMajor" : 1, 248 "versionMinor" : 0, 249 "copyright" : "Copyright Some Foundry.", 250 "trademark" : "Trademark Some Foundry", 251 "note" : "A note.", 252 "fondID" : 15000, 253 "fondName" : "SomeFont Regular (FOND Name)", 254 "fullName" : "Some Font Regular (Compatible Full Name)", 255 "notice" : "Some Font by Some Designer for Some Foundry.", 256 "designer" : "Some Designer", 257 "designerURL" : "http://somedesigner.com", 258 "license" : "License info for Some Foundry.", 259 "licenseURL" : "http://somefoundry.com/license", 260 "createdBy" : "Some Foundry", 261 "vendorURL" : "http://somefoundry.com", 262 "otFamilyName" : "Some Font (Preferred Family Name)", 263 "otStyleName" : "Regular (Preferred Subfamily Name)", 264 "otMacName" : "Some Font Regular (Compatible Full Name)", 265 "ttUniqueID" : "OpenType name Table Unique ID", 266 "ttVersion" : "OpenType name Table Version", 267 "ttVendor" : "SOME", 268 "weightValue" : 500, 269 "widthName" : "Medium (normal)", 270 "defaultWidth" : 400, 271 "fontName" : "SomeFont-Regular (Postscript Font Name)", 272 "fullName" : "Some Font-Regular (Postscript Full Name)", 273 "slantAngle" : -12.5, 274 "uniqueID" : 4000000, 275 "weightName" : "Medium", 276 "msCharSet" : 0, 277 "year" : 2008 278 } trunk/Lib/robofab/test/test_objectsFL.py
r1 r171 13 13 from robofab.tools.glifImport import importAllGlifFiles 14 14 from robofab.pens.digestPen import DigestPointPen 15 from robofab.pens.adapterPens import SegmentToPointPen , FabToFontToolsPenAdapter15 from robofab.pens.adapterPens import SegmentToPointPen 16 16 17 17 trunk/Lib/robofab/test/test_psHints.py
r56 r171 103 103 >>> f["new"].psHints.asDict() == g.psHints.asDict() 104 104 True 105 106 # multiplication107 >>> v = f.psHints * 2108 >>> v.asDict() == {'vStems': [1000, 20], 'blueFuzz': 2, 'blueShift': 2, 'forceBold': 2, 'blueScale': 1.0, 'hStems': [200, 180]}109 True110 111 # division112 >>> v = f.psHints / 2113 >>> v.asDict() == {'vStems': [250.0, 5.0], 'blueFuzz': 0.5, 'blueShift': 0.5, 'forceBold': 0.5, 'blueScale': 0.25, 'hStems': [50.0, 45.0]}114 True115 116 # multiplication with x, y, factor117 # note the h stems are multiplied by .5, the v stems (and blue values) are multiplied by 10118 >>> v = f.psHints * (.5, 10)119 >>> v.asDict() == {'vStems': [5000, 100], 'blueFuzz': 10, 'blueShift': 10, 'forceBold': 0.5, 'blueScale': 5.0, 'hStems': [50.0, 45.0]}120 True121 122 # multiplication with x, y, factor123 # note the h stems are divided by .5, the v stems (and blue values) are divided by 10124 >>> v = f.psHints / (.5, 10)125 >>> v.asDict() == {'vStems': [50.0, 1.0], 'blueFuzz': 0.10000000000000001, 'blueShift': 0.10000000000000001, 'forceBold': 2.0, 'blueScale': 0.050000000000000003, 'hStems': [200.0, 180.0]}126 True127 128 >>> v = f.psHints * .333129 >>> v.round()130 >>> v.asDict() == {'vStems': [167, 3], 'blueScale': 0.16650000000000001, 'hStems': [33, 30]}131 True132 133 105 """ 134 106 trunk/Lib/robofab/tools/toolsAll.py
r65 r171 13 13 14 14 15 import robofab16 from robofab.plistlib import readPlist, writePlist17 18 def readFoundrySettings(dstPath):19 """read the foundry settings xml file and return a keyed dict."""20 fileName = os.path.basename(dstPath)21 if not os.path.exists(dstPath):22 import shutil23 # hm -- a fresh install, make a new default settings file24 print "RoboFab: creating a new foundry settings file at", dstPath25 srcDir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(robofab.__file__))), 'Data')26 srcPath = os.path.join(srcDir, 'template_' + fileName)27 shutil.copy(srcPath, dstPath)28 return readPlist(dstPath)29 30 def getFoundrySetting(key, path):31 """get a specific setting from the foundry settings xml file."""32 d = readFoundrySettings(path)33 return d.get(key)34 35 writeFoundrySettings = writePlist36 37 def setFoundrySetting(key, value, dstPath):38 """write a specific entry in the foundry settings xml file."""39 d = readFoundrySettings(dstPath)40 d[key] = value41 writeFoundrySettings(d, dstPath)42 43 15 def readGlyphConstructions(): 44 16 """read GlyphConstruction and turn it into a dict""" … … 58 30 glyphConstructions[name] = build 59 31 return glyphConstructions 60 61 62 63 32 64 33 # trunk/Lib/robofab/ufoLib.py
r1 r171 1 1 """" 2 2 A library for importing .ufo files and their descendants. 3 This library works with robofab objects. Using the magic of the 4 U.F.O., common attributes are exported to and read from .plist files. 5 6 It contains two very simple classes for reading and writing the 7 various components of the .ufo. Currently, the .ufo supports the 8 files detailed below. But, these files are not absolutely required. 9 If the a file is not included in the .ufo, it is implied that the data 10 of that file is empty. 11 12 FontName.ufo/ 13 metainfo.plist # meta info about the .ufo bundle, most impartantly the 14 # format version number. 15 glyphs/ 16 contents.plist # a plist mapping all glyph names to file names 17 a.glif # a glif file 18 ...etc... 19 fontinfo.plist # font names, versions, copyright, dimentions, etc. 20 kerning.plist # kerning 21 lib.plist # user definable data 22 groups.plist # glyph group definitions 3 Refer to http://unifiedfontobject.com for the UFO specification. 4 5 The UFOReader and UFOWriter classes support versions 1 and 2 6 of the specification. Up and down conversion functions are also 7 supplied in this library. These conversion functions are only 8 necessary if conversion without loading the UFO data into 9 a set of objects is desired. These functions are: 10 convertUFOFormatVersion1ToFormatVersion2 11 convertUFOFormatVersion2ToFormatVersion1 12 13 Two sets that list the font info attribute names for the two 14 fontinfo.plist formats are available for external use. These are: 15 fontInfoAttributesVersion1 16 fontInfoAttributesVersion2 17 18 A set listing the fontinfo.plist attributes that were deprecated 19 in version 2 is available for external use: 20 deprecatedFontInfoAttributesVersion2 21 22 A function, validateFontInfoVersion2ValueForAttribute, that does 23 some basic validation on values for a fontinfo.plist value is 24 available for external use. 25 26 Two value conversion functions are availble for converting 27 fontinfo.plist values between the possible format versions. 28 convertFontInfoValueForAttributeFromVersion1ToVersion2 29 convertFontInfoValueForAttributeFromVersion2ToVersion1 23 30 """ 24 31 25 32 26 33 import os 34 import shutil 27 35 from cStringIO import StringIO 36 import calendar 28 37 from robofab.plistlib import readPlist, writePlist 29 38 from robofab.glifLib import GlyphSet, READ_MODE, WRITE_MODE 30 39 31 32 def writePlistAtomically(obj, path): 33 """Write a plist for 'obj' to 'path'. Do this sort of atomically, 34 making it harder to cause corrupt files, for example when writePlist 35 encounters an error halfway during write. Also: don't write out the 36 file if it would be identical to what's already there, meaning the 37 modification date won't get stomped when writing the same data. 38 """ 39 f = StringIO() 40 writePlist(obj, f) 41 data = f.getvalue() 42 if os.path.exists(path): 43 f = open(path, READ_MODE) 44 oldData = f.read() 45 f.close() 46 if data == oldData: 47 return 48 f = open(path, WRITE_MODE) 49 f.write(data) 50 f.close() 51 52 53 GLYPHS_DIRNAME = 'glyphs' 54 METAINFO_FILENAME = 'metainfo.plist' 55 FONTINFO_FILENAME = 'fontinfo.plist' 56 LIB_FILENAME = 'lib.plist' 57 GROUPS_FILENAME = 'groups.plist' 58 KERNING_FILENAME = 'kerning.plist' 59 60 61 fontInfoAttrs = [ 62 # XXX we need to document how these map to OTF 'name' table fields 63 'familyName', 64 'styleName', 65 'fullName', 66 'fontName', 67 'menuName', 68 'fontStyle', 69 'note', 70 'versionMajor', 71 'versionMinor', 72 'year', 73 'copyright', 74 'notice', 75 'trademark', 76 'license', 77 'licenseURL', 78 'createdBy', 79 'designer', 80 'designerURL', 81 'vendorURL', 82 'unitsPerEm', 83 'ascender', 84 'descender', 85 'capHeight', 86 'xHeight', 87 'defaultWidth', 88 'slantAngle', 89 'italicAngle', 90 'widthName', 91 'weightName', 92 'weightValue', 93 94 # dubious format-specific fields 95 'fondName', 96 'otFamilyName', 97 'otStyleName', 98 'otMacName', 99 'msCharSet', 100 'fondID', 101 'uniqueID', 102 'ttVendor', 103 'ttUniqueID', 104 'ttVersion', 40 try: 41 set 42 except NameError: 43 from sets import Set as set 44 45 __all__ = [ 46 "makeUFOPath" 47 "UFOLibError", 48 "UFOReader", 49 "UFOWriter", 50 "convertUFOFormatVersion1ToFormatVersion2", 51 "convertUFOFormatVersion2ToFormatVersion1", 52 "fontInfoAttributesVersion1", 53 "fontInfoAttributesVersion2", 54 "deprecatedFontInfoAttributesVersion2", 55 "validateFontInfoVersion2ValueForAttribute", 56 "convertFontInfoValueForAttributeFromVersion1ToVersion2", 57 "convertFontInfoValueForAttributeFromVersion2ToVersion1" 105 58 ] 106 59 107 60 108 def makeUFOPath(fontPath): 109 """return a .ufo pathname based on a .vfb pathname""" 110 dir, name = os.path.split(fontPath) 111 name = '.'.join([name.split('.')[0], 'ufo']) 112 return os.path.join(dir, name) 61 class UFOLibError(Exception): pass 62 63 64 # ---------- 65 # File Names 66 # ---------- 67 68 GLYPHS_DIRNAME = "glyphs" 69 METAINFO_FILENAME = "metainfo.plist" 70 FONTINFO_FILENAME = "fontinfo.plist" 71 LIB_FILENAME = "lib.plist" 72 GROUPS_FILENAME = "groups.plist" 73 KERNING_FILENAME = "kerning.plist" 74 FEATURES_FILENAME = "features.fea" 75 76 supportedUFOFormatVersions = [1, 2] 77 78 79 # --------------------------- 80 # Format Conversion Functions 81 # --------------------------- 82 83 84 def convertUFOFormatVersion1ToFormatVersion2(inPath, outPath=None): 85 """ 86 Function for converting a version format 1 UFO 87 to version format 2. inPath should be a path 88 to a UFO. outPath is the path where the new UFO 89 should be written. If outPath is not given, the 90 inPath will be used and, therefore, the UFO will 91 be converted in place. Otherwise, if outPath is 92 specified, nothing must exist at that path. 93 """ 94 if outPath is None: 95 outPath = inPath 96 if inPath != outPath and os.path.exists(outPath): 97 raise UFOLibError("A file already exists at %s." % outPath) 98 # use a reader for loading most of the data 99 reader = UFOReader(inPath) 100 if reader.formatVersion == 2: 101 raise UFOLibError("The UFO at %s is already format version 2." % inPath) 102 groups = reader.readGroups() 103 kerning = reader.readKerning() 104 libData = reader.readLib() 105 # read the info data manually and convert 106 infoPath = os.path.join(inPath, FONTINFO_FILENAME) 107 if not os.path.exists(infoPath): 108 infoData = {} 109 else: 110 infoData = readPlist(infoPath) 111 infoData = _convertFontInfoDataVersion1ToVersion2(infoData) 112 # if the paths are the same, only need to change the 113 # fontinfo and meta info files. 114 infoPath = os.path.join(outPath, FONTINFO_FILENAME) 115 if inPath == outPath: 116 metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) 117 metaInfo = dict( 118 creator="org.robofab.ufoLib", 119 formatVersion=2 120 ) 121 writePlistAtomically(metaInfo, metaInfoPath) 122 writePlistAtomically(infoData, infoPath) 123 # otherwise write everything. 124 else: 125 writer = UFOWriter(outPath) 126 writer.writeGroups(groups) 127 writer.writeKerning(kerning) 128 writer.writeLib(libData) 129 # write the info manually 130 writePlistAtomically(infoData, infoPath) 131 # copy the glyph tree 132 inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) 133 outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) 134 if os.path.exists(inGlyphs): 135 shutil.copytree(inGlyphs, outGlyphs) 136 137 def convertUFOFormatVersion2ToFormatVersion1(inPath, outPath=None): 138 """ 139 Function for converting a version format 2 UFO 140 to version format 1. inPath should be a path 141 to a UFO. outPath is the path where the new UFO 142 should be written. If outPath is not given, the 143 inPath will be used and, therefore, the UFO will 144 be converted in place. Otherwise, if outPath is 145 specified, nothing must exist at that path. 146 """ 147 if outPath is None: 148 outPath = inPath 149 if inPath != outPath and os.path.exists(outPath): 150 raise UFOLibError("A file already exists at %s." % outPath) 151 # use a reader for loading most of the data 152 reader = UFOReader(inPath) 153 if reader.formatVersion == 1: 154 raise UFOLibError("The UFO at %s is already format version 1." % inPath) 155 groups = reader.readGroups() 156 kerning = reader.readKerning() 157 libData = reader.readLib() 158 # read the info data manually and convert 159 infoPath = os.path.join(inPath, FONTINFO_FILENAME) 160 if not os.path.exists(infoPath): 161 infoData = {} 162 else: 163 infoData = readPlist(infoPath) 164 infoData = _convertFontInfoDataVersion2ToVersion1(infoData) 165 # if the paths are the same, only need to change the 166 # fontinfo, metainfo and feature files. 167 infoPath = os.path.join(outPath, FONTINFO_FILENAME) 168 if inPath == outPath: 169 metaInfoPath = os.path.join(inPath, METAINFO_FILENAME) 170 metaInfo = dict( 171 creator="org.robofab.ufoLib", 172 formatVersion=1 173 ) 174 writePlistAtomically(metaInfo, metaInfoPath) 175 writePlistAtomically(infoData, infoPath) 176 featuresPath = os.path.join(inPath, FEATURES_FILENAME) 177 if os.path.exists(featuresPath): 178 os.remove(featuresPath) 179 # otherwise write everything. 180 else: 181 writer = UFOWriter(outPath, formatVersion=1) 182 writer.writeGroups(groups) 183 writer.writeKerning(kerning) 184 writer.writeLib(libData) 185 # write the info manually 186 writePlistAtomically(infoData, infoPath) 187 # copy the glyph tree 188 inGlyphs = os.path.join(inPath, GLYPHS_DIRNAME) 189 outGlyphs = os.path.join(outPath, GLYPHS_DIRNAME) 190 if os.path.exists(inGlyphs): 191 shutil.copytree(inGlyphs, outGlyphs) 192 193 194 # ---------- 195 # UFO Reader 196 # ---------- 113 197 114 198 115 199 class UFOReader(object): 116 117 """ read the various components of the .ufo"""118 200 201 """Read the various components of the .ufo.""" 202 119 203 def __init__(self, path): 120 204 self._path = path 121 205 self.readMetaInfo() 206 207 def _get_formatVersion(self): 208 return self._formatVersion 209 210 formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is determined by reading metainfo.plist during __init__.") 211 122 212 def _checkForFile(self, path): 123 213 if not os.path.exists(path): 124 #print "missing file: %s" % path125 214 return False 126 215 else: 127 216 return True 128 217 129 218 def readMetaInfo(self): 130 """read metainfo.plist. mostly used 131 for internal operations""" 219 """ 220 Read metainfo.plist. Only used for internal operations. 221 """ 132 222 path = os.path.join(self._path, METAINFO_FILENAME) 133 223 if not self._checkForFile(path): 134 return 135 224 raise UFOLibError("metainfo.plist is missing in %s. This file is required." % self._path) 225 # should there be a blind try/except with a UFOLibError 226 # raised in except here (and elsewhere)? It would be nice to 227 # provide external callers with a single exception to catch. 228 data = readPlist(path) 229 formatVersion = data["formatVersion"] 230 if formatVersion not in supportedUFOFormatVersions: 231 raise UFOLibError("Unsupported UFO format (%d) in %s." % (formatVersion, self._path)) 232 self._formatVersion = formatVersion 233 136 234 def readGroups(self): 137 """read groups.plist. returns a dict that should 138 be applied to a font.groups object.""" 235 """ 236 Read groups.plist. Returns a dict. 237 """ 139 238 path = os.path.join(self._path, GROUPS_FILENAME) 140 239 if not self._checkForFile(path): 141 240 return {} 142 241 return readPlist(path) 143 242 144 243 def readInfo(self, info): 145 """read info.plist. it requires a font.info object 146 as an argument. this will write the attributes 147 defined in the file into the info object.""" 244 """ 245 Read fontinfo.plist. It requires an object that allows 246 setting attributes with names that follow the fontinfo.plist 247 version 2 specification. This will write the attributes 248 defined in the file into the object. 249 """ 250 # load the file and return if there is no file 148 251 path = os.path.join(self._path, FONTINFO_FILENAME) 149 252 if not self._checkForFile(path): 150 return {}253 return 151 254 infoDict = readPlist(path) 152 for key, value in infoDict.items(): 255 infoDataToSet = {} 256 # version 1 257 if self._formatVersion == 1: 258 for attr in fontInfoAttributesVersion1: 259 value = infoDict.get(attr) 260 if value is not None: 261 infoDataToSet[attr] = value 262 infoDataToSet = _convertFontInfoDataVersion1ToVersion2(infoDataToSet) 263 # version 2 264 elif self._formatVersion == 2: 265 for attr, dataValidationDict in _fontInfoAttributesVersion2ValueData.items(): 266 value = infoDict.get(attr) 267 if value is None: 268 continue 269 infoDataToSet[attr] = value 270 # unsupported version 271 else: 272 raise NotImplementedError 273 # validate data 274 infoDataToSet = _validateInfoVersion2Data(infoDataToSet) 275 # populate the object 276 for attr, value in infoDataToSet.items(): 153 277 try: 154 setattr(info, key, value)278 setattr(info, attr, value) 155 279 except AttributeError: 156 # object doesn't support setting this attribute 157 pass 158 280 raise UFOLibError("The supplied info object does not support setting a necessary attribute (%s)." % attr) 281 159 282 def readKerning(self): 160 """read kerning.plist. returns a dict that should 161 be applied to a font.kerning object.""" 283 """ 284 Read kerning.plist. Returns a dict. 285 """ 162 286 path = os.path.join(self._path, KERNING_FILENAME) 163 287 if not self._checkForFile(path): … … 170 294 kerning[left, right] = value 171 295 return kerning 172 296 173 297 def readLib(self): 174 """read lib.plist. returns a dict that should 175 be applied to a font.lib object.""" 298 """ 299 Read lib.plist. Returns a dict. 300 """ 176 301 path = os.path.join(self._path, LIB_FILENAME) 177 302 if not self._checkForFile(path): 178 303 return {} 179 304 return readPlist(path) 180 305 306 def readFeatures(self): 307 """ 308 Read features.fea. Returns a string. 309 """ 310 path = os.path.join(self._path, FEATURES_FILENAME) 311 if not self._checkForFile(path): 312 return "" 313 f = open(path, READ_MODE) 314 text = f.read() 315 f.close() 316 return text 317 181 318 def getGlyphSet(self): 182 """return the GlyphSet associated with the 183 glyphs directory in the .ufo""" 319 """ 320 Return the GlyphSet associated with the 321 glyphs directory in the .ufo. 322 """ 184 323 glyphsPath = os.path.join(self._path, GLYPHS_DIRNAME) 185 324 return GlyphSet(glyphsPath) 186 325 187 326 def getCharacterMapping(self): 188 """Return a dictionary that maps unicode values (ints) to 327 """ 328 Return a dictionary that maps unicode values (ints) to 189 329 lists of glyph names. 190 330 """ … … 202 342 203 343 344 # ---------- 345 # UFO Writer 346 # ---------- 347 348 204 349 class UFOWriter(object): 205 206 """write the various components of the .ufo""" 207 208 fileCreator = 'org.robofab.ufoLib' 209 formatVersion = 1 # the format version is an int, the next version will be 2. 210 211 def __init__(self, path): 350 351 """Write the various components of the .ufo.""" 352 353 def __init__(self, path, formatVersion=2, fileCreator="org.robofab.ufoLib"): 354 if formatVersion not in supportedUFOFormatVersions: 355 raise UFOLibError("Unsupported UFO format (%d)." % formatVersion) 212 356 self._path = path 213 357 self._formatVersion = formatVersion 358 self._fileCreator = fileCreator 359 self._writeMetaInfo() 360 # handle down conversion 361 if formatVersion == 1: 362 ## remove existing features.fea 363 featuresPath = os.path.join(path, FEATURES_FILENAME) 364 if os.path.exists(featuresPath): 365 os.remove(featuresPath) 366 367 def _get_formatVersion(self): 368 return self._formatVersion 369 370 formatVersion = property(_get_formatVersion, doc="The format version of the UFO. This is set into metainfo.plist during __init__.") 371 372 def _get_fileCreator(self): 373 return self._fileCreator 374 375 fileCreator = property(_get_fileCreator, doc="The file creator of the UFO. This is set into metainfo.plist during __init__.") 376 214 377 def _makeDirectory(self, subDirectory=None): 215 378 path = self._path … … 218 381 if not os.path.exists(path): 219 382 os.makedirs(path) 220 if not os.path.exists(os.path.join(path, METAINFO_FILENAME)):221 self._writeMetaInfo()222 383 return path 223 384 224 385 def _writeMetaInfo(self): 386 self._makeDirectory() 225 387 path = os.path.join(self._path, METAINFO_FILENAME) 226 metaInfo = {227 'creator': self.fileCreator,228 'formatVersion': self.formatVersion,229 }388 metaInfo = dict( 389 creator=self._fileCreator, 390 formatVersion=self._formatVersion 391 ) 230 392 writePlistAtomically(metaInfo, path) 231 393 232 394 def writeGroups(self, groups): 233 """write groups.plist. this method requires a 234 dict of glyph groups as an argument.""" 395 """ 396 Write groups.plist. This method requires a 397 dict of glyph groups as an argument. 398 """ 235 399 self._makeDirectory() 236 400 path = os.path.join(self._path, GROUPS_FILENAME) … … 242 406 elif os.path.exists(path): 243 407 os.remove(path) 244 408 245 409 def writeInfo(self, info): 246 """write info.plist. this method requires a 247 font.info object. attributes will be taken from 248 the given object and written into the file""" 410 """ 411 Write info.plist. This method requires an object 412 that supports getting attributes that follow the 413 fontinfo.plist version 2 secification. Attributes 414 will be taken from the given object and written 415 into the file. 416 """ 249 417 self._makeDirectory() 250 418 path = os.path.join(self._path, FONTINFO_FILENAME) 251 infoDict = {} 252 for name in fontInfoAttrs: 253 value = getattr(info, name, None) 254 if value is not None: 255 infoDict[name] = value 256 writePlistAtomically(infoDict, path) 257 419 # gather version 2 data 420 infoData = {} 421 for attr in _fontInfoAttributesVersion2ValueData.keys(): 422 try: 423 value = getattr(info, attr) 424 except AttributeError: 425 raise UFOLibError("The supplied info object does not support getting a necessary attribute (%s)." % attr) 426 if value is None: 427 continue 428 infoData[attr] = value 429 # validate data 430 infoData = _validateInfoVersion2Data(infoData) 431 # down convert data to version 1 if necessary 432 if self._formatVersion == 1: 433 infoData = _convertFontInfoDataVersion2ToVersion1(infoData) 434 # write file 435 writePlistAtomically(infoData, path) 436 258 437 def writeKerning(self, kerning): 259 """write kerning.plist. this method requires a 260 dict of kerning pairs as an argument""" 438 """ 439 Write kerning.plist. This method requires a 440 dict of kerning pairs as an argument. 441 """ 261 442 self._makeDirectory() 262 443 path = os.path.join(self._path, KERNING_FILENAME) … … 271 452 elif os.path.exists(path): 272 453 os.remove(path) 273 454 274 455 def writeLib(self, libDict): 275 """write lib.plist. this method requires a 276 lib dict as an argument""" 456 """ 457 Write lib.plist. This method requires a 458 lib dict as an argument. 459 """ 277 460 self._makeDirectory() 278 461 path = os.path.join(self._path, LIB_FILENAME) … … 282 465 os.remove(path) 283 466 467 def writeFeatures(self, features): 468 """ 469 Write features.fea. This method requires a 470 features string as an argument. 471 """ 472 if self._formatVersion == 1: 473 raise UFOLibError("features.fea is not allowed in UFO Format Version 1.") 474 self._makeDirectory() 475 path = os.path.join(self._path, FEATURES_FILENAME) 476 writeFileAtomically(features, path) 477 284 478 def makeGlyphPath(self): 285 """make the glyphs directory in the .ufo 286 returns the path of the directory created""" 479 """ 480 Make the glyphs directory in the .ufo. 481 Returns the path of the directory created. 482 """ 287 483 glyphDir = self._makeDirectory(GLYPHS_DIRNAME) 288 484 return glyphDir 289 485 290 486 def getGlyphSet(self, glyphNameToFileNameFunc=None): 291 """return the GlyphSet associated with the 292 glyphs directory in the .ufo""" 487 """ 488 Return the GlyphSet associated with the 489 glyphs directory in the .ufo. 490 """ 293 491 return GlyphSet(self.makeGlyphPath(), glyphNameToFileNameFunc) 492 493 # ---------------- 494 # Helper Functions 495 # ---------------- 496 497 def makeUFOPath(path): 498 """ 499 Return a .ufo pathname. 500 501 >>> makeUFOPath("/directory/something.ext") 502 '/directory/something.ufo' 503 >>> makeUFOPath("/directory/something.another.thing.ext") 504 '/directory/something.another.thing.ufo' 505 """ 506 dir, name = os.path.split(path) 507 name = ".".join([".".join(name.split(".")[:-1]), "ufo"]) 508 return os.path.join(dir, name) 509 510 def writePlistAtomically(obj, path): 511 """ 512 Write a plist for "obj" to "path". Do this sort of atomically, 513 making it harder to cause corrupt files, for example when writePlist 514 encounters an error halfway during write. This also checks to see 515 if text matches the text that is already in the file at path. 516 If so, the file is not rewritten so that the modification date 517 is preserved. 518 """ 519 f = StringIO() 520 writePlist(obj, f) 521 data = f.getvalue() 522 writeFileAtomically(data, path) 523 524 def writeFileAtomically(text, path): 525 """Write text into a file at path. Do this sort of atomically 526 making it harder to cause corrupt files. This also checks to see 527 if text matches the text that is already in the file at path. 528 If so, the file is not rewritten so that the modification date 529 is preserved.""" 530 if os.path.exists(path): 531 f = open(path, READ_MODE) 532 oldText = f.read() 533 f.close() 534 if text == oldText: 535 return 536 # if the text is empty, remove the existing file 537 if not text: 538 os.remove(path) 539 if text: 540 f = open(path, WRITE_MODE) 541 f.write(text) 542 f.close() 543 544 # ---------------------- 545 # fontinfo.plist Support 546 # ---------------------- 547 548 # Version 1 549 550 fontInfoAttributesVersion1 = set([ 551 "familyName", 552 "styleName", 553 "fullName", 554 "fontName", 555 "menuName", 556 "fontStyle", 557 "note", 558 "versionMajor", 559 "versionMinor", 560 "year", 561 "copyright", 562 "notice", 563 "trademark", 564 "license", 565 "licenseURL", 566 "createdBy", 567 "designer", 568 "designerURL", 569 "vendorURL", 570 "unitsPerEm", 571 "ascender", 572 "descender", 573 "capHeight", 574 "xHeight", 575 "defaultWidth", 576 "slantAngle", 577 "italicAngle", 578 "widthName", 579 "weightName", 580 "weightValue", 581 "fondName", 582 "otFamilyName", 583 "otStyleName", 584 "otMacName", 585 "msCharSet", 586 "fondID", 587 "uniqueID", 588 "ttVendor", 589 "ttUniqueID", 590 "ttVersion", 591 ]) 592 593 # Version 2 594 595 # Validators 596 597 def validateFontInfoVersion2ValueForAttribute(attr, value): 598 """ 599 This performs very basic validation of the value for attribute 600 following the UFO fontinfo.plist specification. The results 601 of this should not be interpretted as *correct* for the font 602 that they are part of. This merely indicates that the value 603 is of the proper type and, where the specification defines 604 a set range of possible values for an attribute, that the 605 value is in the accepted range. 606 """ 607 dataValidationDict = _fontInfoAttributesVersion2ValueData[attr] 608 valueType = dataValidationDict.get("type") 609 validator = dataValidationDict.get("valueValidator") 610 valueOptions = dataValidationDict.get("valueOptions") 611 # have specific options for the validator 612 if valueOptions is not None: 613 isValidValue = validator(value, valueOptions) 614 # no specific options 615 else: 616 if validator == _fontInfoTypeValidator: 617 isValidValue = validator(value, valueType) 618 else: 619 isValidValue = validator(value) 620 return isValidValue 621 622 def _validateInfoVersion2Data(infoData): 623 validInfoData = {} 624 for attr, value in infoData.items(): 625 isValidValue = validateFontInfoVersion2ValueForAttribute(attr, value) 626 if not isValidValue: 627 raise UFOLibError("Invalid value for attribute %s (%s)." % (attr, repr(value))) 628 else: 629 validInfoData[attr] = value 630 return infoData 631 632 def _fontInfoTypeValidator(value, typ): 633 return isinstance(value, typ) 634 635 def _fontInfoVersion2IntListValidator(values, validValues): 636 if not isinstance(values, (list, tuple)): 637 return False 638 valuesSet = set(values) 639 validValuesSet = set(validValues) 640 if len(valuesSet - validValuesSet) > 0: 641 return False 642 for value in values: 643 if not isinstance(value, int): 644 return False 645 return True 646 647 def _fontInfoVersion2StyleMapStyleNameValidator(value): 648 options = ["regular", "italic", "bold", "bold italic"] 649 return value in options 650 651 def _fontInfoVersion2OpenTypeHeadCreatedValidator(value): 652 # format: 0000/00/00 00:00:00 653 if not isinstance(value, (str, unicode)): 654 return False 655 # basic formatting 656 if not len(value) == 19: 657 return False 658 if value.count(" ") != 1: 659 return False 660 date, time = value.split(" ") 661 if date.count("/") != 2: 662 return False 663 if time.count(":") != 2: 664 return False 665 # date 666 year, month, day = date.split("/") 667 if len(year) != 4: 668 return False 669 if len(month) != 2: 670 return False 671 if len(day) != 2: 672 return False 673 try: 674 year = int(year) 675 month = int(month) 676 day = int(day) 677 except ValueError: 678 return False 679 if month < 1 or month > 12: 680 return False 681 monthMaxDay = calendar.monthrange(year, month) 682 if month > monthMaxDay: 683 return False 684 # time 685 hour, minute, second = time.split(":") 686 if len(hour) != 2: 687 return False 688 if len(minute) != 2: 689 return False 690 if len(second) != 2: 691 return False 692 try: 693 hour = int(hour) 694 minute = int(minute) 695 second = int(second) 696 except ValueError: 697 return False 698 if hour < 0 or hour > 23: 699 return False 700 if minute < 0 or minute > 59: 701 return False 702 if second < 0 or second > 59: 703 return True 704 # fallback 705 return True 706 707 def _fontInfoVersion2OpenTypeOS2WeightClassValidator(value): 708 if not isinstance(value, int): 709 return False 710 if value < 0: 711 return False 712 return True 713 714 def _fontInfoVersion2OpenTypeOS2WidthClassValidator(value): 715 if not isinstance(value, int): 716 return False 717 if value < 1: 718 return False 719 if value > 9: 720 return False 721 return True 722 723 def _fontInfoVersion2OpenTypeOS2PanoseValidator(values): 724 if not isinstance(values, (list, tuple)): 725 return False 726 if len(values) != 10: 727 return False 728 for value in values: 729 if not isinstance(value, int): 730 return False 731 # XXX further validation? 732 return True 733 734 def _fontInfoVersion2OpenTypeOS2FamilyClassValidator(values): 735 if not isinstance(values, (list, tuple)): 736 return False 737 if len(values) != 2: 738 return False 739 for value in values: 740 if not isinstance(value, int): 741 return False 742 classID, subclassID = values 743 if classID < 0 or classID > 14: 744 return False 745 if subclassID < 0 or subclassID > 15: 746 return False 747 return True 748 749 def _fontInfoVersion2PostscriptBluesValidator(values): 750 if not isinstance(values, (list, tuple)): 751 return False 752 if len(values) > 14: 753 return False 754 if len(values) % 2: 755 return False 756 for value in values: 757 if not isinstance(value, (int, float)): 758 return False 759 return True 760 761 def _fontInfoVersion2PostscriptOtherBluesValidator(values): 762 if not isinstance(values, (list, tuple)): 763 return False 764 if len(values) > 10: 765 return False 766 if len(values) % 2: 767 return False 768 for value in values: 769 if not isinstance(value, (int, float)): 770 return False 771 return True 772 773 def _fontInfoVersion2PostscriptStemsValidator(values): 774 if not isinstance(values, (list, tuple)): 775 return False 776 if len(values) > 12: 777 return False 778 for value in values: 779 if not isinstance(value, (int, float)): 780 return False 781 return True 782 783 def _fontInfoVersion2PostscriptWindowsCharacterSetValidator(value): 784 validValues = range(1, 21) 785 if value not in validValues: 786 return False 787 return True 788 789 # Attribute Definitions 790 # This defines the attributes, types and, in some 791 # cases the possible values, that can exist is 792 # fontinfo.plist. 793 794 _fontInfoVersion2OpenTypeHeadFlagsOptions = range(0, 14) 795 _fontInfoVersion2OpenTypeOS2SelectionOptions = [1, 2, 3, 4] 796 _fontInfoVersion2OpenTypeOS2UnicodeRangesOptions = range(0, 128) 797 _fontInfoVersion2OpenTypeOS2CodePageRangesOptions = range(0, 64) 798 _fontInfoVersion2OpenTypeOS2TypeOptions = [0, 1, 2, 3, 8, 9] 799 800 _fontInfoAttributesVersion2ValueData = { 801 "familyName" : dict(type=(str, unicode)), 802 "styleName" : dict(type=(str, unicode)), 803 "styleMapFamilyName" : dict(type=(str, unicode)), 804 "styleMapStyleName" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2StyleMapStyleNameValidator), 805 "versionMajor" : dict(type=int), 806 "versionMinor" : dict(type=int), 807 "year" : dict(type=int), 808 "copyright" : dict(type=(str, unicode)), 809 "trademark" : dict(type=(str, unicode)), 810 "unitsPerEm" : dict(type=(int, float)), 811 "descender" : dict(type=(int, float)), 812 "xHeight" : dict(type=(int, float)), 813 "capHeight" : dict(type=(int, float)), 814 "ascender" : dict(type=(int, float)), 815 "italicAngle" : dict(type=(float, int)), 816 "note" : dict(type=(str, unicode)), 817 "openTypeHeadCreated" : dict(type=(str, unicode), valueValidator=_fontInfoVersion2OpenTypeHeadCreatedValidator), 818 "openTypeHeadLowestRecPPEM" : dict(type=(int, float)), 819 "openTypeHeadFlags" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeHeadFlagsOptions), 820 "openTypeHheaAscender" : dict(type=(int, float)), 821 "openTypeHheaDescender" : dict(type=(int, float)), 822 "openTypeHheaLineGap" : dict(type=(int, float)), 823 "openTypeHheaCaretSlopeRise" : dict(type=int), 824 "openTypeHheaCaretSlopeRun" : dict(type=int), 825 "openTypeHheaCaretOffset" : dict(type=(int, float)), 826 "openTypeNameDesigner" : dict(type=(str, unicode)), 827 "openTypeNameDesignerURL" : dict(type=(str, unicode)), 828 "openTypeNameManufacturer" : dict(type=(str, unicode)), 829 "openTypeNameManufacturerURL" : dict(type=(str, unicode)), 830 "openTypeNameLicense" : dict(type=(str, unicode)), 831 "openTypeNameLicenseURL" : dict(type=(str, unicode)), 832 "openTypeNameVersion" : dict(type=(str, unicode)), 833 "openTypeNameUniqueID" : dict(type=(str, unicode)), 834 "openTypeNameDescription" : dict(type=(str, unicode)), 835 "openTypeNamePreferredFamilyName" : dict(type=(str, unicode)), 836 "openTypeNamePreferredSubfamilyName" : dict(type=(str, unicode)), 837 "openTypeNameCompatibleFullName" : dict(type=(str, unicode)), 838 "openTypeNameSampleText" : dict(type=(str, unicode)), 839 "openTypeNameWWSFamilyName" : dict(type=(str, unicode)), 840 "openTypeNameWWSSubfamilyName" : dict(type=(str, unicode)), 841 "openTypeOS2WidthClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WidthClassValidator), 842 "openTypeOS2WeightClass" : dict(type=int, valueValidator=_fontInfoVersion2OpenTypeOS2WeightClassValidator), 843 "openTypeOS2Selection" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2SelectionOptions), 844 "openTypeOS2VendorID" : dict(type=(str, unicode)), 845 "openTypeOS2Panose" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2PanoseValidator), 846 "openTypeOS2FamilyClass" : dict(type="integerList", valueValidator=_fontInfoVersion2OpenTypeOS2FamilyClassValidator), 847 "openTypeOS2UnicodeRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2UnicodeRangesOptions), 848 "openTypeOS2CodePageRanges" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2CodePageRangesOptions), 849 "openTypeOS2TypoAscender" : dict(type=(int, float)), 850 "openTypeOS2TypoDescender" : dict(type=(int, float)), 851 "openTypeOS2TypoLineGap" : dict(type=(int, float)), 852 "openTypeOS2WinAscent" : dict(type=(int, float)), 853 "openTypeOS2WinDescent" : dict(type=(int, float)), 854 "openTypeOS2Type" : dict(type="integerList", valueValidator=_fontInfoVersion2IntListValidator, valueOptions=_fontInfoVersion2OpenTypeOS2TypeOptions), 855 "openTypeOS2SubscriptXSize" : dict(type=(int, float)), 856 "openTypeOS2SubscriptYSize" : dict(type=(int, float)), 857 "openTypeOS2SubscriptXOffset" : dict(type=(int, float)), 858 "openTypeOS2SubscriptYOffset" : dict(type=(int, float)), 859 "openTypeOS2SuperscriptXSize" : dict(type=(int, float)), 860 "openTypeOS2SuperscriptYSize" : dict(type=(int, float)), 861 "openTypeOS2SuperscriptXOffset" : dict(type=(int, float)), 862 "openTypeOS2SuperscriptYOffset" : dict(type=(int, float)), 863 "openTypeOS2StrikeoutSize" : dict(type=(int, float)), 864 "openTypeOS2StrikeoutPosition" : dict(type=(int, float)), 865 "openTypeVheaVertTypoAscender" : dict(type=(int, float)), 866 "openTypeVheaVertTypoDescender" : dict(type=(int, float)), 867 "openTypeVheaVertTypoLineGap" : dict(type=(int, float)), 868 "openTypeVheaCaretSlopeRise" : dict(type=int), 869 "openTypeVheaCaretSlopeRun" : dict(type=int), 870 "openTypeVheaCaretOffset" : dict(type=(int, float)), 871 "postscriptFontName" : dict(type=(str, unicode)), 872 "postscriptFullName" : dict(type=(str, unicode)), 873 "postscriptSlantAngle" : dict(type=(float, int)), 874 "postscriptUniqueID" : dict(type=int), 875 "postscriptUnderlineThickness" : dict(type=(int, float)), 876 "postscriptUnderlinePosition" : dict(type=(int, float)), 877 "postscriptIsFixedPitch" : dict(type=bool), 878 "postscriptBlueValues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), 879 "postscriptOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), 880 "postscriptFamilyBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptBluesValidator), 881 "postscriptFamilyOtherBlues" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptOtherBluesValidator), 882 "postscriptStemSnapH" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), 883 "postscriptStemSnapV" : dict(type="integerList", valueValidator=_fontInfoVersion2PostscriptStemsValidator), 884 "postscriptBlueFuzz" : dict(type=(int, float)), 885 "postscriptBlueShift" : dict(type=(int, float)), 886 "postscriptBlueScale" : dict(type=(float, int)), 887 "postscriptForceBold" : dict(type=bool), 888 "postscriptDefaultWidthX" : dict(type=(int, float)), 889 "postscriptNominalWidthX" : dict(type=(int, float)), 890 "postscriptWeightName" : dict(type=(str, unicode)), 891 "postscriptDefaultCharacter" : dict(type=(str, unicode)), 892 "postscriptWindowsCharacterSet" : dict(type=int, valueValidator=_fontInfoVersion2PostscriptWindowsCharacterSetValidator), 893 "macintoshFONDFamilyID" : dict(type=int), 894 "macintoshFONDName" : dict(type=(str, unicode)), 895 } 896 fontInfoAttributesVersion2 = set(_fontInfoAttributesVersion2ValueData.keys()) 897 898 # insert the type validator for all attrs that 899 # have no defined validator. 900 for attr, dataDict in _fontInfoAttributesVersion2ValueData.items(): 901 if "valueValidator" not in dataDict: 902 dataDict["valueValidator"] = _fontInfoTypeValidator 903 904 # Version Conversion Support 905 # These are used from converting from version 1 906 # to version 2 or vice-versa. 907 908 def _flipDict(d): 909 flipped = {} 910 for key, value in d.items(): 911 flipped[value] = key 912 return flipped 913 914 _fontInfoAttributesVersion1To2 = { 915 "menuName" : "styleMapFamilyName", 916 "designer" : "openTypeNameDesigner", 917 "designerURL" : "openTypeNameDesignerURL", 918 "createdBy" : "openTypeNameManufacturer", 919 "vendorURL" : "openTypeNameManufacturerURL", 920 "license" : "openTypeNameLicense", 921 "licenseURL" : "openTypeNameLicenseURL", 922 "ttVersion" : "openTypeNameVersion", 923 "ttUniqueID" : "openTypeNameUniqueID", 924 "notice" : "openTypeNameDescription", 925 "otFamilyName" : "openTypeNamePreferredFamilyName", 926 "otStyleName" : "openTypeNamePreferredSubfamilyName", 927 "otMacName" : "openTypeNameCompatibleFullName", 928 "weightName" : "postscriptWeightName", 929 "weightValue" : "openTypeOS2WeightClass", 930 "ttVendor" : "openTypeOS2VendorID", 931 "uniqueID" : "postscriptUniqueID", 932 "fontName" : "postscriptFontName", 933 "fondID" : "macintoshFONDFamilyID", 934 "fondName" : "macintoshFONDName", 935 "defaultWidth" : "postscriptDefaultWidthX", 936 "slantAngle" : "postscriptSlantAngle", 937 "fullName" : "postscriptFullName", 938 # require special value conversion 939 "fontStyle" : "styleMapStyleName", 940 "widthName" : "openTypeOS2WidthClass", 941 "msCharSet" : "postscriptWindowsCharacterSet" 942 } 943 _fontInfoAttributesVersion2To1 = _flipDict(_fontInfoAttributesVersion1To2) 944 deprecatedFontInfoAttributesVersion2 = set(_fontInfoAttributesVersion1To2.keys()) 945 946 _fontStyle1To2 = { 947 64 : "regular", 948 1 : "italic", 949 32 : "bold", 950 33 : "bold italic" 951 } 952 _fontStyle2To1 = _flipDict(_fontStyle1To2) 953 # Some UFO 1 files have 0 954 _fontStyle1To2[0] = "regular" 955 956 _widthName1To2 = { 957 "Ultra-condensed" : 1, 958 "Extra-condensed" : 2, 959 "Condensed" : 3, 960 "Semi-condensed" : 4, 961 "Medium (normal)" : 5, 962 "Semi-expanded" : 6, 963 "Expanded" : 7, 964 "Extra-expanded" : 8, 965 "Ultra-expanded" : 9 966 } 967 _widthName2To1 = _flipDict(_widthName1To2) 968 # FontLab's default width value is "Normal". 969 # Many format version 1 UFOs will have this. 970 _widthName1To2["Normal"] = 5 971 # FontLab has an "All" width value. In UFO 1 972 # move this up to "Normal". 973 _widthName1To2["All"] = 5 974 # "medium" appears in a lot of UFO 1 files. 975 _widthName1To2["medium"] = 5 976 977 _msCharSet1To2 = { 978 0 : 1, 979 1 : 2, 980 2 : 3, 981 77 : 4, 982 128 : 5, 983 129 : 6, 984 130 : 7, 985 134 : 8, 986 136 : 9, 987 161 : 10, 988 162 : 11, 989 163 : 12, 990 177 : 13, 991 178 : 14, 992 186 : 15, 993 200 : 16, 994 204 : 17, 995 222 : 18, 996 238 : 19, 997 255 : 20 998 } 999 _msCharSet2To1 = _flipDict(_msCharSet1To2) 1000 1001 def convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value): 1002 """ 1003 Convert value from version 1 to version 2 format. 1004 Returns the new attribute name and the converted value. 1005 If the value is None, None will be returned for the new value. 1006 """ 1007 # convert floats to ints if possible 1008 if isinstance(value, float): 1009 if int(value) == value: 1010 value = int(value) 1011 if value is not None: 1012 if attr == "fontStyle": 1013 v = _fontStyle1To2.get(value) 1014 if v is None: 1015 raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) 1016 value = v 1017 elif attr == "widthName": 1018 v = _widthName1To2.get(value) 1019 if v is None: 1020 raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) 1021 value = v 1022 elif attr == "msCharSet": 1023 v = _msCharSet1To2.get(value) 1024 if v is None: 1025 raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), attr)) 1026 value = v 1027 attr = _fontInfoAttributesVersion1To2.get(attr, attr) 1028 return attr, value 1029 1030 def convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value): 1031 """ 1032 Convert value from version 2 to version 1 format. 1033 Returns the new attribute name and the converted value. 1034 If the value is None, None will be returned for the new value. 1035 """ 1036 if value is not None: 1037 if attr == "styleMapStyleName": 1038 value = _fontStyle2To1.get(value) 1039 elif attr == "openTypeOS2WidthClass": 1040 value = _widthName2To1.get(value) 1041 elif attr == "postscriptWindowsCharacterSet": 1042 value = _msCharSet2To1.get(value) 1043 attr = _fontInfoAttributesVersion2To1.get(attr, attr) 1044 return attr, value 1045 1046 def _convertFontInfoDataVersion1ToVersion2(data): 1047 converted = {} 1048 for attr, value in data.items(): 1049 # FontLab gives -1 for the weightValue 1050 # for fonts wil no defined value. Many 1051 # format version 1 UFOs will have this. 1052 if attr == "weightValue" and value == -1: 1053 continue 1054 newAttr, newValue = convertFontInfoValueForAttributeFromVersion1ToVersion2(attr, value) 1055 # skip if the attribute is not part of version 2 1056 if newAttr not in fontInfoAttributesVersion2: 1057 continue 1058 # catch values that can't be converted 1059 if value is None: 1060 raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) 1061 # store 1062 converted[newAttr] = newValue 1063 return converted 1064 1065 def _convertFontInfoDataVersion2ToVersion1(data): 1066 converted = {} 1067 for attr, value in data.items(): 1068 newAttr, newValue = convertFontInfoValueForAttributeFromVersion2ToVersion1(attr, value) 1069 # only take attributes that are registered for version 1 1070 if newAttr not in fontInfoAttributesVersion1: 1071 continue 1072 # catch values that can't be converted 1073 if value is None: 1074 raise UFOLibError("Cannot convert value (%s) for attribute %s." % (repr(value), newAttr)) 1075 # store 1076 converted[newAttr] = newValue 1077 return converted 1078 1079 1080 if __name__ == "__main__": 1081 import doctest 1082 doctest.testmod() trunk/Scripts/RoboFabIntro/demo_FindCompatibleGlyphs.py
r22 r171 22 22 23 23 print 24 print 'In %s, these glyphs could interpolate:'%(f.info. fullName)24 print 'In %s, these glyphs could interpolate:'%(f.info.postscriptFullName) 25 25 for d, names in compatibles.items(): 26 26 if len(names) > 1: trunk/Scripts/RoboFabIntro/intro_FontObject.py
r22 r171 31 31 else: 32 32 # and another dialog. 33 Message("The current font is %s"%(f.info. fullName))33 Message("The current font is %s"%(f.info.postscriptFullName)) 34 34 35 35 # let's have a look at some of the attributes a RoboFab Font object has … … 38 38 # some of the attributes map straight to the FontLab Font class 39 39 # We just straightened the camelCase here and there 40 print "full name of this font:", f.info. fullName40 print "full name of this font:", f.info.postscriptFullName 41 41 print "list of glyph names:", f.keys() 42 42 print 'ascender:', f.info.ascender trunk/Scripts/RoboFabIntro/intro_FoundrySettings.py
r22 r171 51 51 font.info.copyright = mySettings['copyright'] 52 52 font.info.trademark = mySettings['trademark'] 53 font.info. license = mySettings['license']54 font.info. licenseURL = mySettings['licenseurl']55 font.info. notice= mySettings['notice']56 font.info. ttVendor= mySettings['ttvendor']57 font.info. vendorURL = mySettings['vendorurl']58 font.info. designer = mySettings['designer']59 font.info. designerURL = mySettings['designerurl']53 font.info.openTypeNameLicense = mySettings['license'] 54 font.info.openTypeNameLicenseURL = mySettings['licenseurl'] 55 font.info.openTypeNameDescription = mySettings['notice'] 56 font.info.openTypeOS2VendorID = mySettings['ttvendor'] 57 font.info.openTypeNameManufacturerURL = mySettings['vendorurl'] 58 font.info.openTypeNameDesigner = mySettings['designer'] 59 font.info.openTypeNameDesignerURL = mySettings['designerurl'] 60 60 61 61 # and call the update method trunk/Scripts/RoboFabIntro/intro_Kerning.py
r22 r171 30 30 31 31 # kerning gives you access to some bits of global data 32 print "%s has %s kerning pairs"%(f.info. fullName, len(kerning))32 print "%s has %s kerning pairs"%(f.info.postscriptFullName, len(kerning)) 33 33 print "the average kerning value is %s"%kerning.getAverage() 34 34 min, max = kerning.getExtremes() trunk/Scripts/RoboFabUtils/RobustBatchGenerate.py
r22 r171 26 26 27 27 def generateOne(f, dstDir): 28 print "generating %s"%f.info. fullName28 print "generating %s"%f.info.postscriptFullName 29 29 f.generate('mactype1', dstDir) 30 30 trunk/Scripts/RoboFabUtils/TestFontEquality.py
r22 r171 9 9 line = [] 10 10 for n in af: 11 line.append(`n.info. fullName`)11 line.append(`n.info.postscriptFullName`) 12 12 results.append(line) 13 13 … … 15 15 one = af[i] 16 16 line = [] 17 line.append(af[i].info. fullName)17 line.append(af[i].info.postscriptFullName) 18 18 for j in range(len(af)): 19 19 other = af[j]
