1def OpenFont(path, showInterface=True): 2 """ 3 Open font located at **path**. If **showInterface** 4 is ``False``, the font should be opened without 5 graphical interface. The default for **showInterface** 6 is ``True``. 7 8 :: 9 10 from fontParts.world import * 11 12 font = OpenFont("/path/to/my/font.ufo") 13 font = OpenFont("/path/to/my/font.ufo", showInterface=False) 14 """ 15 return dispatcher["OpenFont"](pathOrObject=path, showInterface=showInterface) 16 17 18def NewFont(familyName=None, styleName=None, showInterface=True): 19 """ 20 Create a new font. **familyName** will be assigned 21 to ``font.info.familyName`` and **styleName** 22 will be assigned to ``font.info.styleName``. These 23 are optional and default to ``None``. If **showInterface** 24 is ``False``, the font should be created without 25 graphical interface. The default for **showInterface** 26 is ``True``. 27 28 :: 29 30 from fontParts.world import * 31 32 font = NewFont() 33 font = NewFont(familyName="My Family", styleName="My Style") 34 font = NewFont(showInterface=False) 35 """ 36 return dispatcher["NewFont"](familyName=familyName, styleName=styleName, 37 showInterface=showInterface) 38 39 40def CurrentFont(): 41 """ 42 Get the "current" font. 43 """ 44 return dispatcher["CurrentFont"]() 45 46 47def CurrentGlyph(): 48 """ 49 Get the "current" glyph from :func:`CurrentFont`. 50 51 :: 52 53 from fontParts.world import * 54 55 glyph = CurrentGlyph() 56 """ 57 return dispatcher["CurrentGlyph"]() 58 59 60def CurrentLayer(): 61 """ 62 Get the "current" layer from :func:`CurrentGlyph`. 63 64 :: 65 66 from fontParts.world import * 67 68 layer = CurrentLayer() 69 """ 70 return dispatcher["CurrentLayer"]() 71 72 73def CurrentContours(): 74 """ 75 Get the "currently" selected contours from :func:`CurrentGlyph`. 76 77 :: 78 79 from fontParts.world import * 80 81 contours = CurrentContours() 82 83 This returns an immutable list, even when nothing is selected. 84 """ 85 return dispatcher["CurrentContours"]() 86 87 88def _defaultCurrentContours(): 89 glyph = CurrentGlyph() 90 if glyph is None: 91 return () 92 return glyph.selectedContours 93 94 95def CurrentSegments(): 96 """ 97 Get the "currently" selected segments from :func:`CurrentContours`. 98 99 :: 100 101 from fontParts.world import * 102 103 segments = CurrentSegments() 104 105 This returns an immutable list, even when nothing is selected. 106 """ 107 return dispatcher["CurrentSegments"]() 108 109 110def _defaultCurrentSegments(): 111 glyph = CurrentGlyph() 112 if glyph is None: 113 return () 114 segments = [] 115 for contour in glyph.selectedContours: 116 segments.extend(contour.selectedSegments) 117 return tuple(segments) 118 119 120def CurrentPoints(): 121 """ 122 Get the "currently" selected points from :func:`CurrentContours`. 123 124 :: 125 126 from fontParts.world import * 127 128 points = CurrentPoints() 129 130 This returns an immutable list, even when nothing is selected. 131 """ 132 return dispatcher["CurrentPoints"]() 133 134 135def _defaultCurrentPoints(): 136 glyph = CurrentGlyph() 137 if glyph is None: 138 return () 139 points = [] 140 for contour in glyph.selectedContours: 141 points.extend(contour.selectedPoints) 142 return tuple(points) 143 144 145def CurrentComponents(): 146 """ 147 Get the "currently" selected components from :func:`CurrentGlyph`. 148 149 :: 150 151 from fontParts.world import * 152 153 components = CurrentComponents() 154 155 This returns an immutable list, even when nothing is selected. 156 """ 157 return dispatcher["CurrentComponents"]() 158 159 160def _defaultCurrentComponents(): 161 glyph = CurrentGlyph() 162 if glyph is None: 163 return () 164 return glyph.selectedComponents 165 166 167def CurrentAnchors(): 168 """ 169 Get the "currently" selected anchors from :func:`CurrentGlyph`. 170 171 :: 172 173 from fontParts.world import * 174 175 anchors = CurrentAnchors() 176 177 This returns an immutable list, even when nothing is selected. 178 """ 179 return dispatcher["CurrentAnchors"]() 180 181 182def _defaultCurrentAnchors(): 183 glyph = CurrentGlyph() 184 if glyph is None: 185 return () 186 return glyph.selectedAnchors 187 188 189def CurrentGuidelines(): 190 """ 191 Get the "currently" selected guidelines from :func:`CurrentGlyph`. 192 This will include both font level and glyph level guidelines. 193 194 :: 195 196 from fontParts.world import * 197 198 guidelines = CurrentGuidelines() 199 200 This returns an immutable list, even when nothing is selected. 201 """ 202 return dispatcher["CurrentGuidelines"]() 203 204 205def _defaultCurrentGuidelines(): 206 guidelines = [] 207 font = CurrentFont() 208 if font is not None: 209 guidelines.extend(font.selectedGuidelines) 210 glyph = CurrentGlyph() 211 if glyph is not None: 212 guidelines.extend(glyph.selectedGuidelines) 213 return tuple(guidelines) 214 215 216def AllFonts(sortOptions=None): 217 """ 218 Get a list of all open fonts. Optionally, provide a 219 value for ``sortOptions`` to sort the fonts. See 220 :meth:`world.FontList.sortBy` for options. 221 222 223 :: 224 225 from fontParts.world import * 226 227 fonts = AllFonts() 228 for font in fonts: 229 # do something 230 231 fonts = AllFonts("magic") 232 for font in fonts: 233 # do something 234 235 fonts = AllFonts(["familyName", "styleName"]) 236 for font in fonts: 237 # do something 238 """ 239 fontList = FontList(dispatcher["AllFonts"]()) 240 if sortOptions is not None: 241 fontList.sortBy(sortOptions) 242 return fontList 243 244 245def RFont(path=None, showInterface=True): 246 return dispatcher["RFont"](pathOrObject=path, showInterface=showInterface) 247 248 249def RGlyph(): 250 return dispatcher["RGlyph"]() 251 252 253# --------- 254# Font List 255# --------- 256 257def FontList(fonts=None): 258 """ 259 Get a list with font specific methods. 260 261 :: 262 263 from fontParts.world import * 264 265 fonts = FontList() 266 267 Refer to :class:`BaseFontList` for full documentation. 268 """ 269 l = dispatcher["FontList"]() 270 if fonts: 271 l.extend(fonts) 272 return l 273 274 275class BaseFontList(list): 276 277 # Sort 278 279 def sortBy(self, sortOptions, reverse=False): 280 """ 281 Sort ``fonts`` with the ordering preferences defined 282 by ``sortBy``. ``sortBy`` must be one of the following: 283 284 * sort description string 285 * :class:`BaseInfo` attribute name 286 * sort value function 287 * list/tuple containing sort description strings, :class:`BaseInfo` 288 attribute names and/or sort value functions 289 * ``"magic"`` 290 291 Sort Description Strings 292 ------------------------ 293 294 The sort description strings, and how they modify the sort, are: 295 296 +--------------------+--------------------------------------+ 297 | ``"familyName"`` | Family names by alphabetical order. | 298 +--------------------+--------------------------------------+ 299 | ``"styleName"`` | Style names by alphabetical order. | 300 +--------------------+--------------------------------------+ 301 | ``"isItalic"`` | Italics before romans. | 302 +--------------------+--------------------------------------+ 303 | ``"isRoman"`` | Romans before italics. | 304 +--------------------+--------------------------------------+ 305 | ``"widthValue"`` | Width values by numerical order. | 306 +--------------------+--------------------------------------+ 307 | ``"weightValue"`` | Weight values by numerical order. | 308 +--------------------+--------------------------------------+ 309 | ``"monospace"`` | Monospaced before proportional. | 310 +--------------------+--------------------------------------+ 311 | ``"proportional"`` | Proportional before monospaced. | 312 +--------------------+--------------------------------------+ 313 314 :: 315 316 >>> fonts.sortBy(("familyName", "styleName")) 317 318 319 Font Info Attribute Names 320 ------------------------- 321 322 Any :class:`BaseFont` attribute name may be included as 323 a sort option. For example, to sort by x-height value, 324 you'd use the ``"xHeight"`` attribute name. 325 326 :: 327 328 >>> fonts.sortBy("xHeight") 329 330 331 Sort Value Function 332 ------------------- 333 334 A sort value function must be a function that accepts 335 one argument, ``font``. This function must return 336 a sortable value for the given font. For example: 337 338 :: 339 340 def glyphCountSortValue(font): 341 return len(font) 342 343 >>> fonts.sortBy(glyphCountSortValue) 344 345 A list of sort description strings and/or sort functions 346 may also be provided. This should be in order of most 347 to least important. For example, to sort by family name 348 and then style name, do this: 349 350 351 "magic" 352 ------- 353 354 If "magic" is given for ``sortBy``, the fonts will be 355 sorted based on this sort description sequence: 356 357 * ``"familyName"`` 358 * ``"isProportional"`` 359 * ``"widthValue"`` 360 * ``"weightValue"`` 361 * ``"styleName"`` 362 * ``"isRoman"`` 363 364 :: 365 366 >>> fonts.sortBy("magic") 367 """ 368 from types import FunctionType 369 valueGetters = dict( 370 familyName=_sortValue_familyName, 371 styleName=_sortValue_styleName, 372 isRoman=_sortValue_isRoman, 373 isItalic=_sortValue_isItalic, 374 widthValue=_sortValue_widthValue, 375 weightValue=_sortValue_weightValue, 376 isProportional=_sortValue_isProportional, 377 isMonospace=_sortValue_isMonospace 378 ) 379 if isinstance(sortOptions, str) or isinstance(sortOptions, FunctionType): 380 sortOptions = [sortOptions] 381 if not isinstance(sortOptions, (list, tuple)): 382 raise ValueError("sortOptions must a string, list or function.") 383 if not sortOptions: 384 raise ValueError("At least one sort option must be defined.") 385 if sortOptions == ["magic"]: 386 sortOptions = [ 387 "familyName", 388 "isProportional", 389 "widthValue", 390 "weightValue", 391 "styleName", 392 "isRoman" 393 ] 394 sorter = [] 395 for originalIndex, font in enumerate(self): 396 sortable = [] 397 for valueName in sortOptions: 398 if isinstance(valueName, FunctionType): 399 value = valueName(font) 400 elif valueName in valueGetters: 401 value = valueGetters[valueName](font) 402 elif hasattr(font.info, valueName): 403 value = getattr(font.info, valueName) 404 else: 405 raise ValueError("Unknown sort option: %s" % repr(valueName)) 406 sortable.append(value) 407 sortable.append(originalIndex) 408 sortable.append(font) 409 sorter.append(tuple(sortable)) 410 sorter.sort() 411 fonts = [i[-1] for i in sorter] 412 del self[:] 413 self.extend(fonts) 414 if reverse: 415 self.reverse() 416 417 # Search 418 419 def getFontsByFontInfoAttribute(self, *attributeValuePairs): 420 """ 421 Get a list of fonts that match the (attribute, value) 422 combinations in ``attributeValuePairs``. 423 424 :: 425 426 >>> subFonts = fonts.getFontsByFontInfoAttribute(("xHeight", 20)) 427 >>> subFonts = fonts.getFontsByFontInfoAttribute(("xHeight", 20), ("descender", -150)) 428 429 This will return an instance of :class:`BaseFontList`. 430 """ 431 found = self 432 for attr, value in attributeValuePairs: 433 found = self._matchFontInfoAttributes(found, (attr, value)) 434 return found 435 436 def _matchFontInfoAttributes(self, fonts, attributeValuePair): 437 found = self.__class__() 438 attr, value = attributeValuePair 439 for font in fonts: 440 if getattr(font.info, attr) == value: 441 found.append(font) 442 return found 443 444 def getFontsByFamilyName(self, familyName): 445 """ 446 Get a list of fonts that match ``familyName``. 447 This will return an instance of :class:`BaseFontList`. 448 """ 449 return self.getFontsByFontInfoAttribute(("familyName", familyName)) 450 451 def getFontsByStyleName(self, styleName): 452 """ 453 Get a list of fonts that match ``styleName``. 454 This will return an instance of :class:`BaseFontList`. 455 """ 456 return self.getFontsByFontInfoAttribute(("styleName", styleName)) 457 458 def getFontsByFamilyNameStyleName(self, familyName, styleName): 459 """ 460 Get a list of fonts that match ``familyName`` and ``styleName``. 461 This will return an instance of :class:`BaseFontList`. 462 """ 463 return self.getFontsByFontInfoAttribute(("familyName", familyName), ("styleName", styleName)) 464 465 466def _sortValue_familyName(font): 467 """ 468 Returns font.info.familyName. 469 """ 470 value = font.info.familyName 471 if value is None: 472 value = "" 473 return value 474 475 476def _sortValue_styleName(font): 477 """ 478 Returns font.info.styleName. 479 """ 480 value = font.info.styleName 481 if value is None: 482 value = "" 483 return value 484 485 486def _sortValue_isRoman(font): 487 """ 488 Returns 0 if the font is roman. 489 Returns 1 if the font is not roman. 490 """ 491 italic = _sortValue_isItalic(font) 492 if italic == 1: 493 return 0 494 return 1 495 496 497def _sortValue_isItalic(font): 498 """ 499 Returns 0 if the font is italic. 500 Returns 1 if the font is not italic. 501 """ 502 info = font.info 503 styleMapStyleName = info.styleMapStyleName 504 if styleMapStyleName is not None and "italic" in styleMapStyleName: 505 return 0 506 if info.italicAngle not in (None, 0): 507 return 0 508 return 1 509 510 511def _sortValue_widthValue(font): 512 """ 513 Returns font.info.openTypeOS2WidthClass. 514 """ 515 value = font.info.openTypeOS2WidthClass 516 if value is None: 517 value = -1 518 return value 519 520 521def _sortValue_weightValue(font): 522 """ 523 Returns font.info.openTypeOS2WeightClass. 524 """ 525 value = font.info.openTypeOS2WeightClass 526 if value is None: 527 value = -1 528 return value 529 530 531def _sortValue_isProportional(font): 532 """ 533 Returns 0 if the font is proportional. 534 Returns 1 if the font is not proportional. 535 """ 536 monospace = _sortValue_isMonospace(font) 537 if monospace == 1: 538 return 0 539 return 1 540 541 542def _sortValue_isMonospace(font): 543 """ 544 Returns 0 if the font is monospace. 545 Returns 1 if the font is not monospace. 546 """ 547 if font.info.postscriptIsFixedPitch: 548 return 0 549 if not len(font): 550 return 1 551 testWidth = None 552 for glyph in font: 553 if testWidth is None: 554 testWidth = glyph.width 555 else: 556 if testWidth != glyph.width: 557 return 1 558 return 0 559 560 561# ---------- 562# Dispatcher 563# ---------- 564 565class _EnvironmentDispatcher(object): 566 567 def __init__(self, registryItems): 568 self._registry = {item: None for item in registryItems} 569 570 def __setitem__(self, name, func): 571 self._registry[name] = func 572 573 def __getitem__(self, name): 574 func = self._registry[name] 575 if func is None: 576 raise NotImplementedError 577 return func 578 579 580dispatcher = _EnvironmentDispatcher([ 581 "OpenFont", 582 "NewFont", 583 "AllFonts", 584 "CurrentFont", 585 "CurrentGlyph", 586 "CurrentLayer", 587 "CurrentContours", 588 "CurrentSegments", 589 "CurrentPoints", 590 "CurrentComponents", 591 "CurrentAnchors", 592 "CurrentGuidelines", 593 "FontList", 594 "RFont", 595 "RLayer", 596 "RGlyph", 597 "RContour", 598 "RPoint", 599 "RAnchor", 600 "RComponent", 601 "RGuideline", 602 "RImage", 603 "RInfo", 604 "RFeatures", 605 "RGroups", 606 "RKerning", 607 "RLib", 608 609]) 610 611# Register the default functions. 612 613dispatcher["CurrentContours"] = _defaultCurrentContours 614dispatcher["CurrentSegments"] = _defaultCurrentSegments 615dispatcher["CurrentPoints"] = _defaultCurrentPoints 616dispatcher["CurrentComponents"] = _defaultCurrentComponents 617dispatcher["CurrentAnchors"] = _defaultCurrentAnchors 618dispatcher["CurrentGuidelines"] = _defaultCurrentGuidelines 619dispatcher["FontList"] = BaseFontList 620 621# ------- 622# fontshell 623# ------- 624 625try: 626 from fontParts import fontshell 627 628 # OpenFont, RFont 629 630 def _fontshellRFont(pathOrObject=None, showInterface=True): 631 return fontshell.RFont(pathOrObject=pathOrObject, showInterface=showInterface) 632 633 dispatcher["OpenFont"] = _fontshellRFont 634 dispatcher["RFont"] = _fontshellRFont 635 636 # NewFont 637 638 def _fontshellNewFont(familyName=None, styleName=None, showInterface=True): 639 font = fontshell.RFont(showInterface=showInterface) 640 if familyName is not None: 641 font.info.familyName = familyName 642 if styleName is not None: 643 font.info.styleName = styleName 644 return font 645 646 dispatcher["NewFont"] = _fontshellNewFont 647 648 # RLayer, RGlyph, RContour, RPoint, RAnchor, RComponent, RGuideline, RImage, RInfo, RFeatures, RGroups, RKerning, RLib 649 650 dispatcher["RLayer"] = fontshell.RLayer 651 dispatcher["RGlyph"] = fontshell.RGlyph 652 dispatcher["RContour"] = fontshell.RContour 653 dispatcher["RPoint"] = fontshell.RPoint 654 dispatcher["RAnchor"] = fontshell.RAnchor 655 dispatcher["RComponent"] = fontshell.RComponent 656 dispatcher["RGuideline"] = fontshell.RGuideline 657 dispatcher["RImage"] = fontshell.RImage 658 dispatcher["RInfo"] = fontshell.RInfo 659 dispatcher["RFeatures"] = fontshell.RFeatures 660 dispatcher["RGroups"] = fontshell.RGroups 661 dispatcher["RKerning"] = fontshell.RKerning 662 dispatcher["RLib"] = fontshell.RLib 663 664except ImportError: 665 pass 666