2828from matplotlib .font_manager import fontManager
2929from matplotlib .afm import AFM
3030from matplotlib .dviread import Dvi
31- from matplotlib .ft2font import FT2Font , FIXED_WIDTH , ITALIC , LOAD_NO_SCALE , LOAD_NO_HINTING
31+ from matplotlib .ft2font import FT2Font , FIXED_WIDTH , ITALIC , LOAD_NO_SCALE
3232from matplotlib .mathtext import math_parse_s_pdf
3333from matplotlib .transforms import Bbox
3434from matplotlib import ttconv
@@ -491,124 +491,52 @@ def cvt(length, upe=font.units_per_EM, nearest=True):
491491 # boxes and the like
492492 if value < 0 : return floor (value )
493493 else : return ceil (value )
494-
495- # You are lost in a maze of TrueType tables, all different...
496- ps_name = Name (font .get_sfnt ()[(1 ,0 ,0 ,6 )])
497- pclt = font .get_sfnt_table ('pclt' ) \
498- or { 'capHeight' : 0 , 'xHeight' : 0 }
499- post = font .get_sfnt_table ('post' ) \
500- or { 'italicAngle' : (0 ,0 ) }
501- ff = font .face_flags
502- sf = font .style_flags
503-
504- # Get widths for the 256 characters of PDF encoding "WinAnsiEncoding" (similar to
505- # Python encoding "cp1252"). According to the PDF Reference, a simple font, based on
506- # single-byte characters, can't manage more than 256 characters, contrary to a
507- # composite font, based on multi-byte characters.
508-
509- from encodings import cp1252
510- # The "decoding_map" was changed to a "decoding_table" as of Python 2.5.
511- if hasattr (cp1252 , 'decoding_map' ):
512- def decode_char (charcode ):
513- return cp1252 .decoding_map [charcode ] or 0
514- else :
515- def decode_char (charcode ):
516- return ord (cp1252 .decoding_table [charcode ])
517-
518- def get_char_width (charcode ):
519- unicode = decode_char (charcode )
520- width = font .load_char (unicode , flags = LOAD_NO_SCALE | LOAD_NO_HINTING ).horiAdvance
521- return cvt (width )
522-
523- firstchar , lastchar = 0 , 255
524- widths = [ get_char_width (charcode ) for charcode in range (firstchar , lastchar + 1 ) ]
525- font_bbox = [ cvt (x , nearest = False ) for x in font .bbox ]
526-
527- widthsObject = self .reserveObject ('font widths' )
528- fontdescObject = self .reserveObject ('font descriptor' )
529- # TODO: "WinAnsiEncoding" could become a parameter of PdfFile. The PDF encoding
530- # "WinAnsiEncoding" matches the Python enconding "cp1252" used in method
531- # RendererPdf.draw_text and RendererPdf.get_text_width_height to encode Unicode strings.
532- fontdict = { 'Type' : Name ('Font' ),
533- 'BaseFont' : ps_name ,
534- 'FirstChar' : firstchar ,
535- 'LastChar' : lastchar ,
536- 'Widths' : widthsObject ,
537- 'FontDescriptor' : fontdescObject }
538-
539- if fonttype == 3 :
494+
495+ def embedTTFType3 (font , characters , descriptor ):
496+ """The Type 3-specific part of embedding a Truetype font"""
497+ widthsObject = self .reserveObject ('font widths' )
498+ fontdescObject = self .reserveObject ('font descriptor' )
499+ fontdictObject = self .reserveObject ('font dictionary' )
540500 charprocsObject = self .reserveObject ('character procs' )
541501 differencesArray = []
542- fontdict ['Subtype' ] = Name ('Type3' )
543- fontdict ['Name' ] = ps_name
544- fontdict ['FontBBox' ] = font_bbox
545- fontdict ['FontMatrix' ] = [ .001 , 0 , 0 , .001 , 0 , 0 ]
546- fontdict ['CharProcs' ] = charprocsObject
547- fontdict ['Encoding' ] = {
548- 'Type' : Name ('Encoding' ),
549- 'Differences' : differencesArray }
550- elif fonttype == 42 :
551- fontdict ['Subtype' ] = Name ('TrueType' )
552- fontdict ['Encoding' ] = Name ('WinAnsiEncoding' )
553-
502+ firstchar , lastchar = 0 , 255
503+
504+ fontdict = {
505+ 'Type' : Name ('Font' ),
506+ 'BaseFont' : ps_name ,
507+ 'FirstChar' : firstchar ,
508+ 'LastChar' : lastchar ,
509+ 'FontDescriptor' : fontdescObject ,
510+ 'Subtype' : Name ('Type3' ),
511+ 'Name' : descriptor ['FontName' ],
512+ 'FontBBox' : [cvt (x , nearest = False ) for x in font .bbox ],
513+ 'FontMatrix' : [ .001 , 0 , 0 , .001 , 0 , 0 ],
514+ 'CharProcs' : charprocsObject ,
515+ 'Encoding' : {
516+ 'Type' : Name ('Encoding' ),
517+ 'Differences' : differencesArray },
518+ 'Widths' : widthsObject
519+ }
520+
521+ # Make the "Widths" array
522+ from encodings import cp1252
523+ # The "decoding_map" was changed to a "decoding_table" as of Python 2.5.
524+ if hasattr (cp1252 , 'decoding_map' ):
525+ def decode_char (charcode ):
526+ return cp1252 .decoding_map [charcode ] or 0
527+ else :
528+ def decode_char (charcode ):
529+ return ord (cp1252 .decoding_table [charcode ])
554530
555- flags = 0
556- symbolic = False #ps_name.name in ('Cmsy10', 'Cmmi10', 'Cmex10')
557- if ff & FIXED_WIDTH : flags |= 1 << 0
558- if 0 : flags |= 1 << 1 # TODO: serif
559- if symbolic : flags |= 1 << 2
560- else : flags |= 1 << 5
561- if sf & ITALIC : flags |= 1 << 6
562- if 0 : flags |= 1 << 16 # TODO: all caps
563- if 0 : flags |= 1 << 17 # TODO: small caps
564- if 0 : flags |= 1 << 18 # TODO: force bold
531+ def get_char_width (charcode ):
532+ unicode = decode_char (charcode )
533+ width = font .load_char (unicode , flags = LOAD_NO_SCALE ).horiAdvance
534+ return cvt (width )
565535
566- descriptor = {
567- 'Type' : Name ('FontDescriptor' ),
568- 'FontName' : ps_name ,
569- 'Flags' : flags ,
570- 'FontBBox' : [ cvt (x , nearest = False ) for x in font .bbox ],
571- 'Ascent' : cvt (font .ascender , nearest = False ),
572- 'Descent' : cvt (font .descender , nearest = False ),
573- 'CapHeight' : cvt (pclt ['capHeight' ], nearest = False ),
574- 'XHeight' : cvt (pclt ['xHeight' ]),
575- 'ItalicAngle' : post ['italicAngle' ][1 ], # ???
576- 'MaxWidth' : max (widths ),
577- 'StemV' : 0 # ???
578- }
579-
580- if fonttype == 42 :
581- descriptor ['FontFile2' ] = self .reserveObject ('font file' )
582-
583- # Other FontDescriptor keys include:
584- # /FontFamily /Times (optional)
585- # /FontStretch /Normal (optional)
586- # /FontFile (stream for type 1 font)
587- # /CharSet (used when subsetting type1 fonts)
588-
589- # Make an Identity-H encoded CID font for CM fonts? (Doesn't quite work)
590- if False :
591- del fontdict ['Widths' ], fontdict ['FontDescriptor' ], \
592- fontdict ['LastChar' ], fontdict ['FirstChar' ]
593-
594- fontdict ['Subtype' ] = Name ('Type0' )
595- fontdict ['Encoding' ] = Name ('Identity-H' )
596- fontdict2Object = self .reserveObject ('descendant font' )
597- fontdict ['DescendantFonts' ] = [ fontdict2Object ]
598- # TODO: fontdict['ToUnicode']
599- fontdict2 = { 'Type' : Name ('Font' ),
600- 'Subtype' : Name ('CIDFontType2' ),
601- 'BaseFont' : ps_name ,
602- 'W' : widthsObject ,
603- 'CIDSystemInfo' : { 'Registry' : 'Adobe' ,
604- 'Ordering' : 'Identity' ,
605- 'Supplement' : 0 },
606- 'FontDescriptor' : fontdescObject }
607- self .writeObject (fontdict2Object , fontdict2 )
608-
609- widths = [ firstchar , widths ]
536+ widths = [ get_char_width (charcode ) for charcode in range (firstchar , lastchar + 1 ) ]
537+ descriptor ['MaxWidth' ] = max (widths )
610538
611- if fonttype == 3 :
539+ # Make the "Differences" array
612540 cmap = font .get_charmap ()
613541 glyph_ids = []
614542 differences = []
@@ -626,6 +554,8 @@ def get_char_width(charcode):
626554 differencesArray .append (Name (name ))
627555 last_c = c
628556
557+ # Make the charprocs array (using ttconv for the
558+ # actual outlines)
629559 rawcharprocs = ttconv .get_pdf_charprocs (filename , glyph_ids )
630560 charprocs = {}
631561 charprocsRef = {}
@@ -637,13 +567,52 @@ def get_char_width(charcode):
637567 self .currentstream .write (stream )
638568 self .endStream ()
639569 charprocs [charname ] = charprocObject
570+
571+ # Write everything out
572+ self .writeObject (fontdictObject , fontdict )
573+ self .writeObject (fontdescObject , descriptor )
574+ self .writeObject (widthsObject , widths )
640575 self .writeObject (charprocsObject , charprocs )
641576
642- elif fonttype == 42 :
577+ return fontdictObject
578+
579+ def embedTTFType42 (font , characters , descriptor ):
580+ """The Type 42-specific part of embedding a Truetype font"""
581+ fontdescObject = self .reserveObject ('font descriptor' )
582+ cidFontDictObject = self .reserveObject ('CID font dictionary' )
583+ type0FontDictObject = self .reserveObject ('Type 0 font dictionary' )
584+ cidToGidMapObject = self .reserveObject ('CIDToGIDMap stream' )
585+ fontfileObject = self .reserveObject ('font file stream' )
586+ wObject = self .reserveObject ('Type 0 widths' )
587+
588+ cidFontDict = {
589+ 'Type' : Name ('Font' ),
590+ 'Subtype' : Name ('CIDFontType2' ),
591+ 'BaseFont' : ps_name ,
592+ 'CIDSystemInfo' : {
593+ 'Registry' : 'Adobe' ,
594+ 'Ordering' : 'Identity' ,
595+ 'Supplement' : 0 },
596+ 'FontDescriptor' : fontdescObject ,
597+ 'W' : wObject ,
598+ 'CIDToGIDMap' : cidToGidMapObject
599+ }
600+
601+ type0FontDict = {
602+ 'Type' : Name ('Font' ),
603+ 'Subtype' : Name ('Type0' ),
604+ 'BaseFont' : ps_name ,
605+ 'Encoding' : Name ('Identity-H' ),
606+ 'DescendantFonts' : [cidFontDictObject ]
607+ }
608+
609+ # Make fontfile stream
610+ descriptor ['FontFile2' ] = fontfileObject
643611 length1Object = self .reserveObject ('decoded length of a font' )
644- self .beginStream (descriptor ['FontFile2' ].id ,
645- self .reserveObject ('length of font stream' ),
646- {'Length1' : length1Object })
612+ self .beginStream (
613+ fontfileObject .id ,
614+ self .reserveObject ('length of font stream' ),
615+ {'Length1' : length1Object })
647616 fontfile = open (filename , 'rb' )
648617 length1 = 0
649618 while True :
@@ -655,13 +624,92 @@ def get_char_width(charcode):
655624 self .endStream ()
656625 self .writeObject (length1Object , length1 )
657626
658- fontdictObject = self .reserveObject ('font dictionary' )
659- self .writeObject (fontdictObject , fontdict )
660- self .writeObject (widthsObject , widths )
661- self .writeObject (fontdescObject , descriptor )
627+ # Make the 'W' (Widths) array and the CidToGidMap at the same time
628+ cid_to_gid_map = [u'\u0000 ' ] * 65536
629+ cmap = font .get_charmap ()
630+ widths = []
631+ max_ccode = 0
632+ for c in characters :
633+ ccode = ord (c )
634+ gind = cmap .get (ccode ) or 0
635+ glyph = font .load_char (ccode )
636+ # Why divided by 3.0 ??? Wish I knew... MGD
637+ widths .append ((ccode , cvt (glyph .horiAdvance ) / 3.0 ))
638+ cid_to_gid_map [ccode ] = unichr (gind )
639+ max_ccode = max (ccode , max_ccode )
640+ widths .sort ()
641+ cid_to_gid_map = cid_to_gid_map [:max_ccode + 1 ]
642+
643+ last_ccode = - 2
644+ w = []
645+ max_width = 0
646+ for ccode , width in widths :
647+ if ccode != last_ccode + 1 :
648+ w .append (ccode )
649+ w .append ([width ])
650+ else :
651+ w [- 1 ].append (width )
652+ max_width = max (max_width , width )
653+ last_ccode = ccode
654+
655+ # CIDToGIDMap stream
656+ cid_to_gid_map = "" .join (cid_to_gid_map ).encode ("utf-16be" )
657+ self .beginStream (cidToGidMapObject .id ,
658+ None ,
659+ {'Length' : len (cid_to_gid_map )})
660+ self .currentstream .write (cid_to_gid_map )
661+ self .endStream ()
662662
663- return fontdictObject
663+ descriptor ['MaxWidth' ] = max_width
664+
665+ # Write everything out
666+ self .writeObject (cidFontDictObject , cidFontDict )
667+ self .writeObject (type0FontDictObject , type0FontDict )
668+ self .writeObject (fontdescObject , descriptor )
669+ self .writeObject (wObject , w )
664670
671+ return type0FontDictObject
672+
673+ # Beginning of main embedTTF function...
674+
675+ # You are lost in a maze of TrueType tables, all different...
676+ ps_name = Name (font .get_sfnt ()[(1 ,0 ,0 ,6 )])
677+ pclt = font .get_sfnt_table ('pclt' ) \
678+ or { 'capHeight' : 0 , 'xHeight' : 0 }
679+ post = font .get_sfnt_table ('post' ) \
680+ or { 'italicAngle' : (0 ,0 ) }
681+ ff = font .face_flags
682+ sf = font .style_flags
683+
684+ flags = 0
685+ symbolic = False #ps_name.name in ('Cmsy10', 'Cmmi10', 'Cmex10')
686+ if ff & FIXED_WIDTH : flags |= 1 << 0
687+ if 0 : flags |= 1 << 1 # TODO: serif
688+ if symbolic : flags |= 1 << 2
689+ else : flags |= 1 << 5
690+ if sf & ITALIC : flags |= 1 << 6
691+ if 0 : flags |= 1 << 16 # TODO: all caps
692+ if 0 : flags |= 1 << 17 # TODO: small caps
693+ if 0 : flags |= 1 << 18 # TODO: force bold
694+
695+ descriptor = {
696+ 'Type' : Name ('FontDescriptor' ),
697+ 'FontName' : ps_name ,
698+ 'Flags' : flags ,
699+ 'FontBBox' : [ cvt (x , nearest = False ) for x in font .bbox ],
700+ 'Ascent' : cvt (font .ascender , nearest = False ),
701+ 'Descent' : cvt (font .descender , nearest = False ),
702+ 'CapHeight' : cvt (pclt ['capHeight' ], nearest = False ),
703+ 'XHeight' : cvt (pclt ['xHeight' ]),
704+ 'ItalicAngle' : post ['italicAngle' ][1 ], # ???
705+ 'StemV' : 0 # ???
706+ }
707+
708+ if fonttype == 3 :
709+ return embedTTFType3 (font , characters , descriptor )
710+ elif fonttype == 42 :
711+ return embedTTFType42 (font , characters , descriptor )
712+
665713 def alphaState (self , alpha ):
666714 """Return name of an ExtGState that sets alpha to the given value"""
667715
@@ -1113,12 +1161,7 @@ def draw_mathtext(self, gc, x, y, s, prop, angle):
11131161 self .file .output (self .file .fontName (fontname ), fontsize ,
11141162 Op .selectfont )
11151163 prev_font = fontname , fontsize
1116-
1117- if num < 256 :
1118- string = chr (num )
1119- else :
1120- string = "?"
1121- self .file .output (string , Op .show )
1164+ self .file .output (self .encode_string (unichr (num )), Op .show )
11221165 self .file .output (Op .end_text )
11231166
11241167 for record in pswriter :
@@ -1178,12 +1221,14 @@ def mytrans(x1, y1, x=x, y=y, a=angle / 180.0 * pi):
11781221 self .draw_polygon (boxgc , gc ._rgb ,
11791222 ((x1 ,y1 ), (x2 ,y2 ), (x3 ,y3 ), (x4 ,y4 )))
11801223
1224+ def encode_string (self , s ):
1225+ if rcParams ['pdf.fonttype' ] == 42 :
1226+ return s .encode ('utf-16be' , 'replace' )
1227+ return s .encode ('cp1252' , 'replace' )
1228+
11811229 def draw_text (self , gc , x , y , s , prop , angle , ismath = False ):
11821230 # TODO: combine consecutive texts into one BT/ET delimited section
11831231
1184- if isinstance (s , unicode ):
1185- s = s .encode ('cp1252' , 'replace' )
1186-
11871232 if ismath : return self .draw_mathtext (gc , x , y , s , prop , angle )
11881233 self .check_gc (gc , gc ._rgb )
11891234
@@ -1195,7 +1240,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
11951240 else :
11961241 font = self ._get_font_ttf (prop )
11971242 self .track_characters (font , s )
1198- font .set_text (s , 0.0 , flags = LOAD_NO_HINTING )
1243+ font .set_text (s , 0.0 )
11991244 y += font .get_descent () / 64.0
12001245
12011246 self .file .output (Op .begin_text ,
@@ -1204,7 +1249,8 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
12041249 Op .selectfont )
12051250
12061251 self ._setup_textpos (x , y , angle )
1207- self .file .output (s , Op .show , Op .end_text )
1252+
1253+ self .file .output (self .encode_string (s ), Op .show , Op .end_text )
12081254
12091255 def get_text_width_height (self , s , prop , ismath ):
12101256 if isinstance (s , unicode ):
@@ -1222,7 +1268,7 @@ def get_text_width_height(self, s, prop, ismath):
12221268
12231269 else :
12241270 font = self ._get_font_ttf (prop )
1225- font .set_text (s , 0.0 , flags = LOAD_NO_HINTING )
1271+ font .set_text (s , 0.0 )
12261272 w , h = font .get_width_height ()
12271273 w /= 64.0
12281274 h /= 64.0
0 commit comments