From: <md...@us...> - 2007-08-09 14:24:59
|
Revision: 3690 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3690&view=rev Author: mdboom Date: 2007-08-09 07:23:46 -0700 (Thu, 09 Aug 2007) Log Message: ----------- Add support for Unicode fonts (char codes > 255) in PDF with Type 3 fonts. (Type 42 fonts were supported as of a preceding commit.) Modified Paths: -------------- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py trunk/matplotlib/lib/matplotlib/mathtext.py Modified: trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-08-09 09:02:13 UTC (rev 3689) +++ trunk/matplotlib/lib/matplotlib/backends/backend_pdf.py 2007-08-09 14:23:46 UTC (rev 3690) @@ -5,7 +5,6 @@ """ from __future__ import division -import md5 import os import re import sys @@ -28,9 +27,10 @@ from matplotlib.font_manager import fontManager from matplotlib.afm import AFM from matplotlib.dviread import Dvi -from matplotlib.ft2font import FT2Font, FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, LOAD_NO_HINTING +from matplotlib.ft2font import FT2Font, FIXED_WIDTH, ITALIC, LOAD_NO_SCALE, \ + LOAD_NO_HINTING, KERNING_UNFITTED from matplotlib.mathtext import math_parse_s_pdf -from matplotlib.transforms import Bbox +from matplotlib.transforms import Bbox, Affine, multiply_affines, Value from matplotlib import ttconv # Overview @@ -377,6 +377,7 @@ self.nextImage = 1 self.markers = {} + self.two_byte_charprocs = {} self.nextMarker = 1 # The PDF spec recommends to include every procset @@ -409,6 +410,8 @@ xobjects = dict(self.images.values()) for name, value in self.markers.items(): xobjects[name] = value[0] + for name, value in self.two_byte_charprocs.items(): + xobjects[name] = value self.writeObject(self.XObjectObject, xobjects) self.writeImages() self.writeMarkers() @@ -477,6 +480,11 @@ self.writeObject(fontdictObject, fontdict) return fontdictObject + def _get_xobject_symbol_name(self, filename, symbol_name): + return "%s-%s" % ( + os.path.splitext(os.path.basename(filename))[0], + symbol_name) + def embedTTF(self, filename, characters): """Embed the TTF font from the named file into the document.""" @@ -536,25 +544,32 @@ widths = [ get_char_width(charcode) for charcode in range(firstchar, lastchar+1) ] descriptor['MaxWidth'] = max(widths) - # Make the "Differences" array + # Make the "Differences" array, sort the ccodes < 255 from + # the two-byte ccodes, and build the whole set of glyph ids + # that we need from this font. cmap = font.get_charmap() glyph_ids = [] differences = [] + two_byte_chars = Set() for c in characters: ccode = ord(c) gind = cmap.get(ccode) or 0 glyph_ids.append(gind) - differences.append((ccode, font.get_glyph_name(gind))) + glyph_name = font.get_glyph_name(gind) + if ccode <= 255: + differences.append((ccode, glyph_name)) + else: + two_byte_chars.add(glyph_name) differences.sort() - last_c = -256 + last_c = -2 for c, name in differences: if c != last_c + 1: differencesArray.append(c) differencesArray.append(Name(name)) last_c = c - # Make the charprocs array (using ttconv for the + # Make the charprocs array (using ttconv to generate the # actual outlines) rawcharprocs = ttconv.get_pdf_charprocs(filename, glyph_ids) charprocs = {} @@ -563,10 +578,19 @@ charprocObject = self.reserveObject('charProc for %s' % name) self.beginStream(charprocObject.id, None, - {'Length': len(stream)}) + {'Length': len(stream), + 'Type': Name('XObject'), + 'Subtype': Name('Form'), + 'BBox': [cvt(x, nearest=False) for x in font.bbox]}) self.currentstream.write(stream) self.endStream() - charprocs[charname] = charprocObject + # Send the glyphs with ccode > 255 to the XObject dictionary, + # and the others to the font itself + if charname in two_byte_chars: + name = self._get_xobject_symbol_name(filename, charname) + self.two_byte_charprocs[name] = charprocObject + else: + charprocs[charname] = charprocObject # Write everything out self.writeObject(fontdictObject, fontdict) @@ -955,7 +979,11 @@ self.truetype_font_cache = {} self.afm_font_cache = {} self.file.used_characters = self.used_characters = {} - + if rcParams['pdf.fonttype'] == 3: + self.encode_string = self.encode_string_type3 + else: + self.encode_string = self.encode_string_type42 + def finalize(self): self.gc.finalize() del self.truetype_font_cache @@ -1145,30 +1173,61 @@ math_parse_s_pdf(s, 72, prop, 0) self.merge_used_characters(used_characters) + # When using Type 3 fonts, we can't use character codes higher + # than 255, so we use the "Do" command to render those + # instead. + fonttype = rcParams['pdf.fonttype'] + + # Set up a global transformation matrix for the whole math expression + a = angle / 180.0 * pi + self.file.output(Op.gsave) + self.file.output(cos(a), sin(a), -sin(a), cos(a), x, y, + Op.concat_matrix) + self.check_gc(gc, gc._rgb) self.file.output(Op.begin_text) prev_font = None, None oldx, oldy = 0, 0 for record in pswriter: if record[0] == 'glyph': - rec_type, ox, oy, fontname, fontsize, num = record - a = angle / 180.0 * pi - newx = x + cos(a)*ox - sin(a)*oy - newy = y + sin(a)*ox + cos(a)*oy - self._setup_textpos(newx, newy, angle, oldx, oldy) - oldx, oldy = newx, newy - if (fontname, fontsize) != prev_font: - self.file.output(self.file.fontName(fontname), fontsize, - Op.selectfont) - prev_font = fontname, fontsize - self.file.output(self.encode_string(unichr(num)), Op.show) + rec_type, ox, oy, fontname, fontsize, num, symbol_name = \ + record + if fonttype == 42 or num <= 255: + self._setup_textpos(ox, oy, 0, oldx, oldy) + oldx, oldy = ox, oy + if (fontname, fontsize) != prev_font: + self.file.output(self.file.fontName(fontname), fontsize, + Op.selectfont) + prev_font = fontname, fontsize + self.file.output(self.encode_string(unichr(num)), Op.show) self.file.output(Op.end_text) + # If using Type 3 fonts, render all of the two-byte characters + # as XObjects using the 'Do' command. + if fonttype == 3: + for record in pswriter: + if record[0] == 'glyph': + rec_type, ox, oy, fontname, fontsize, num, symbol_name = \ + record + if num > 255: + self.file.output(Op.gsave, + 0.001 * fontsize, 0, + 0, 0.001 * fontsize, + ox, oy, Op.concat_matrix) + name = self.file._get_xobject_symbol_name( + fontname, symbol_name) + self.file.output(Name(name), Op.use_xobject) + self.file.output(Op.grestore) + + # Draw any horizontal lines in the math layout for record in pswriter: if record[0] == 'rect': rec_type, ox, oy, width, height = record - self.file.output(Op.gsave, x + ox, y + oy, width, height, Op.rectangle, Op.fill, Op.grestore) + self.file.output(Op.gsave, ox, oy, width, height, Op.rectangle, Op.fill, Op.grestore) + # Pop off the global transformation + self.file.output(Op.grestore) + def _draw_tex(self, gc, x, y, s, prop, angle): # Rename to draw_tex to enable, but note the following: # TODO: @@ -1221,21 +1280,34 @@ self.draw_polygon(boxgc, gc._rgb, ((x1,y1), (x2,y2), (x3,y3), (x4,y4))) - def encode_string(self, s): - if rcParams['pdf.fonttype'] == 42: - return s.encode('utf-16be', 'replace') + def encode_string_type3(self, s): return s.encode('cp1252', 'replace') + def encode_string_type42(self, s): + return s.encode('utf-16be', 'replace') + def draw_text(self, gc, x, y, s, prop, angle, ismath=False): # TODO: combine consecutive texts into one BT/ET delimited section + # This function is rather complex, since there is no way to + # access characters of a Type 3 font with codes > 255. (Type + # 3 fonts can not have a CIDMap). Therefore, we break the + # string into chunks, where each chunk contains exclusively + # 1-byte or exclusively 2-byte characters, and output each + # chunk a separate command. 1-byte characters use the regular + # text show command (Tj), whereas 2-byte characters use the + # use XObject command (Do). If using Type 42 fonts, all of + # this complication is avoided, but of course, those fonts can + # not be subsetted. + if ismath: return self.draw_mathtext(gc, x, y, s, prop, angle) self.check_gc(gc, gc._rgb) + fontsize = prop.get_size_in_points() + if rcParams['pdf.use14corefonts']: font = self._get_font_afm(prop) l, b, w, h = font.get_str_bbox(s) - fontsize = prop.get_size_in_points() y -= b * fontsize / 1000 else: font = self._get_font_ttf(prop) @@ -1243,18 +1315,112 @@ font.set_text(s, 0.0, flags=LOAD_NO_HINTING) y += font.get_descent() / 64.0 - self.file.output(Op.begin_text, - self.file.fontName(prop), - prop.get_size_in_points(), - Op.selectfont) + def check_simple_method(s): + """Determine if we should use the simple or woven method + to output this text, and chunks the string into 1-bit and + 2-bit sections if necessary.""" + use_simple_method = True + chunks = [] + if rcParams['pdf.fonttype'] == 3: + if not isinstance(s, str) and len(s) != 0: + # Break the string into chunks where each chunk is either + # a string of chars <= 255, or a single character > 255. + s = unicode(s) + for c in s: + if ord(c) <= 255: + char_type = 1 + else: + char_type = 2 + if len(chunks) and chunks[-1][0] == char_type: + chunks[-1][1].append(c) + else: + chunks.append((char_type, [c])) + use_simple_method = (len(chunks) == 1 + and chunks[-1][0] == 1) + return use_simple_method, chunks - self._setup_textpos(x, y, angle) - - self.file.output(self.encode_string(s), Op.show, Op.end_text) + def draw_text_simple(): + """Outputs text using the simple method.""" + self.file.output(Op.begin_text, + self.file.fontName(prop), + prop.get_size_in_points(), + Op.selectfont) + self._setup_textpos(x, y, angle) + self.file.output(self.encode_string(s), Op.show, Op.end_text) + + def draw_text_woven(chunks): + """Outputs text using the woven method, alternating + between chunks of 1-byte characters and 2-byte characters. + Only used for Type 3 fonts.""" + chunks = [(a, ''.join(b)) for a, b in chunks] + cmap = font.get_charmap() + # Do the rotation and global translation as a single matrix + # concatenation up front + self.file.output(Op.gsave) + a = angle / 180.0 * pi + self.file.output(cos(a), sin(a), -sin(a), cos(a), x, y, + Op.concat_matrix) + + # Output all the 1-byte characters in a BT/ET group, then + # output all the 2-byte characters. + for mode in (1, 2): + newx = oldx = 0 + # Output a 1-byte character chunk + if mode == 1: + self.file.output(Op.begin_text, + self.file.fontName(prop), + prop.get_size_in_points(), + Op.selectfont) + + for chunk_type, chunk in chunks: + if mode == 1 and chunk_type == 1: + self._setup_textpos(newx, 0, 0, oldx, 0, 0) + self.file.output(self.encode_string(chunk), Op.show) + oldx = newx + + lastgind = None + for c in chunk: + ccode = ord(c) + gind = cmap.get(ccode) + if gind is not None: + if mode == 2 and chunk_type == 2: + glyph_name = font.get_glyph_name(gind) + self.file.output(Op.gsave) + self.file.output(0.001 * fontsize, 0, + 0, 0.001 * fontsize, + newx, 0, Op.concat_matrix) + name = self.file._get_xobject_symbol_name( + font.fname, glyph_name) + self.file.output(Name(name), Op.use_xobject) + self.file.output(Op.grestore) + + # Move the pointer based on the character width + # and kerning + glyph = font.load_char(ccode, flags=LOAD_NO_HINTING) + if lastgind is not None: + kern = font.get_kerning( + lastgind, gind, KERNING_UNFITTED) + else: + kern = 0 + lastgind = gind + newx += kern/64.0 + glyph.linearHoriAdvance/65536.0 + + if mode == 1: + self.file.output(Op.end_text) + + self.file.output(Op.grestore) + + use_simple_method, chunks = check_simple_method(s) + if use_simple_method: + return draw_text_simple() + else: + return draw_text_woven(chunks) + def get_text_width_height(self, s, prop, ismath): - if isinstance(s, unicode): - s = s.encode('cp1252', 'replace') + # FT2Font can handle unicode, and so can we + # if isinstance(s, unicode): + # s = s.encode('cp1252', 'replace') if ismath: w, h, pswriter, used_characters = math_parse_s_pdf(s, 72, prop, 0) Modified: trunk/matplotlib/lib/matplotlib/mathtext.py =================================================================== --- trunk/matplotlib/lib/matplotlib/mathtext.py 2007-08-09 09:02:13 UTC (rev 3689) +++ trunk/matplotlib/lib/matplotlib/mathtext.py 2007-08-09 14:23:46 UTC (rev 3690) @@ -301,8 +301,9 @@ def render_glyph(self, ox, oy, info): filename = info.font.fname oy = self.height - oy + info.offset - - self.pswriter.append(('glyph', ox, oy, filename, info.fontsize, info.num)) + self.pswriter.append( + ('glyph', ox, oy, filename, info.fontsize, + info.num, info.symbol_name)) def render_rect_filled(self, x1, y1, x2, y2): self.pswriter.append(('rect', x1, self.height - y2, x2 - x1, y2 - y1)) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |