# -*- coding: ISO-8859-1 -*- # # # Copyright (C) 2005-2006 Jörg Lehmann # Copyright (C) 2005-2006 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # # PyX 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 2 of the License, or # (at your option) any later version. # # PyX 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 PyX; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import cStringIO, copy, time, math import bbox, style, version, type1font, unit, trafo try: enumerate([]) except NameError: # fallback implementation for Python 2.2 and below def enumerate(list): return zip(xrange(len(list)), list) try: dict([]) except NameError: # fallback implementation for Python 2.1 def dict(list): result = {} for key, value in list: result[key] = value return result class PSregistry: def __init__(self): # in order to keep a consistent order of the registered resources we # not only store them in a hash but also keep an ordered list (up to a # possible merging of resources, in which case the first instance is # kept) self.resourceshash = {} self.resourceslist = [] def add(self, resource): rkey = (resource.type, resource.id) if self.resourceshash.has_key(rkey): self.resourceshash[rkey].merge(resource) else: self.resourceshash[rkey] = resource self.resourceslist.append(resource) def mergeregistry(self, registry): for resource in registry.resources: self.add(resource) def output(self, file, writer): """ write all PostScript code of the prolog resources """ for resource in self.resourceslist: resource.output(file, writer, self) # # Abstract base class # class PSresource: """ a PostScript resource """ def __init__(self, type, id): # Every PSresource has to have a type and a unique id. # Resources with the same type and id will be merged # when they are registered in the PSregistry self.type = type self.id = id def merge(self, other): """ merge self with other, which has to be a resource of the same type and with the same id""" pass def output(self, file, writer, registry): raise NotImplementedError("output not implemented for %s" % repr(self)) # # Different variants of prolog items # class PSdefinition(PSresource): """ PostScript function definition included in the prolog """ def __init__(self, id, body): self.type = "definition" self.id = id self.body = body def output(self, file, writer, registry): file.write("%%%%BeginRessource: %s\n" % self.id) file.write("%(body)s /%(id)s exch def\n" % self.__dict__) file.write("%%EndRessource\n") class PSfont: def __init__(self, font, chars, registry): if font.filename: registry.add(PSfontfile(font.basefontname, font.filename, font.encoding, chars)) if font.encoding and font.slant: assert font.encname # do first the reencoding and then the slanting: enc_basename, enc_finalname = font.basefontname, font.encname slt_basename, slt_finalname = tfont.encname, font.name elif font.encoding: enc_basename, enc_finalname = font.basefontname, font.name elif font.slant: slt_basename, slt_finalname = font.basefontname, font.name if font.encoding: registry.add(_ReEncodeFont) registry.add(PSfontencoding(font.encoding)) registry.add(PSfontreencoding(enc_finalname, enc_basename, font.encoding.name)) if font.slant: # we need the current fontmatrix in order to manipulate it: # for this we need to re-read the fontfile as below in # PSfontfile.ouput: # XXX Is there a better way to do this? t = trafo.trafo_pt(matrix=((1, font.slant), (0, 1))) if font.filename: # for the builtin fonts, we assume a trivial fontmatrix import font.t1font as t1fontmodule t1font = t1fontmodule.T1pfbfont(font.filename) m = t1font.fontmatrixpattern.search(t1font.data1) m11, m12, m21, m22, v1, v2 = map(float, m.groups()[:6]) t *= trafo.trafo_pt(matrix=((m11, m12), (m21, m22)), vector=(v1, v2)) else: raise NotImplementedError( "cannot slant unembedded fonts -- try to include \"download35.map\" in fontmaps") registry.add(PSfontslanting(slt_finalname, slt_basename, t.__str__())) class PSfontfile(PSresource): """ PostScript font definition included in the prolog """ def __init__(self, name, filename, encoding, chars): """ include type 1 font defined by the following parameters - name: name of the PostScript font - filename: name (without path) of file containing the font definition - encfilename: name (without path) of file containing used encoding of font or None (if no encoding file used) - chars: character list to fill usedchars """ # Note that here we only need the encoding for selecting the used glyphs! self.type = "fontfile" self.id = self.name = name self.filename = filename if encoding is None: self.encodingfilename = None else: self.encodingfilename = encoding.filename self.usedchars = {} for char in chars: self.usedchars[char] = 1 self.strip = 1 def merge(self, other): if self.encodingfilename == other.encodingfilename: self.usedchars.update(other.usedchars) else: # TODO: need to resolve the encoding when several encodings are in the play self.strip = 0 def output(self, file, writer, registry): import font.t1font font = font.t1font.T1pfbfont(self.filename) file.write("%%%%BeginFont: %s\n" % self.name) # file.write("%%Included glyphs: %s\n" % " ".join(usedglyphs)) if self.strip: # XXX: access to the encoding file if self.encodingfilename: encodingfile = type1font.encodingfile(self.encodingfilename, self.encodingfilename) usedglyphs = dict([(encodingfile.decode(char)[1:], 1) for char in self.usedchars.keys()]) else: font._encoding() usedglyphs = dict([(font.encoding.decode(char), 1) for char in self.usedchars.keys()]) strippedfont = font.getstrippedfont(usedglyphs) else: strippedfont = font strippedfont.outputPS(file, writer) file.write("\n%%EndFont\n") class PSfontencoding(PSresource): """ PostScript font encoding vector included in the prolog """ def __init__(self, encoding): """ include font encoding vector specified by encoding """ self.type = "fontencoding" self.id = encoding.name self.encoding = encoding def output(self, file, writer, registry): encodingfile = type1font.encodingfile(self.encoding.name, self.encoding.filename) encodingfile.outputPS(file, writer) class PSfontslanting(PSresource): """ PostScript font slanting directive included in the prolog """ def __init__(self, fontname, basefontname, matrixstring): """ include transformed font directive specified by - fontname: PostScript FontName of the new slanted font - basefontname: PostScript FontName of the original font - slant: the value of slanting """ self.type = "fontslanting" self.id = self.fontname = fontname self.basefontname = basefontname self.matrixstring = matrixstring def output(self, file, writer, registry): file.write("%%%%BeginProcSet: %s\n" % self.fontname) file.write("/%s findfont\n" % self.basefontname) file.write("dup length dict begin\n") file.write("{ 1 index /FID ne {def} {pop pop} ifelse } forall\n") file.write("/FontMatrix %s readonly def\n" % self.matrixstring) file.write("currentdict\n") file.write("end\n") file.write("/%s exch definefont pop\n" % self.fontname) file.write("%%EndProcSet\n") class PSfontreencoding(PSresource): """ PostScript font re-encoding directive included in the prolog """ def __init__(self, fontname, basefontname, encodingname): """ include font re-encoding directive specified by - fontname: PostScript FontName of the new reencoded font - basefontname: PostScript FontName of the original font - encname: name of the encoding Before being able to reencode a font, you have to include the encoding via a fontencoding prolog item with name=encname """ self.type = "fontreencoding" self.id = self.fontname = fontname self.basefontname = basefontname self.encodingname = encodingname def output(self, file, writer, registry): file.write("%%%%BeginProcSet: %s\n" % self.fontname) file.write("/%s /%s %s ReEncodeFont\n" % (self.basefontname, self.fontname, self.encodingname)) file.write("%%EndProcSet\n") _ReEncodeFont = PSdefinition("ReEncodeFont", """{ 5 dict begin /newencoding exch def /newfontname exch def /basefontname exch def /basefontdict basefontname findfont def /newfontdict basefontdict maxlength dict def basefontdict { exch dup dup /FID ne exch /Encoding ne and { exch newfontdict 3 1 roll put } { pop pop } ifelse } forall newfontdict /FontName newfontname put newfontdict /Encoding newencoding put newfontname newfontdict definefont pop end }""") class epswriter: def __init__(self, document, file): if len(document.pages) != 1: raise ValueError("EPS file can be constructed out of a single page document only") page = document.pages[0] canvas = page.canvas try: file.write("") except: filename = file if not filename.endswith(".eps"): filename += ".eps" try: file = open(filename, "w") except IOError: raise IOError("cannot open output file") else: filename = "stream" pagefile = cStringIO.StringIO() registry = PSregistry() acontext = context() pagebbox = bbox.empty() page.processPS(pagefile, self, acontext, registry, pagebbox) file.write("%!PS-Adobe-3.0 EPSF-3.0\n") if pagebbox: file.write("%%%%BoundingBox: %d %d %d %d\n" % pagebbox.lowrestuple_pt()) file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % pagebbox.highrestuple_pt()) file.write("%%%%Creator: PyX %s\n" % version.version) file.write("%%%%Title: %s\n" % filename) file.write("%%%%CreationDate: %s\n" % time.asctime(time.localtime(time.time()))) file.write("%%EndComments\n") file.write("%%BeginProlog\n") registry.output(file, self) file.write("%%EndProlog\n") file.write(pagefile.getvalue()) pagefile.close() file.write("showpage\n") file.write("%%Trailer\n") file.write("%%EOF\n") class pswriter: def __init__(self, document, file, writebbox=0): try: file.write("") except: filename = file if not filename.endswith(".ps"): filename += ".ps" try: file = open(filename, "w") except IOError: raise IOError("cannot open output file") else: filename = "stream" # We first have to process the content of the pages, writing them into the stream pagesfile # Doing so, we fill the registry and also calculate the page bounding boxes, which are # stored in page._bbox for every page pagesfile = cStringIO.StringIO() registry = PSregistry() # calculated bounding boxes of the whole document documentbbox = bbox.empty() for nr, page in enumerate(document.pages): # process contents of page pagefile = cStringIO.StringIO() acontext = context() pagebbox = bbox.empty() page.processPS(pagefile, self, acontext, registry, pagebbox) documentbbox += pagebbox pagesfile.write("%%%%Page: %s %d\n" % (page.pagename is None and str(nr+1) or page.pagename, nr+1)) if page.paperformat: pagesfile.write("%%%%PageMedia: %s\n" % page.paperformat.name) pagesfile.write("%%%%PageOrientation: %s\n" % (page.rotated and "Landscape" or "Portrait")) if pagebbox and writebbox: pagesfile.write("%%%%PageBoundingBox: %d %d %d %d\n" % pagebbox.lowrestuple_pt()) # page setup section pagesfile.write("%%BeginPageSetup\n") pagesfile.write("/pgsave save def\n") pagesfile.write("%%EndPageSetup\n") pagesfile.write(pagefile.getvalue()) pagefile.close() pagesfile.write("pgsave restore\n") pagesfile.write("showpage\n") pagesfile.write("%%PageTrailer\n") file.write("%!PS-Adobe-3.0\n") if documentbbox and writebbox: file.write("%%%%BoundingBox: %d %d %d %d\n" % documentbbox.lowrestuple_pt()) file.write("%%%%HiResBoundingBox: %g %g %g %g\n" % documentbbox.highrestuple_pt()) file.write("%%%%Creator: PyX %s\n" % version.version) file.write("%%%%Title: %s\n" % filename) file.write("%%%%CreationDate: %s\n" % time.asctime(time.localtime(time.time()))) # required paper formats paperformats = {} for page in document.pages: if page.paperformat: paperformats[page.paperformat] = page.paperformat first = 1 for paperformat in paperformats.values(): if first: file.write("%%DocumentMedia: ") first = 0 else: file.write("%%+ ") file.write("%s %d %d 75 white ()\n" % (paperformat.name, unit.topt(paperformat.width), unit.topt(paperformat.height))) # file.write(%%DocumentNeededResources: ") # register not downloaded fonts here file.write("%%%%Pages: %d\n" % len(document.pages)) file.write("%%PageOrder: Ascend\n") file.write("%%EndComments\n") # document defaults section #file.write("%%BeginDefaults\n") #file.write("%%EndDefaults\n") # document prolog section file.write("%%BeginProlog\n") registry.output(file, self) file.write("%%EndProlog\n") # document setup section #file.write("%%BeginSetup\n") #file.write("%%EndSetup\n") file.write(pagesfile.getvalue()) pagesfile.close() file.write("%%Trailer\n") file.write("%%EOF\n") class context: def __init__(self): self.linewidth_pt = None self.colorspace = None self.font = None def __call__(self, **kwargs): newcontext = copy.copy(self) for key, value in kwargs.items(): setattr(newcontext, key, value) return newcontext