1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
|
## Fonty Python Copyright (C) 2006, 2007, 2008, 2009 Donn.C.Ingle
## Contact: donn.ingle@gmail.com - I hope this email lasts.
##
## This file is part of Fonty Python.
## Fonty Python is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## Fonty Python is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Fonty Python. If not, see <http://www.gnu.org/licenses/>.
import os, sys, locale, glob, errno
import Image, ImageFont, ImageDraw
import fontybugs, fpsys
from pathcontrol import *
## Sep 2009 : zip functionality
import zipfile
zcompress=zipfile.ZIP_STORED #we default to uncompressed.
try:
import zlib
zcompress=zipfile.ZIP_DEFLATED #i.e. we can compress using this formula.
except:
pass
class FontItem( object ):
"""
Represents a single font file. It has the ability to provide a font image
and query a font file for the family name and style.
Ancestor to the specific classes per font type.
Never instantiate one directly.
"""
def __init__(self, glyphpaf ):
## glyphpaf must be whatever it is. unicode or a byte string
## This is used to access files themselves.
## When the LANG encoding is utf8, its Unicode.
self.glyphpaf = glyphpaf
#print "FontItem init, glyphpaf:", [glyphpaf]
## I want to have a var I can use when I display the glyphpaf
## either in the gui or onto the cli. This should be a unicode
## object, so I must make sure of it's type before I do so.
self.glyphpaf_unicode = fpsys.LSP.ensure_unicode( glyphpaf )
## The same goes for name. It *must* be unicode.
self.name = os.path.basename ( self.glyphpaf_unicode )
self.name = fpsys.LSP.ensure_unicode( self.name )
self.ticked = False # State of the tick/cross symbol.
self.inactive = False # Set in fpsys.markInactive()
self.activeInactiveMsg = "" #Say something unique when I draw this item.
## These are lists to cater for sub-faces
self.family, self.style = [], []
self.numFaces = 0
self.pilheight, self.pilwidth = 0,0
## I'm not bad in any way, until I turn bad that is :)
self.badfont = False
## If I'm bad, what should I say?
self.badfontmsg = ""
## What kind of bad to the bone am I?
## One of FILE_NOT_FOUND, PIL_IO_ERROR, PIL_UNICODE_ERROR, PIL_CANNOT_RENDER
self.badstyle = ""
## We need the family name and style to be fetched
## because we have that filter thingy in the gui
## and it uses those strings to search for terms.
##
## We also want to know what flavour of bad this item will be
## and we must open the file and query it to know that.
self.__queryFontFamilyStyleFlagBad()
## Vars used in the rendering stage.
self.fx = [None for i in xrange(self.numFaces)] # Position calculated for best top-left of each face image.
self.fy = self.fx[:] #Damn! Make a COPY of that list!
self.top_left_adjust_completed = False
def __queryFontFamilyStyleFlagBad( self ):
"""
Get the family, style and size of entire font.
If this font has a problem (PIL can't read it) then we set the
badfont flag, along with the badstyle & badfontmsg vars.
badstyle: FILE_NOT_FOUND, PIL_IO_ERROR, PIL_UNICODE_ERROR
The last kind of badstyle is set in generatePilFont() and is
set to: PIL_CANNOT_RENDER
This is tricky because FontImage.getname() is fragile
and apt to segfault. As of Dec 2007 I have reported
the bug, and a patch has been written but I don't
know how it will be implemented yet.
NB: InfoFontItem overrides this so it does not happen
in that case. This is why I made this a method and not
simply part of the __init__ above.
"""
i = 0
## Step through all subfaces.
#print "BUILDING:", [self.glyphpaf]
while True:
try:
fileDoesExist = os.path.exists( self.glyphpaf )
if not fileDoesExist:
self.badfont = True
self.badfontmsg = _("Font cannot be found, you should purge this Pog.")
self.badstyle = "FILE_NOT_FOUND"
## If the multi face font is damaged after the
## first face, then this won't catch it...
break # it's at the end of the sub-faces
except:
print _("Unhandled error:\nPlease move (%s) away from here and report this to us.") % self.glyphpaf
raise
try:
font = ImageFont.truetype(self.glyphpaf, 16, index=i, encoding="unicode" )
except IOError:
"""
Means the ttf file cannot be opened or rendered by PIL !
NOTE: Sets badfont
"""
if i == 0: # fubar on the first face:
self.badfont = True
self.badfontmsg = _("Font may be bad and it cannot be drawn.")
self.badstyle = "PIL_IO_ERROR"
## If the multi face font is damaged after the
## first face, then this won't catch it...
break # it's at the end of the sub-faces
except UnicodeEncodeError:
"""
NOTE: Sets badfont
"""
## Aw man! I thought I had this taped. This error does not *seem* to
## be related to the encoding param passed to ImageFont. I have
## mailed the PIL list about this.
##
## In the meantime, this will have to be flagged as a bad font.
self.badfont = True
self.badfontmsg = _("Unicode problem. Font may be bad and it cannot be drawn.")
self.badstyle = "PIL_UNICODE_ERROR"
break
except:
## What next on the error pile?
print "CORNER CASE in FontItem.__queryFontFamilyStyleFlagBad:", [self.glyphpaf]
print sys.exc_info()
raise
## Abort the app because this is an unhandled PIL error of some kind.
if not self.badfont:
## *If* 'check' is run, there will be a file containing pafs of the
## fonts that segfault PIL. (To my best knowledge, at least.)
## This file is held in the 'segfonts' list - opened in fpsys
## So, before we do a getname and cause a segfault, let's
## see whether that font is in segfonts, and if so, skip it.
if self.glyphpaf not in fpsys.segfonts:
# This writes to lastFontBeforeSegfault file. Just in case it crashes the
# app on the .family call below.
fpsys.logSegfaulters( self.glyphpaf )
##
## Sep 2009
## It has been reported by a user that some fonts have family names (and other info?) that
## are not English. They appear as ???y??? etc. in the font list. On my system Inkscape
## draws tham with squares (holding a unicode number) and a few Eastern characters.
## I am not sure AT ALL what to do about this. Is it a font/locale I should have installed?
##
## This is bug number: https://savannah.nongnu.org/bugs/index.php?27305
##
## I await help from PIL list.
#self.family.append( font.getname()[0] ) # old code
## No point Try-ing here, this segfaults when style/family is Null.
## July 2016
## =========
## Font caused an error. Here it is:
## chinese_rocks_rg-webfont.ttf [None] ['\x7f']
## The None is the problem.
n = fpsys.LSP.ensure_unicode( font.getname()[0] )
if n is None: n=""
self.family.append( n )
## July 2016: Let's assure no None in the style too
n = font.getname()[1]
if n is None: n=""
self.style.append( n )
i += 1
else:
## It WAS in the list! So, we can flag it and get on with life :)
#print "SKIPPING:",[self.glyphpaf]
self.badfont = True
self.badfontmsg = _("Font causes a segfault. It cannot be drawn.")
self.badstyle = "PIL_SEGFAULT_ERROR"
break
self.numFaces = i
def generatePilFont( self, enc="unicode" ):
"""
This function seems too similar to the __queryFontFamilyStyleFlagBad one
and in many ways it is. I am forced to work with PIL and it's not ideal
at the moment.
This function is called from the GUI in a tight loop. It provides (generates)
pilimage objects with the font's text rendered onto them.
Fonts that cause errors are marked 'badfont' and provide no image.
They can then be 'displayed' and can be put into Pogs etc., but they cannot
be seen.
"""
## text gets extra spaces at the end to cater for cut-off characters.
paf, points, text = self.glyphpaf, fpsys.config.points, " " + fpsys.config.text + " "
i = 0
while (True):
try:
font = ImageFont.truetype(paf, points,index=i, encoding=enc)
w,h = font.getsize( text )
## Some fonts (50SDINGS.ttf) return a 0 width.
## I don't know exactly why, it could be it could not render
## any of the chars in text.
if int(w) == 0:
w = 1
pilheight = int(h)
pilwidth = int(w)
pilheight += 10
## Sept 2009 : Fiddled this to produce alpha (ish) images.
pilimage = Image.new("RGBA", (pilwidth, pilheight), (0,0,0,0))#(255,255,255,255))
if self.inactive:
col = (0,0,0,64) #alpha makes it gray
else:
col = (0,0,0,255)
## Well, I have since discovered that some fonts
## cause a MemoryError on the next command:
drawnFont = ImageDraw.Draw( pilimage ) # Draws INTO pilimage
drawnFont.text((0,0) , text, font=font, fill=col)
## All is well, so we step ahead to the next *potential* sub-face
## and return the font image data.
i += 1
yield pilimage#, pilheight, pilwidth
except MemoryError:
"""
NOTE: Sets badfont
This one CAN ONLY BE CAUGHT HERE.
**IDEALLY**, it should have been caught in __queryFontFamilyStyleFlagBad
but for reasons explained below, it cannot.
So, we have a badfont flag being set here too :(
"""
## I found a font throwing a MemoryError (Onsoku Seinen Plane.ttf)
## that only happens upon the .text() command.
##
## UPDATE: Clever tricks don't work. Onsoku *only* barfs on "TE" and not
## "A" or even chr(0) to chr(255) all in a string...
## So, it's virtually impossible to know at this point what will
## cause the MemoryError in the rendering step.
self.badfontmsg = _("Font causes a memory error, it can't be drawn.")
self.badstyle = "PIL_CANNOT_RENDER"
self.badfont = True
break
## These two must be caught, but are already known about
## from the exact same test in __queryFontFamilyStyleFlagBad
except IOError:
## The font at index (i==0) cannot be opened.
break
except UnicodeEncodeError:
## Already handled in __queryFontFamilyStyleFlagBad
break
def __str__( self ):
return self.glyphpaf
def InfoOrErrorText(self):
"""Used in Fitmap code to draw strings and things."""
if self.badfont:
l1 = self.badfontmsg
l2 = self.glyphpaf_unicode
return ( l1, l2 )
## Create some subclasses to represent the fonts that we support:
class InfoFontItem( FontItem ):
"""
This class is only instantiated in wxgui.CreateFitmaps
It's used to indicate when a Folder or Pog is EMPTY and
if a single font is bad in some way.
It's the only Font Item in the target or source view list
at that time.
"""
def __init__( self, glyphpaf="" ):
FontItem.__init__( self, glyphpaf )
def __queryFontFamilyStyleFlagBad( self ):
"""Overridden so that it does not happen for this class."""
pass
def InfoOrErrorText( self ):
"""An override : InfoFontItem needs only these words"""
l1 = _("There are no fonts to see here, move along.")
l2 = _("(Check your filter!)")
return ( l1, l2 )
class TruetypeItem( FontItem ):
def __init__( self, glyphpaf ):
FontItem.__init__( self, glyphpaf )
class TruetypeCollectionItem( FontItem ):
def __init__( self, glyphpaf ):
FontItem.__init__( self, glyphpaf )
class OpentypeItem( FontItem ):
def __init__( self, glyphpaf ):
FontItem.__init__( self, glyphpaf )
class Type1Item( FontItem ):
def __init__( self, glyphpaf, metricpaf=None ):
FontItem.__init__( self, glyphpaf )
self.metricpaf = metricpaf
def itemGenerator( fromObj, sourceList ):
"""
Prepare for hell...
This is a *generator* function that yields a FontItem
instantiated according to the type of the font.
Call it once-off, or in a loop, to get one FontItem after another.
Pass it a sourceList that contains pafs.
VERY NB:
When the app is run from a utf8 locale, this func generates
UNICODE glyphpaf vars.
When run from C/POSIX/None it generates BYTE STRING glyphpaf vars.
sourceList is not a predictable beast. It comes in mixed strings/unicode
"""
#print "sourceList comes in:",[sourceList]
#print
def ext(s): return s.split(".")[-1].upper()
def stripExt(s): return s[:s.rfind(".")]
listOfItemsGenerated = []
## So, [paf,paf,paf,paf,paf] comes in.
## If it comes from FOLDER then it's full of ALL the files in a single dir.
## NB: It may or may not include 'type1' files and their 'metrics'
## If it comes from POG then it's just pafs of the basic glyph files
## thus it does not include the afm/pfm files.
## So: this is a special case.
## We must "fill-up" the list with the 'metric' files that are matched
## to Type1 files - i.e. AFM and PFM files that belong to the fonts
## of type 'type1' which we are detecting by extension as ".pfb" and ".pfa"
if isinstance( fromObj, Pog ):
tmp = []
for paf in sourceList:
## paf : /some/path/somefont.pfb (or .ttf, or whatever. Do them all.)
tmp.append( paf ) # add it to what will replace sourceList
dir = os.path.dirname( paf ) # /some/path
filename = os.path.basename( paf ) # somefont.pfb
## Find metric files with his name in his dir
wild = os.path.join( dir, stripExt(filename) + ".[PpAa][Ff][Mm]" )
metricfiles = glob.glob( wild )
## Add what we find (if anything) to the tmp list
for metric in metricfiles:
tmp.append( metric ) # merge them in
## Replace the sourceList
del( sourceList )
sourceList = tmp
## Now we have a sourceList that is complete - full of files
## TYPE1 stuff. 10 Jan 2008
## Jump through hoops to find the 'metric' files (AFM then PFM)
## for each Type1 font that's in the list
##
## As per advice on the freetype list I am doing this:
## For every PFA/PFB file, I look for a matching path and filename
## starting with AFM extensions, then trying PFM extensions.
## AFM is preferred over PFM.
## Make Type1Item objects and associate the 'metric' file found (or none)
## Filter some lists from sourceList to step through:
PFABs= [[stripExt(e),e] for e in sourceList if ext(e) in ("PFA","PFB")] # all type1 files
AFMs = [[stripExt(e),e] for e in sourceList if ext(e) in ("AFM")] # all AFM metric files
PFMs = [[stripExt(e),e] for e in sourceList if ext(e) in ("PFM")] # all PFM metric files
## Those lists look like this:
## ["/some/path/file", "/some/path/file.pfa"]
## [0] is paf sans extension, [1] is all of it (*)
## (*) We have to worry about case sensitivity, so I store more data.
## Go through the (maybe empty) list of PFA and FPB files
for pfab_tup in PFABs:
foundAFM = False
## Looking for AFM files
for afm_tup in AFMs:
if afm_tup[0] == pfab_tup[0]:
## We have found an afm file for this pfa/pfb file
## so make an object
fi = Type1Item( pfab_tup[1], metricpaf = afm_tup[1] )
listOfItemsGenerated.append( fi )
foundAFM = True
break
## If we found no AFM then try find a PFM
foundPFM = False
if not foundAFM:
## Looking for PFM files
for pfm_tup in PFMs:
if pfm_tup[0] == pfab_tup[0]:
## We have found pfm file for it, make an object
fi = Type1Item( pfab_tup[1], metricpaf = pfm_tup[1] )
listOfItemsGenerated.append( fi )
foundPFM = True
break
## If we found neither:
if not foundAFM and not foundPFM:
## Just make an object without a metric file associated.
fi = Type1Item( pfab_tup[1], metricpaf = None )
listOfItemsGenerated.append( fi )
## Do the other font types
TTFList = [ paf for paf in sourceList if ext(paf) == "TTF" ]
if len(TTFList) > 0:
for paf in TTFList:
fi = TruetypeItem( paf )
listOfItemsGenerated.append( fi )
OTFList = [ paf for paf in sourceList if ext(paf) == "OTF" ]
if len(OTFList) > 0:
for paf in OTFList:
fi = OpentypeItem( paf )
listOfItemsGenerated.append(fi)
TTCList = [ paf for paf in sourceList if ext(paf) == "TTC" ]
if len(TTCList) > 0:
for paf in TTCList:
fi = TruetypeCollectionItem( paf )
listOfItemsGenerated.append(fi)
## NB: listOfItemsGenerated can contain MIXED byte strings/unicode
## Sort the list: I use the glyphpaf_unicode var becuase it's unicode only.
listOfItemsGenerated.sort( cmp=locale.strcoll, key=lambda obj:obj.glyphpaf_unicode ) # Try to sort on that field.
## Supply it: This is pure magic!
for fi in listOfItemsGenerated:
yield fi
class BasicFontList(list):
"""
Ancestor to the Pog and Folder classes.
"""
def clear(self):
del self[:] # works. It was real touch and go there for a while.
def clearInactiveflags(self):
for fi in self:
fi.inactive = False
class EmptyView(BasicFontList):
"""
Imitates an empty Pog or an empty Folder.
"""
def __init__(self):
BasicFontList.__init__(self)
## Public properties:
self.name = "EMPTY"
self.installed = False
self.empty = True
def label(self):
return str(self.name)
def genList(self):
return
def isInstalled(self):
return False
class Folder(BasicFontList):
"""
Represents an entire Folder (from a path given by user clicking on the
GenericDirCtrl or from a command line string.)
This is called from fpsys.instantiateViewFolder
Contains a list of various FontItem Objects.
Supply the start path and an optional recurse T/F param.
"""
def __init__(self, path, recurse=False):
BasicFontList.__init__(self)
#print "path:",[path]
## I reckon self.path is always coming in as unicode.
## From the gui, it's unicode anyway cos of the dir control.
## From the cli, I converted args to unicode there.
self.path = os.path.abspath(path) # fix relative paths
## Added June 2009
def safeJoin(apath,filelist):
'''
It seems filelist cannot be relied on to be anything other than a mixed-bag
of unicode and/or bytestrings. I will join each to the apath and force the
result to bytestrings.
'''
returnList = []
for f in filelist:
paf = fpsys.LSP.path_join_ensure_bytestring_result( apath, f )
if os.path.isfile( paf ):
returnList.append( paf )
return returnList
## All recursive changes June 2009
if not recurse:
## Note: If self.path DOES NOT EXIST then this raises and OSError
## This can happen when we use the --all cli argument (see cli.py)
# Calling os.listdir here is okay because self.path is unicode, but
# listOfFilenamesOnly *should* be a list of pure unicode objects as a result.
# It's NOT - I have found problems... see safeJoin func just above.
listOfFilenamesOnly = os.listdir ( self.path ) # Get the unicode list
sourceList = safeJoin(self.path, listOfFilenamesOnly )
else:
# Recursive code
sourceList=[]
# Force P to be BYTE STRING from unicode<-came in as
P = fpsys.LSP.ensure_bytes( self.path )
for root, dirs, files in os.walk( P ):
## I need root and each file to be UNICODE, so I must decode them here
R = fpsys.LSP.to_unicode( root )
F = [ fpsys.LSP.to_unicode(f) for f in files ]
sourceList.extend( safeJoin( R, F ) )
# At this point sourceList is full of PURE BYTE STRINGS
## Now employ the generator magic:
## Makes FontItem objects for each paf in the list.
for fi in itemGenerator( self, sourceList ):
self.append(fi)
if len(self) == 0:
#print "EMPTY FOLDER"
raise fontybugs.FolderHasNoFonts(self.path)
def __str__(self):
return str(self.path)
def label( self ):
"""
A handy way to refer to Folders & Pogs in certain circumstances.
Pog.label returns the name
Folder.label returns the path
"""
return os.path.basename( self.path )
class Pog(BasicFontList):
"""
Represents an entire Pog.
Contains a list of various FontItems.
Dec 2007 - adding OTF and Type 1
Supply the pog name.
Must call genList() if you want actual font items in the list.
"""
def __init__(self, name ): #, progressCallback = None):
BasicFontList.__init__(self)
self.__pc = fpsys.iPC # A hack to pathcontrol.
## Public properties:
##
## name always comes in as a byte string because
## we built the path up to .fontypython from byte strings
## Make a unicode of that name:
uname = fpsys.LSP.ensure_unicode( name )
## Stores a unicode for access from other places:
self.name = uname
self.__installed = "dirty" #am I installed?
##
## NB NOTE: self.paf IS A BYTE STRING
##
## Note, name (not self.name) is used here. It is a byte string.
## appPath() is a bs, name is a bs so join leaves this all as a bs.
self.paf = os.path.join( self.__pc.appPath(),name + ".pog")
## OVERKILL : self.paf = fpsys.LSP.path_join_ensure_bytestring_result( self.__pc.appPath(),name + ".pog" )
self.badpog = False #To be used mainly to draw icons and ask user to purge.
def label(self):
"""
A handy way to refer to Folders & Pogs in certain circumstances.
See around line 1296 wxgui.OnMainClick()
Pog.label returns the name (in unicode)
Folder.label return the path
These are both the full paf of the font.
"""
return self.name
def __openfile(self):
"""
Open my pog file. Raise PogInvalid error or return a file handle.
"""
## NB: NO error is raised if an empty file is opened and read...
## If there is some chronic hard drive problem, I assume Python will quit anyway...
try:
#print "Trying to open:", [self.paf]
## In theory, any file can *always* be opened - no matter what
## locale. POSIX deals only in byte strings. So, this next
## line will never fail.
## I am going to open it as an ASCII file:
f = open( self.paf, 'r' ) # ASCII byte string file only.
except:
print "CORNER CASE in __openfile on paf:", [self.paf]
print sys.exc_info()
raise SystemExit
## Let's see what kind of line 1 we have
line1 = f.readline()[:-1]
self.__installed = "dirty" # unsure as to the status
if line1.upper() == "INSTALLED": self.__installed = "yes"
if line1.upper() == "NOT INSTALLED": self.__installed = "no"
if self.__installed == "dirty":
## We have a bad pog.
#print "ABOUT TO RENAME POG"
self.__renameBadPog()
raise fontybugs.PogInvalid( self.paf )
## At this point, we have a valid pog file:
## It has a valid line 1
## It may or may not have paf lines below it.
return f
def isInstalled(self):
"""
Passes a raise PogInvalid error through. Any other will abort app.
"""
if self.__installed == "yes": return True
if self.__installed == "no": return False
## Else it == "dirty" and:
## We must open the file to discover the status:
## Will raise an error, so don't handle it, let it propogate upwards.
f = self.__openfile() #sets __installed flag
f.close()
if self.__installed == "yes": return True
if self.__installed == "no": return False
def setInstalledFlag(self, TF):
"""
JULY 2016
=========
In a situation where we have two objects of the same Pog, and one is installed
we need to set the other one to installed too.
See gui_Right multiClick
"""
self.__installed = TF
def __renameBadPog(self):
"""
This is a bad pog, My plan is to rename it out of the .pog namespace.
No error detection ... yet
"""
newpaf = self.paf[:-4] + ".badpog" #kick out the .pog and append .badpog
#print "Invalid Pog : \"%s\"\nRenaming it to \"%s\"" % (self.paf, newpaf)
os.rename(self.paf, newpaf) #just going to hope this works...
self.paf = newpaf
def genList(self):
"""
Generate the list of font items within myself.
Access the disk. Build the object up. All attribs and the list of fonts.
Passes any PogInvalid error directly through.
"""
f = self.__openfile() #sets install flag, raises PogInvalid error.
self.clear() #clear is in basicfontlist.py
sourceList = []
## Right,
## If a line of the CONTENT of f is encoded differently to what the locale is
## then the for paf in f: line throws a UnicodeDecodeError
## This means that paf can't be read, but perhaps others can be...
try:
for paf in f: #This continues from line 2 onwards ...
paf = paf[:-1] #Strip the damn \n from the file
sourceList.append(paf)
f.close()
except UnicodeDecodeError:
## I can't even display the paf because it's not been set
## (paf is the last value that was read, since this is an error condition)
## I don't think I have a choice here but to simply pass
pass
## Not using this anymore.
#raise fontybugs.PogContentEncodingNotMatched( self.paf )
## Now to make Fontitems out of sourceList
for fi in itemGenerator( self, sourceList):
self.append(fi) # store them in myself.
def purge(self):
"""
Purge method - remove fonts in the pog that are not on disk.
Raises
PogEmpty
PogInstalled
"""
## can't purge an empty pog
if len(self) == 0:
raise fontybugs.PogEmpty(self.name) # RAISED :: PogEmpty
## can't purge an installed pog
if self.__installed == "yes":
raise fontybugs.PogInstalled(self.name) # RAISED :: PogInstalled
else:
## Let's build a new list of all the bad font items.
badfonts = []
for i in self:
try: #prevent weird errors on path test...
if not os.path.exists(i.glyphpaf) :
badfonts.append(i)
except:
pass # it's bad through-and-through! It'll be axed too.
## Now go thru this list and remove the bad items.
for bi in badfonts:
#print "purging:", bi.name
self.remove(bi)
self.write()
def install(self):
"""
Install the fonts in myself to the user's fonts folder.
NOTE:
Even if ONLY ONE font out of a gigazillion in the pog
actually installs, the POG == INSTALLED.
If we have a font that cannot be sourced, flag BADPOG
For Type1 fonts -
The choice I have made is:
I will ONLY reference the PFA/B in the Pog file.
When we install a Pog, the metric file must be sought
in the original folder and linked.
Raises:
PogEmpty
PogAllFontsFailedToInstall
PogSomeFontsDidNotInstall
NoFontsDir
"""
if not os.path.exists(self.__pc.userFontPath()):
raise fontybugs.NoFontsDir("Missing .fonts dir")
def linkfont(fi, frompaf, topaf):
## 15 Sept 2009 : Catch situations where the font is already installed.
try:
os.symlink(frompaf, topaf) #Should do the trick.
return True
except OSError, detail:
if detail.errno != errno.EEXIST: raise # File exists -- this font is already installed, we can ignore EEXIST.
## This font has been linked before.
fpsys.Overlap.inc(fi.name) # Use the class in fpsys to manage overlaps.
return False
## If this is flagged as installed, then just get out.
if self.__installed == "yes":
print _("%s is already installed." % self.name)
return
## We start thinking all is rosey:
self.__installed = "yes"
## Now we make sure ...
if len(self) == 0:
self.__installed = "no"
raise fontybugs.PogEmpty(self.name) # RAISED :: PogEmpty
## Now we go through the guts of the pog, font by font:
bugs = 0
for fi in self:
## These os.path functions have been performing flawlessly.
## See linux_safe_path_library remarks (at top) for details.
dirname = os.path.basename( fi.glyphpaf )
linkDestination = os.path.join(self.__pc.userFontPath(), dirname )
## Link it if it ain't already there.
if os.path.exists(fi.glyphpaf):
if linkfont(fi, fi.glyphpaf, linkDestination): #link the font and if True, it was not overlapped, so also do Type1 step 2
## Now, the Type1 step 2, link the metric file.
if isinstance( fi, Type1Item ):
## It's a Type 1, does it have a metricpaf?
if fi.metricpaf:
linkDestination = \
os.path.join( self.__pc.userFontPath(), os.path.basename( fi.metricpaf ) )
linkfont( fi, fi.metricpaf, linkDestination )
else:
bugs += 1
if bugs == len(self): # There was 100% failure to install fonts.
## We flag ourselves as NOT INSTALLED
self.__installed = "no"
self.write()
raise fontybugs.PogAllFontsFailedToInstall(self.name) # RAISED :: PogAllFontsFailedToInstall
elif bugs > 0:
## Some fonts did get installed, but not all. so, we are INSTALLED
self.write()
#print " semi [INSTALL COMPLETE]"
#print self.__installed
raise fontybugs.PogSomeFontsDidNotInstall(self.name) # RAISED :: PogSomeFontsDidNotInstall
self.write()
def uninstall(self):
"""
Uninstall the fonts.
NOTE:
If any font is NOT removed POG = INSTALLED
Any links that are not found = just ignore: They could have been removed by another pog, or this
could have been a bad pog anyway.
NO BAD POG flag EVER.
Raises:
PogEmpty
PogLinksRemain
PogNotInstalled
"""
if len(self) == 0: raise fontybugs.PogEmpty(self.name) # RAISED :: PogEmpty
bugs = 0
if self.__installed == "yes":
for fi in self:
#print "*Uninstalling candidate %s" % fi.name
if fpsys.Overlap.dec(fi.name):
#print " Going to leave this one here"
continue # If it overlaps then we skip removing it by going to next fi in loop.
#print " Going to REMOVE this one"
dirname = os.path.basename( fi.glyphpaf )
link = os.path.join(self.__pc.userFontPath(), dirname )
## Step one - look for the actual file (link)
if os.path.exists(link):
try:
os.unlink(link)
## The Type1 special case - its AFM/PFM may be here...
if isinstance( fi, Type1Item ):
## It's a Type 1, does it have a metricpaf?
## It may be None (if this pfb happens not to have had a afm/pfm.)
if fi.metricpaf:
pfmlink = os.path.join(self.__pc.userFontPath(), os.path.basename(fi.metricpaf))
if os.path.exists( pfmlink ):
os.unlink( pfmlink )
except: # e.g. Permission denied [err 13]
## Only bugs that imply that the file is THERE but CANNOT BE REMOVED
## are classified as bugs. We are making a sweeping assumption here.
bugs += 1
## Okay, we are currently INSTALLED, so what is the result of the loop?
if bugs > 0:
## We still have fonts in the pog that could NOT be removed, ergo we stay INSTALLED
raise fontybugs.PogLinksRemain(self.name) # RAISED :: PogLinksRemain
else:
## Okay - there were no problems, so we are now done.
self.__installed = "no"
self.write() #save to disk
#print " [UNINSTALL COMPLETE]"
else:
## self.__installed says we are not installed:
raise fontybugs.PogNotInstalled(self.name) # RAISED :: PogNotInstalled
def write(self) :
"""
Write a pog to disk.
"""
try:
f = open( self.paf, 'w' ) # Going to make the contents ASCII only. Byte strings.
i = "not installed\n"
if self.__installed == "yes":
i = "installed\n"
f.write(i)
#Now write the font pafs
for i in self:
## since the glyphpaf can vary it's type
## we must encode it to a byte string if it's unicode.
gpaf = fpsys.LSP.ensure_bytes( i.glyphpaf )
f.write( gpaf + "\n")
f.close()
except:
raise fontybugs.PogWriteError(self.paf)
def delete(self):
"""
Delete my pogfile, then clean myself up, ready to be destroyed.
"""
try:
os.unlink(self.paf)
except:
raise fontybugs.PogCannotDelete(self.paf)
self.clear()
self.__installed = "no"
def zip(self, todir):
"""Sept 2009 : Add all the fonts to a zip file in todir."""
## Start a zip file: I am not sure if a filename should be bytestrings or unicode....
file = zipfile.ZipFile(os.path.join(todir,self.name + ".fonts.zip"), "w")
self.genList() # I forget how to handle errors raised in that labyrinth... sorry world :(
#print "ZIP:",ipog.name
bugs=False
for fi in self:
## zipfiles have no internal encoding, so I must encode from unicode to a byte string
arcfile = fpsys.LSP.ensure_bytes(os.path.basename(fi.glyphpaf))
try:
file.write(fi.glyphpaf, arcfile, zcompress) #var set global at start of this module.
except OSError,e:
bugs=True
# e.errno == errno.ENOENT: # No such file or directory
print e # whatever is wrong, print the message and continue
## July 2016
## =========
## Randomly saw this error: ValueError('ZIP does not support timestamps before 1980')
## HAND. Added this new except with a message.
except ValueError,e:
bugs=True
print _("%s failed to zip because %s" % (fi.glyphpaf, e))
file.close()
return bugs # a flag for later.
|