Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eac2fe0

Browse files
committed
Add support for fonts with more than 256 codepoints in PDF backend.
This currently only works when pdf.fonttype == 42. (It doesn't seem possible to have a CID-keyed Type 3 font in PDF, so some hacky solution will have to be developed.) svn path=/trunk/matplotlib/; revision=3684
1 parent 08b841a commit eac2fe0

1 file changed

Lines changed: 181 additions & 135 deletions

File tree

lib/matplotlib/backends/backend_pdf.py

Lines changed: 181 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from matplotlib.font_manager import fontManager
2929
from matplotlib.afm import AFM
3030
from 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
3232
from matplotlib.mathtext import math_parse_s_pdf
3333
from matplotlib.transforms import Bbox
3434
from 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

Comments
 (0)