File: fontcontrol.py

package info (click to toggle)
fontypython 0.4.6-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 1,152 kB
  • ctags: 559
  • sloc: python: 3,619; makefile: 10
file content (950 lines) | stat: -rw-r--r-- 32,746 bytes parent folder | download
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.