PSStream.py :  » Network » Grail-Internet-Browser » grail-0.6 » printing » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Network » Grail Internet Browser 
Grail Internet Browser » grail 0.6 » printing » PSStream.py
__version__ = '$Revision: 1.6 $'

import fonts                            # a package
import utils                            # || module
import os
import regsub
import settings
import string
import sys
import time
import urlparse

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

from types import StringType,TupleType


RECT_DEBUG = 0

SYSTEM_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")


# Load the PostScript prologue:
def get_systemheader():
    options = settings.get_settings()
    fn = utils.which(
        "header.ps", list(options.user_data_dirs) + [SYSTEM_DATA_DIR])
    if fn:
        return open(fn).read()
    return "%%\%%  System header %s not found!\n%%" % fn


USERHEADER_INFO = """\
%%
%% This is custom header material was loaded from:
%%      %s
%%"""

# Allow the user to provide supplemental prologue material:
def get_userheader():
    options = settings.get_settings()
    templates = []
    for fn in settings.get_settings().user_headers:
        filename = utils.which(fn, options.user_data_dirs)
        if filename:
            templates.append(USERHEADER_INFO % fn)
            templates.append(open(filename).read())
    return string.join(templates, '\n')


# Regular expressions.
L_PAREN = '('
R_PAREN = ')'
B_SLASH = '\\\\'
QUOTE_re = '\\(%c\\|%c\\|%s\\)' % (L_PAREN, R_PAREN, B_SLASH)

def cook(string):
    return regsub.gsub(QUOTE_re, '\\\\\\1', string)


# Keep images that come above the ascenders for the current line
# from clobbering the descenders of the line above by allowing the
# font height * PROTECT_DESCENDERS_MULTIPLIER.  This should be a
# reasonable percentage of the font height.
#
PROTECT_DESCENDERS_MULTIPLIER = 0.20


ALIGN_LEFT = 'left'
ALIGN_CENTER = 'center'
ALIGN_RIGHT = 'right'

# horizontal rule spacing, in points
HR_TOP_MARGIN = 4.0
HR_BOT_MARGIN = 2.0

# paragraph rendering
PARAGRAPH_SEPARATION = 1.0              # * base-font-size

# distance after a label tag in points
LABEL_TAB = 6.0


class PSStream:
    _margin = 0.0
    _rmargin = 0.0
    _leading = 0.0                      # "external" leading == between lines
    _align = ALIGN_LEFT
    _inliteral_p = None
    _render = 'S'                       # S == normal string, U == underline

    # current line state; we really need some good comments about these....
    _space_width = 0.0
    _baseline = None
    _descender = 0.0
    _xpos = 0.0
    _ypos = 0.0
    _vtab = _leading                    # extra vertical tab before the line
    _lineshift = 0.0                    # adjustment at start of line

    def __init__(self, psfont, ofp, title='', url='', paper=None):
        self._paper = paper
        self._font = psfont
        self._ofp = ofp
        self.set_title(title)
        # strip any fragment identifiers from the url, and pre-cook:
        self.set_url(url)
        # current line state
        self._linestr = []
        self._yshift = [(0.0, 0.0)]     # vertical baseline shift w/in line
        self._linefp = StringIO()
        self._base_font_size = self.get_fontsize()
        self._line_start_font = self._font.get_font()

    __pageno = 1
    def get_pageno(self):
        return self.__pageno

    def set_pageno(self, pageno):
        utils.debug("========== new page number: %s\n" % pageno)
        self.__pageno = pageno

    __titles = None
    def set_title(self, title):
        # replace all whitespace sequences with a single space
        title = string.join(string.split(title))
        if self.__titles is None:
            self.__titles = [title]
        else:
            self.__titles.append(title)

    def get_title(self):
        return self.__titles[0]

    def prune_titles(self):
        del self.__titles[:-1]

    def set_url(self, url):
        parsed = urlparse.urlparse(url)[:3] + ('', '', '')
        self._url_cooked = cook(urlparse.urlunparse(parsed))

    def start(self):
        # print document preamble
        oldstdout = sys.stdout
        try:
            sys.stdout = self._ofp
            print "%!PS-Adobe-1.0"
            if self.get_title():
                print "%%Title:", self.get_title()
            # output font prolog
            print "%%DocumentPaperSizes:", self._paper.PaperName
            print "%%DocumentFonts: Symbol ZapfDingbats",
            docfonts = self._font.docfonts
            for dfv in docfonts.values(): print dfv,
            print
            # spew out the contents of the header PostScript file
            print get_systemheader()
            # define the fonts
            print "/scalfac", self._font.points_per_pixel, "D"
            for docfont in docfonts.keys():
                print "/%s /%s dup reencodeISO D findfont D" \
                      % (docfont, docfonts[docfont])
            # finish out the prolog with paper information:
            for name, value in vars(self._paper).items():
                if type(value) is type(''):
                    print "/Gr%s (%s) D" % (name, value)
                else:
                    print "/Gr%s %s D" % (name, value)
            # Add time information to allow the printing functions to include
            # 'date printed' to the footers if desired.  We need a way to get
            # the last-modified time of the document from the context headers,
            # but these are not available to us at this point.
            print "%%\n%% time values for use by page decorating functions:"
            names = ("Year", "Month", "Day", "Hour", "Minute", "Second",
                     "Weekday", "Julian", "DST")
            t = time.time()
            local = time.localtime(t)
            utc = time.gmtime(t)
            for name, local, utc in map(None, names, local, utc):
                print "/Gr%s %s D /GrUTC%s %s D" % (name, local, name, utc)
            # add per-user customization:
            user_template = get_userheader()
            if user_template:
                print user_template
            print "%%EndProlog"
        finally:
            sys.stdout = oldstdout
        self.print_page_preamble()
        self.push_font_change(None)     # ??? why ???

    def get_fontsize(self):
        return self._font.font_size()

    def get_pageheight(self):
        return self._paper.ImageHeight

    def get_pagewidth(self):
        return self._paper.ImageWidth - self._margin - self._rmargin

    def set_leading(self, value):
        # `value' is the "internal" leading: for '10 on 12', pass in 12;
        # the stored value is the "external" leading.  This is only computed
        # here.  The external leading is restricted to non-negative values
        # to ensure at least some level of sanity.
        self._leading = max(0.0, value - self._base_font_size)

    def push_eps(self, img, align=None):
        """Insert encapsulated postscript in stream."""
        if self._linestr:
            self.close_string()
        if align not in ('absmiddle', 'baseline', 'middle', 'texttop', 'top'):
            align = 'bottom'

        # constrain image size to fit on page:
        width, height = img.get_size()
        xscaling, yscaling = settings.get_settings().get_scaling()
        if xscaling:
            img.restrict(height=height * xscaling)
        if yscaling:
            img.restrict(width=width * yscaling)
        pagewidth = self.get_pagewidth()
        img.restrict(width=pagewidth, height=self.get_pageheight())

        extra = PROTECT_DESCENDERS_MULTIPLIER * self.get_fontsize()
        above_portion, below_portion, vshift = 0.5, 0.5, 0.0
        if align == 'absmiddle':
            vshift = self.get_fontsize() / 2.0
        elif align in ('bottom', 'baseline'):
            above_portion, below_portion = 1.0, 0.0
        elif align != 'middle':
            # ALIGN in 'top', 'texttop'
            above_portion, below_portion, extra = 0.0, 1.0, 0.0
            vshift = self.get_fontsize()

        width, height = img.get_size()
        above = above_portion * height
        below = (below_portion * height) - vshift

        # Check space available:
        if width > (pagewidth - self._xpos):
            self.close_line()
        # Update page availability info:
        imgshift = above + self._yshift[-1][0] + vshift + extra
        if self._baseline is None:
            self._baseline = imgshift
        else:
            self._baseline = max(self._baseline, imgshift)
        self._descender = max(self._descender, below - self._yshift[-1][0])
        self._xpos = self._xpos + width
        ll_x, ll_y, ur_x, ur_y = img.bbox
        #
        xscale, yscale = img.get_scale()
        oldstdout = sys.stdout
        try:
            sys.stdout = self._linefp
            # Translate & scale for image origin (maybe should add
            # some cropping?  just assuming image is reasonable):
            print 'gsave\n currentpoint %s sub translate %s %s scale' \
                  % (below, xscale, yscale)
            if ll_x or ll_y:
                #  Have to translate again to make image happy:
                print ' %d %d translate' % (-ll_x, -ll_y)
            if img.data[-1] == '\n':
                img.data = img.data[:-1]
            print img.data
            #  Restore context, move to right of image:
            print 'grestore %s 0 R' % width
        finally:
            sys.stdout = oldstdout

    def push_font_string(self, s, font):
        if not font:
            self.push_string_flowing(s)
            return
        if self._linestr:
            self.close_string()
        if not self._font.fontobjs.has_key(font):
            self._font.fontobjs[font] = fonts.font_from_name(font)
        fontobj = self._font.fontobjs[font]
        size = self.get_fontsize()
        width = fontobj.text_width(size, s)
        if self._xpos + width > self.get_pagewidth():
            self.close_line()
        if self._baseline is None:
            self._baseline = size
        else:
            self._baseline = max(self._baseline, size)
        self._linefp.write('gsave\n /%s findfont %d scalefont setfont '
                           % (font, size))
        self._linefp.write('(%s) show\ngrestore %d 0 R\n' % (cook(s), width))
        self._xpos = self._xpos + width

    def push_alignment(self, align):
        if align == 'right':
            self._align = ALIGN_RIGHT
        elif align == 'center':
            self._align = ALIGN_CENTER
        else:
            self._align = ALIGN_LEFT

    def push_yshift(self, yshift):
        """Adjust the current baseline relative to the real baseline.

        The `yshift' parameter is a float value specifying the adjustment
        relative to the current virtual baseline.  Use pop_yshift() to
        undo the effects of the adjustment.
        """
        if self._linestr:
            self.close_string()
        yshift = 1.0 * yshift
        self._linefp.write('0 %s R\n' % yshift)
        absshift = self._yshift[-1][0] + yshift
        self._yshift.append((absshift, yshift))
        newheight = absshift + self.get_fontsize()
        if self._baseline is None:
            self._baseline = max(0.0, newheight)
        else:
            self._baseline = max(self._baseline, newheight)
        if absshift < 0.0:
            if self._descender is None:
                self._descender = -absshift
            else:
                self._descender = max(self._descender, -absshift)

    def pop_yshift(self):
        if self._linestr:
            self.close_string()
        self._linefp.write('0 %s R\n' % -self._yshift[-1][1])
        del self._yshift[-1]

    def push_end(self):
        self.close_line()
        self.print_page_postamble()
        oldstdout = sys.stdout
        try:
            sys.stdout = self._ofp
            print "%%Trailer"
            print "%%Pages:", self.get_pageno()
            print "%%EOF"
        finally:
            sys.stdout = oldstdout

    def push_font_change(self, font):
        if self._linestr:
            self.close_string()
        if self._baseline is None and self._xpos != 0.0:
            self._baseline = self.get_fontsize() \
                             + max(0.0, self._yshift[-1][0])
        psfontname, size = self._font.set_font(font)
        self._linefp.write('%s %s SF\n' % (psfontname, size))
        self._space_width = self._font.text_width(' ')
        newfontsize = size + max(0.0, self._yshift[-1][0])
        if self._baseline is None:
            self._baseline = newfontsize
        else:
            self._baseline = max(self._baseline, newfontsize)

    def push_space(self, spaces=1):
        # spaces at the beginning of a line are thrown away, unless we
        # are in literal text.
        if self._inliteral_p or self._xpos > 0.0:
            self._linestr.append(' ' * spaces)
            self._xpos = self._xpos + self._space_width * spaces

    def push_horiz_rule(self, abswidth=None, percentwidth=None,
                        height=None, align=None):
        if type(height) is type(0):
            height = 0.5 * max(height, 1)       # each unit is 0.5pts
        else:
            height = 1                          # 2 "units"
        old_align = self._align
        if align is not None:
            self.push_alignment(align)
        self._baseline = HR_TOP_MARGIN + height
        descent = PROTECT_DESCENDERS_MULTIPLIER * self.get_fontsize()
        self._vtab = max(self._vtab, descent)
        self._descender = HR_BOT_MARGIN
        pagewidth = self.get_pagewidth()
        if abswidth:
            width = min(1.0 * abswidth, pagewidth)
        elif percentwidth:
            width = min(1.0, percentwidth) * pagewidth
        else:
            width = pagewidth
        if self._align is ALIGN_LEFT:
            start = 0.0
        elif self._align is ALIGN_CENTER:
            start = (pagewidth - width) / 2
        else:   #  ALIGN = right
            start = pagewidth - width
        self._linefp.write('%d %s %s HR\n'
                           % (height, start + self._margin, width))
        self.close_line()
        self._align = old_align
        self._xpos = 0.0

    def push_horiz_space(self, width):
        # `width' is in points
        if self._linestr:
            self.close_string()
        max_width = self.get_pagewidth()
        if self._xpos + width > max_width:
            self.close_line()
        self._linefp.write("%s 0 R\n" % width)
        self._xpos = self._xpos + width

    def push_margin(self, level):
        if self._linestr:
            self.close_string()
        distance = level * self._paper.TabStop
        if self._margin != distance:
            self._margin = distance
            self._ofp.write('/grIndentMargin %s D CR\n' % distance)

    def push_rightmargin(self, level):
        if self._linestr:
            self.close_string()
        self._rmargin = level * self._paper.TabStop

    def push_paragraph(self, blankline, parskip):
        if blankline and self._ypos:
            self._vtab = max(self._vtab, (self._base_font_size * parskip))

    def push_label(self, bullet):
        if self._linestr:
            self.close_string()
        if type(bullet) is StringType:
            #  Simple textual bullet:
            distance = self._font.text_width(bullet) + LABEL_TAB
            self._linefp.write('gsave CR -%s 0 R (%s) S grestore\n' %
                               (distance, cook(bullet)))
        elif type(bullet) is TupleType:
            #  Font-based dingbats:
            string, font = bullet
            self._linefp.write('gsave\n CR %s %d SF\n'
                               % (font, self.get_fontsize()))
            self._linefp.write(' (%s) dup\n' % cook(string))
            self._linefp.write(' stringwidth pop -%s E sub 0 R S\ngrestore\n'
                               % LABEL_TAB)
        else:
            #  This had better be an EPSImage object!
            max_width = self._paper.TabStop - LABEL_TAB
            bullet.restrict(height=0.9 * self.get_fontsize())
            width, height = bullet.get_size()
            distance = width + LABEL_TAB
            xscale, yscale = bullet.get_scale()
            #  Locate new origin:
            vshift = (self.get_fontsize() - height) / 2.0
            self._linefp.write("gsave\n CR -%s %s R currentpoint translate "
                               "%s %s scale\n"
                               % (distance, vshift, xscale, yscale))
            ll_x, ll_y, ur_x, ur_y = bullet.bbox
            if ll_x or ll_y:
                #  Have to translate again to make image happy:
                self._linefp.write(' %d %d translate\n' % (-ll_x, -ll_y))
            self._linefp.write(bullet.data)
            self._linefp.write("grestore\n")

    def push_hard_newline(self, blanklines=1):
        self.close_line()

    def push_underline(self, flag):
        render = flag and 'U' or 'S'
        if self._render <> render and self._linestr:
            self.close_string()
        self._render = render

    def push_literal(self, flag):
        if self._inliteral_p <> flag and self._linestr:
            self.close_string()
        self._inliteral_p = flag

    def push_string_flowing(self, data):
        allowed_width = self.get_pagewidth()
        # special case getting it on one line:
        tw = self._font.text_width(data)
        if tw <= (allowed_width - self._xpos):
            self._linestr.append(data)
            self._xpos = self._xpos + tw
            return
        # local variable cache
        text_width = self._font.text_width
        linestr = self._linestr
        append = linestr.append
        xpos = self._xpos
        # must break line; just do it by "words" as best we understand them
        words = string.splitfields(data, ' ')
        wordcnt = len(words) - 1
        space_width = self._space_width
        for word, width in map(None, words, map(text_width, words)):
            # Does the word fit on the current line?
            if xpos + width < allowed_width:
                append(word)
                xpos = xpos + width
            # The current line, with the additional text, is too
            # long.  We need to figure out where to break the
            # line.  If the previous text was a space, and the
            # current line width is > 75% of the page width, and
            # the current text is smaller than the page width,
            # then just break the line at the last space.
            # (Checking the last whitespace char against a tab is
            # unnecessary; data is de-tabbed before this method is
            # called.)
            elif linestr and linestr[-1] and \
                 linestr[-1][-1] == ' ' and \
                 xpos > allowed_width * 0.75 and \
                 width < allowed_width:
                #
                # output the current line data (removes trailing space)
                #
                linestr[-1] = linestr[-1][:-1]
                self._xpos = xpos - space_width
                self.close_line(linestr=linestr)
                # close_line() touches these, but we're using a
                # local variable cache, which must be updated.
                xpos = width
                linestr = [word]
                append = linestr.append
            # Try an alternative line break strategy.  If we're
            # closer than 75% of the page width to the end of the
            # line, then start a new line, print the word,
            # possibly splitting the word if it is longer than a
            # single line.
            else:
                # only force a break immediately if it buys us something:
                if width < allowed_width:
                    self._xpos = xpos
                    self.close_line(linestr=linestr)
                    # close_line() touches these, but we're using a
                    # local variable cache, which must be updated.
                    xpos = 0.0
                    linestr = []
                    append = linestr.append
                while width > allowed_width:
                    # make our best guess as to the longest bit of
                    # the word we can write on a line.
                    if self._inliteral_p:
                        append(word)
                        self._xpos = width
                        word = ''
                    else:
                        average_charwidth = width / len(word)
                        chars_on_line = int(allowed_width
                                            / average_charwidth)
                        s = word[:chars_on_line]
                        # ugly!
                        if s and s[-1] in string.letters:
                            s = s + "-"
                        append(s)
                        self._xpos = text_width(s)
                        word = word[chars_on_line:]
                    # now write the word
                    self.close_line(linestr=linestr)
                    # close_line() touches these, but we're using a
                    # local variable cache, which must be updated.
                    xpos = 0.0
                    linestr = []
                    append = linestr.append
                    width = text_width(word)
                append(word)
                xpos = width
            # for every word but the last, put a space after it
            # inlining push_space() for speed
            if wordcnt > 0 and (self._inliteral_p or xpos > 0.0):
                append(' ')
                xpos = xpos + space_width
            wordcnt = wordcnt - 1
        # undo effects of caching variables:
        self._linestr = linestr
        self._xpos = xpos

    def push_string(self, data):
        lines = string.splitfields(data, '\n')
        linecnt = len(lines) - 1
        for line in lines:
            # do flowing text
            self.push_string_flowing(line)
            # for every line but the last, put a hard newline after it
            if linecnt:
                self.push_hard_newline()
            linecnt = linecnt - 1

    def print_page_preamble(self):
        oldstdout = sys.stdout
        try:
            sys.stdout = self._ofp
            # write the structure page convention
            pageno = self.get_pageno()
            print '%%Page:', pageno, pageno
            print '%%BeginPageProlog'
            psfontname, size = self._line_start_font
            print "save", self._margin, psfontname, size, pageno, "NP"
            print '%%EndPageProlog'
            if RECT_DEBUG:
                print 'gsave', 0, 0, "M"
                print self._paper.ImageWidth, 0, "RL"
                print 0, -self._paper.ImageHeight, "RL"
                print -self._paper.ImageWidth, 0, "RL closepath stroke newpath"
                print 'grestore'
        finally:
            sys.stdout = oldstdout

    def print_page_postamble(self):
        title = ''
        url = self._url_cooked
        if self.get_pageno() != 1:
            title = cook(self.get_title())
        self.prune_titles()
        stdout = sys.stdout
        self._ofp.write("(%s)\n(%s)\n%d EP\n"
                        % (url, title, self.get_pageno()))

    def push_page_end(self):
        # self._baseline could be None
        linesz = (self._baseline or 0.0) + self._descender + self._vtab
        self._ypos = self._ypos - linesz
        self.print_page_postamble()
        return linesz

    def push_page_start(self, linesz):
        self.print_page_preamble()
        self._ypos = -linesz
        self._vtab = self._leading

    def push_page_break(self):
        linesz = self.push_page_end()
        self.set_pageno(self.get_pageno() + 1)
        self.push_page_start(linesz)

    def close_line(self, linestr=None):
        if linestr is None:
            linestr = self._linestr
        if linestr:
            self.close_string(linestr)
        baseline = self._baseline
        yshift = self._yshift[-1][0]
        if baseline is None:
            baseline = self.get_fontsize() + max(yshift, 0.0)
            self._baseline = baseline
        if not self._linefp.getvalue():
            if self._ypos:
                self._vtab = self._vtab + baseline
            return
        # do we need to break the page?
        # will the line we're about to write fit on the current page?
        linesz = self._baseline + self._descender + self._vtab
##      utils.debug('ypos= %f, linesz= %f, diff= %f, PH= %f' %
##                  (self._ypos, linesz, (self._ypos - linesz),
##                   -self._paper.ImageHeight))
        self._ypos = self._ypos - linesz
        if self._ypos <= -self._paper.ImageHeight:
            self.push_page_break()
        distance = baseline + self._vtab
        if self._align == ALIGN_CENTER:
            offset = (self.get_pagewidth() - self._xpos) / 2
        elif self._align == ALIGN_RIGHT:
            offset = self.get_pagewidth() - self._xpos
        else:
            offset = 0.0
        self._ofp.write('CR %s -%s R\n%s' %
                        (offset, distance, self._linefp.getvalue()))
        if self._descender > 0:
            self._ofp.write('0 -%s R\n' % self._descender)
            self._descender = 0.0
        # reset cache
        self._line_start_font = self._font.get_font()
        self._linefp = StringIO()
        self._lineshift = yshift
        self._xpos = 0.0
        self._vtab = self._leading
        self._baseline = None

    _prev_render = _render
    def close_string(self, linestr=None):
        if linestr is None:
            linestr = self._linestr
        # handle quoted characters
        cooked = cook(string.joinfields(linestr, ''))
        if not cooked:
            return
        # TBD: handle ISO encodings
        #pass
        render = self._render
        # This only works if 'S' and 'U' are the only values for render:
        if self._prev_render != render and cooked[0] == ' ':
            cooked = cooked[1:]
            self._linefp.write('( ) S\n')
        self._linefp.write('(%s) %s\n' % (cooked, render))
        self._prev_render = render
        self._linestr = []
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.