# Copyright (C) 2003-2006 Alexei Gilchrist and Paul Cochrane # # This program 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. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # $Id: presentation.py,v 1.39 2006/04/24 14:07:10 paultcochrane Exp $ ''' PyScript presentation library (posters and talks) There are some common useful component classes such as TeXBox and Box_1, followed by Poster and Talk classes ''' __revision__ = '$Revision: 1.39 $' from pyscript.defaults import defaults from pyscript import Color, Group, Epsf, Area, P, Align, Rectangle, TeX, \ Page, Distribute, Text, Pages, VAlign, Path from pyscript.render import render import os, types class TeXBox(Group): ''' Typeset some LaTeX within a fixed width box. @ivar fixed_width: the width of the box @type fixed_width: float @ivar tex_scale: The amount by which to scale the TeX @type tex_scale: float @ivar align: alignment of the LaTeX to box if it is smaller @type align: anchor string ''' def __init__(self, text, fixed_width=9.4, tex_scale=0.7, align="w", fg = Color(0), text_style="", **options): Group.__init__(self, **options) self.text = text self.fixed_width = fixed_width self.tex_scale = tex_scale self.fg = fg self.align = align self.text_style = text_style def set_fg(self, fg): """ Set the foreground colour """ self.fg = fg def set_fixed_width(self, fixed_width): """ Set the fixed width attribute """ self.fixed_width = fixed_width def set_tex_scale(self, tex_scale): """ Set the scale of TeX objects """ self.tex_scale = tex_scale def set_align(self, align): """ Set the anchor point where to align objects """ self.align = align def set_text_style(self, text_style): """ Set the text style (in LaTeX) """ self.text_style = text_style def make(self): """ Make the TeXBox """ width_pp = int(self.fixed_width/float(self.tex_scale)*defaults.units) al = Align(a1=self.align, a2=self.align, space=0) t = TeX(r'\begin{minipage}{%dpt}%s %s\end{minipage}' \ % (width_pp, self.text_style, self.text), fg=self.fg) t.scale(self.tex_scale, self.tex_scale) al.append(t) a = Area(width=self.fixed_width, height=0) al.append(a) self.append(al) return self #apply(self, (), options) # why do we do this??? class Box_1(Group): ''' A box of fixed width. Items added to it are aligned vertically and separated by a specified padding @cvar border: width of the border (in pts) @type border: int @cvar fg: color of border @type fg: L{Color} object @cvar bg: color of box background @type bg: L{Color} object @cvar fixed_width: width of box @type fixed_width: float @cvar pad: vertical padding between items @type pad: float @cvar r: corner radius @type r: float ''' bg = Color('Lavender') fg = Color(0) border = 1 fixed_width = 9.6 pad = 0.2 r = 0 def __init__(self, *items, **options): Group.__init__(self, **options) apply(self.append, items) Align(self, a1="s", a2="n", angle=180, space=self.pad) gb = self.bbox() r = Rectangle(n=gb.n+P(0, self.pad), width=self.fixed_width, height=gb.height+2*self.pad, bg=self.bg, fg=self.fg, linewidth=self.border, r=self.r, ) self.insert(0, r) class CodeBox(Group): """ A box with a 'dog-ear' to contain code fragments """ def __init__(self, text, **options): Group.__init__(self, **options) obj = TeXBox(text) obj.make() bg = Color('Orange')*1.3 fg = Color('black')*0.4 border = 0.75 #fixed_width = 2.5 pad = 0.2 dogear = 0.25 gb = obj.bbox() h = gb.height w = gb.width self.append(Path( P(-pad, -pad), P(-pad, h+pad), P(w+pad, h+pad), P(w+pad, -pad+dogear), P(w+pad-dogear, -pad), P(w+pad-dogear, -pad+dogear), P(w+pad, -pad+dogear), P(w+pad-dogear, -pad), P(-pad, -pad), bg=bg, fg=fg, linewidth=border, miterlimit=1.0) ) obj.c = self.bbox().c self.append(obj) class Poster(Page): """ A poster class. More docs forthcoming... """ def __init__(self, size, style=None): Page.__init__(self) # set stuff up self.size = size self.orientation = "Portrait" self.num_columns = 2 # set the default style settings self.title = "" self.title_fg = Color(0) self.title_scale = 1.4 self.title_width = 0.8 # as a fraction of the total poster width self.title_text_style = "\large" self.authors = "" self.authors_fg = Color(0) self.authors_scale = 1 self.authors_width = 0.8 # as a fraction of the total poster width self.authors_text_style = "" self.address = "" self.address_fg = Color(0) self.address_scale = 0.9 self.address_width = 0.8 # as a fraction of the total poster width self.address_text_style = "" self.abstract = "" self.abstract_fg = Color(0) self.abstract_scale = 0.8 self.abstract_width = 0.92 # relative to total width of poster self.abstract_text_style = "" self.gutter = 0.2 self.pad = 0 # should get set by add_column() self.item_sep = 0.3 self.bg = Color(1) self.signature_fg = Color(0) self.logo_height = 1.2 # styles for columns self.column_item_sep = 0.3 # styles for column boxes # the title's style... self.column_box_title_align = "c" self.column_box_title_tex_scale = 1.4 self.column_box_title_fixed_width = 9.4 self.column_box_title_text_style = r"" self.column_box_title_fg = Color(0) # the text styles of the column box self.column_box_text_align = "w" self.column_box_tex_scale = 0.7 self.column_box_text_width = 9.4 self.column_box_text_style = r"" self.column_box_text_fg = Color(0) # the column box styles self.column_box_item_sep = 0.1 self.column_box_width = 9.9 self.column_box_bg = Color(1) self.column_box_border = 1 # process the style option if style is not None: # make sure the file exists in either the .pyscript/styles # directory, or the current directory styleFname = style + ".py" HOME = os.path.expandvars("$HOME") if os.path.exists(HOME + "/.pyscript/styles/" + styleFname): print "Found %s in .pyscript/styles dir" % style self._read_style(HOME + "/.pyscript/styles/" + styleFname) elif os.path.exists(styleFname): print "Found %s in current dir" % style self._read_style(styleFname) else: # barf raise ValueError, "Style %s not found!" % style self.logos = [] self.columns = [] self.area = self.area() # subtract the gutter to get the printing area self.printing_area = Area( sw=self.area.sw + P(1, 1)*self.gutter, width=self.area.width - 2*self.gutter, height=self.area.height - 2*self.gutter ) def _read_style(self, styleFname): """ Read the talk style file @param styleFname: The name of the style file to process @type styleFname: string """ # slurp in the text fp = open(styleFname, "r") lines = fp.readlines() fp.close() # make one big string... styleText = "" for line in lines: styleText += line # exec the text exec(styleText) def set_title(self, title): """ Set the title to use for the poster @param title: the text of the poster title @type title: string """ self.title = title pass def set_authors(self, authors): """ Set the authors of the poster @param authors: the text of the poster authors @type authors: string """ self.authors = authors pass def set_address(self, address): """ Set the address of the institution of those presenting the poster @param address: the text of the address @type address: string """ self.address = address pass def set_abstract(self, abstract): """ Set the abstract of the poster @param abstract: the text of the poster abstract @type abstract: string """ self.abstract = abstract pass def set_size(self, size): """ Set the size of the poster. These are standard page sizes. It is a good idea to develop a poster at a size of "a4" and then for the final poster use "a0". It is also handy when at a poster session at a conference to have a4 size versions of the a0 poster to give out to people, so the a4 setting is also handy for that. @param size: the size of the poster @type size: string """ self.size = size pass def set_style(self, style): """ Set the style of the poster. This is the name of a set of predefined fonts, sizes, colours etc for the text, the columns of the poster and the poster background. @param style: the text of the name of the poster style to use @type style: string """ self.style = style pass def set_orientation(self, orientation): """ Set the orientation of the poster. Options are "portrait" or "landscape". @param orientation: the page orientation of the poster @type orientation: string """ self.orientation = orientation pass def set_num_columns(self, num_columns): """ Set the number of columns to use for the poster. Typically one would use two columns for portrait, and three for landscape. @param num_columns: the number of columns to use @type num_columns: int """ self.num_columns = num_columns pass def add_logo(self, logo, height=None): """ Add a logo to the poster. If only one logo is added to the poster, it is by default located at the top left-hand corner. The second logo is then positioned in the top right-hand corner. The third is positioned in the top middle. If you add more than that, the logos are distributed evenly across the top of the poster. @param logo: the file name of the eps file of the logo to add @type logo: text @param height: the height of the logo @type height: float """ if height is None: height = self.logo_height obj = Epsf(logo, height=height) self.logos.append(obj) def add_logos(self, *logos, **options): """ Add several logos to the poster at one time. If only one logo is added to the poster, it is by default located at the top left-hand corner. The second logo is then positioned in the top right-hand corner. The third is positioned in the top middle. If you add more than that, the logos are distributed evenly across the top of the poster. @param logos: list of file names of the eps files of the logos to add @type logos: list of strings @keyword height: the height of the logo @type height: float """ # process the options, if any if options.has_key('height'): height = options['height'] else: height = self.logo_height for logo in logos: obj = Epsf(logo, height=height) self.logos.append(obj) def add_column(self, column, side): """ Add a column of the poster. @param column: the Column object to add as the poster column @type column: Column object @param side: the side on which the column is to be on the poster. Valid values are "left", "middle" (useful for landscape only) and "right". @type side: string """ # there must be a better way to write this if statement, # something like if side is not in [left, middle, right] ??? if side != 'left' and side != 'middle' and side != 'right': errMsg = "You must specify either 'left', 'middle', or 'right'\n" errMsg += "I got: '%s'" % side raise ValueError, errMsg self.columns.append(column._make()) def _make_title(self): """ Make the title """ titlebox = TeXBox(text=self.title) titlebox.set_fg(self.title_fg) titlebox.set_fixed_width(self.printing_area.width*self.title_width) titlebox.set_tex_scale(self.title_scale) titlebox.set_align("c") titlebox.set_text_style(self.title_text_style) titlebox.make() return titlebox def _make_authors(self): """ Make the authors """ authorbox = TeXBox(self.authors) authorbox.set_fg(self.authors_fg) authorbox.set_tex_scale(self.authors_scale) authorbox.set_fixed_width(self.printing_area.width*self.authors_width) authorbox.set_align("c") authorbox.set_text_style(self.authors_text_style) authorbox.make() return authorbox def _make_address(self): """ Make the address """ addressbox = TeXBox(self.address) addressbox.set_fg(self.address_fg) addressbox.set_tex_scale(self.address_scale) addressbox.set_fixed_width(self.printing_area.width*self.address_width) addressbox.set_align("c") addressbox.set_text_style(self.address_text_style) addressbox.make() return addressbox def _make_abstract(self): """ Make the abstract """ abstractbox = TeXBox(self.abstract) abstractbox.set_fg(self.abstract_fg) abstractbox.set_tex_scale(self.abstract_scale) abstractbox.set_fixed_width( self.printing_area.width*self.abstract_width) abstractbox.set_align("c") abstractbox.set_text_style(self.abstract_text_style) abstractbox.make() return abstractbox def _make_logos(self): """ Make the logos """ logos = Align(a1="e", a2="w", angle=90, space=None) for logo in self.logos: logos.append(logo) Distribute(logos, a1="e", a2="w", p1=self.printing_area.nw, p2=self.printing_area.ne, ) return logos def _make_columns(self): """ Make the columns """ #print "Number of columns is: %d" % len(self.columns) # vertically align the columns items, but with no spacing yet #for col in self.columns: #VAlign(col, space=None) # distribute the columns horizontally if self.num_columns == 2: Distribute(Area(width=0, height=0), self.columns[0], self.columns[1], Area(width=0, height=0), p1=self.printing_area.w, p2=self.printing_area.e, a1="e", a2="w") elif self.num_columns == 3: Distribute(Area(width=0, height=0), self.columns[0], self.columns[1], self.columns[2], Area(width=0, height=0), p1=self.printing_area.w, p2=self.printing_area.e, a1="e", a2="w") else: raise ValueError, \ "Incorrect number of columns. Should be 2 or 3. I got %d" % \ self.num_columns # find the distance between two of the columns self.pad = (self.columns[1].bbox().w - self.columns[0].bbox().e)[0] # vertically align the column items #print self.pad #for col in self.columns: #VAlign(col, space=self.pad) # now align the columns themselves all_cols = Align(angle=90, space=None, a1="ne", a2="nw") for col in self.columns: #col.set_space(self.pad) col.set_space(0) #print col.get_space() all_cols.append(col._make()) return all_cols def _make_background(self): """ Make the background of the poster """ area = self.area() return Rectangle(width=area.width, height=area.height, fg=None, bg=self.bg ) def make(self, file): """ Make the poster. @param file: the file name of the poster output eps file @type file: string """ all = Align(a1="s", a2="n", angle=180, space=self.item_sep) all.append(self._make_logos()) all.append(self._make_title()) all.append(self._make_authors()) all.append(self._make_address()) all.append(self._make_abstract()) all.append(self._make_columns()) all.n = self.printing_area.n - P(0, 0.1) back = self._make_background() p = self.printing_area.se+P(0, 1.2) signature = Text( "Created with PyScript. http://pyscript.sourceforge.net", size=6, sw=p, fg=self.signature_fg).rotate(-90, p) self.append(back) self.append(all) self.append(signature) # actually generate the postscript render(self, file=file) class Column(VAlign): # I *think* this should inherit from VAlign... """ A column of a poster. Basically just a container for various boxes. More docs forthcoming... """ def __init__(self, poster): VAlign.__init__(self) #Group.__init__(self) self.boxes = [] self.space = poster.column_item_sep def add_box(self, box): """ Add a box to the column @param box: the box to add to the column @type box: ColumnBox object """ self.append(box._make()) def set_space(self, space): """ Set the spacing of the column items @param space: the space between the items @type space: float """ #print "Column.set_space()" self.space = space def get_space(self): """ Get the spacing of the column items """ return self.space def _make(self): """ Make the column """ #print "Column._make()" for box in self.boxes: self.append(box._make()) return self class ColumnBox(Group): """ A box, containing various objects, with a fixed width, but variable height Should add more docs here too... """ def __init__(self, poster): Group.__init__(self) self.title_align = poster.column_box_title_align self.title_tex_scale = poster.column_box_title_tex_scale self.title_fixed_width = poster.column_box_title_fixed_width self.title_text_style = poster.column_box_title_text_style self.title_fg = poster.column_box_title_fg self.align = poster.column_box_text_align self.tex_scale = poster.column_box_tex_scale self.fixed_width = poster.column_box_text_width self.text_style = poster.column_box_text_style self.fg = poster.column_box_text_fg self.item_sep = poster.column_box_item_sep self.box_width = poster.column_box_width self.box_bg = poster.column_box_bg self.box_border = poster.column_box_border self.title = "" self.items = [] def set_title(self, title): """ Set the title of the box within the column @param title: the title of the column box @type title: string """ self.title = title def add_TeXBox(self, text): """ Adds a TeXBox object to the column @param text: the text of the TeXBox object to add @type text: string """ texbox = TeXBox(text) texbox.make() # other settings here... self.items.append(texbox) def add_fig(self, fig, height=None, width=None, bg=Color(1)): """ Add an arbitrary figure to the column box, with a background. This could be a previously defined pyscript diagram (for instance). If only one of the height or width is given then the figure is scaled appropriately, maintaining the original aspect ratio. @param fig: the figure to add @type fig: PyScript object @param width: the width of the figure @type width: float @param height: the height of the figure @type height: float @param bg: the colour of the figure background @type bg: Color object """ # get the figure's current height and width oldHeight = fig.bbox().height oldWidth = fig.bbox().width # scale the figure appropriately if height is not None and width is None: scale = height/oldHeight fig = fig.scale(scale, scale) elif height is None and width is not None: scale = width/oldWidth fig = fig.scale(scale, scale) elif height is not None and width is not None: xscale = width/oldWidth yscale = height/oldHeight fig = fig.scale(xscale, yscale) else: # leave well alone... pass # put a white background on it gutter = 0.1 rect = Rectangle(width=fig.bbox().width+gutter, height=fig.bbox().height+gutter, c=fig.bbox().c, bg=bg, fg=None) # group everything together all = Group() all.append(rect, fig) # append it to the list of items in the ColumnBox self.items.append(all) def add_epsf(self, file, height=None, width=None): """ Add an eps file to the column box. If only one of the height or width is given then the figure is scaled appropriately, maintaining the original aspect ratio. @param file: the file name of the eps file to add @type file: string @param width: the width of the figure @type width: float @param height: the height of the figure @type height: float """ # load the eps with the appropriate dimensions if height is not None and width is None: eps = Epsf(file=file, height=height) elif height is None and width is not None: eps = Epsf(file=file, width=width) elif height is not None and width is not None: eps = Epsf(file=file, width=width, height=height) else: # use the file's own size eps = Epsf(file=file) # append it to the list of items in the ColumnBox self.items.append(eps) def add_object(self, obj): """ Add a pre-defined object to the box, this could be an Align or Group object for example @param obj: the object to be added @type obj: pyscript object """ self.items.append(obj) def add_text(self, text): """ Add arbitrarily placed text to the box @param text: the text to be added @type text: string """ tex = TeX(text) self.items.append(tex) def _make_title(self): """ Make the title """ titlebox = TeXBox(self.title) titlebox.set_align(self.title_align) titlebox.set_tex_scale(self.title_tex_scale) titlebox.set_fixed_width(self.title_fixed_width) titlebox.set_text_style(self.title_text_style) titlebox.set_fg(self.title_fg) titlebox.make() return titlebox def _make(self): """ Make the column box object """ valign = VAlign(space=self.item_sep) valign.append(self._make_title()) #print "Number of items in the column box is: %d" % len(self.items) for item in self.items: valign.append(item) # the reason for the BasicBox class is to let the overall poster # style handle the width, foreground, etc. etc. box = BasicBox() box.set_height(valign.bbox().height + 2*box.pad) box.set_width(self.box_width) box.set_anchor("n", valign.bbox().n+P(0, 0.2)) #box.n = valign.bbox().n+P(0, 0.2) # absorb into style ??? box.set_bg(self.box_bg) box.set_border(self.box_border) # append the objects to the group self.append(box) self.append(valign) return self class BasicBox(Rectangle): """ A basic box, with border, and background to use in behind textual and other objects """ def __init__(self): Rectangle.__init__(self) self.bg = Color("lavender") self.fg = Color(0) self.border = 1 self.fixed_width = 9.6 self.pad = 0.2 self.radius = 0 self.width = 9.9 self.height = 1 self.anchor = "n" def set_height(self, height): """ Set the height of the box """ self.height = height def set_width(self, width): """ Set the width of the box """ self.width = width def set_bg(self, bg): """ Set the background colour """ self.bg = bg def set_fg(self, fg): """ Set the foreground colour """ self.fg = fg def set_border(self, border): """ Set the width of the border around the box """ self.linewidth = border def set_radius(self, radius): """ Set the radius of the corners of the box, if they are rounded """ self.r = radius def set_pad(self, pad): """ Set the padding around the box """ self.pad = pad def set_anchor(self, anchor, location): """ Set the anchor location (c, n, ne, e, se, s, sw, w, nw) """ exec("self.%s = location" % anchor) class Poster_1(Page): ''' A poster style, portrait orientation very similar to a journal article's front page. Title, authors and abstract across top. two columns for boxes with details. It is set up for A4 paper which can then be scaled for A0 etc. @cvar bg: poster background @cvar gutter: nonprintable margin around entire poster @cvar title: TeX of title @cvar title_fg: fg color of title @cvar title_scale: scale of title TeX @cvar title_width: proportion of total width for title @cvar authors: TeX of authors @cvar authors_fg: fg color of authors @cvar authors_scale: scale of authors TeX @cvar authors_width: proportion of total width for authors @cvar address: TeX of address @cvar address_fg: fg color of address @cvar address_scale: scale of address TeX @cvar address_width: proportion of total width for address @cvar abstract: TeX of abstract @cvar abstract_fg: fg color of abstract @cvar abstract_scale: scale of abstract TeX @cvar abstract_width: proportion of total width for abstract @cvar logos: a list of filenames for the logos @cvar logo_height: the height to which to scale the logos @cvar printing_area: an Area the size of the page minus the gutter @cvar col1: a Group() containing left column objects @cvar col2: a Group() containing right column objects ''' col1 = Group() col2 = Group() logos = () def __init__(self): Page.__init__(self) self.size = "A4" self.gutter = 0.2 # paper margin for A4 in cm self.bg = Color('DarkSlateBlue') self.title = "" self.title_fg = Color('Yellow') self.title_scale = 1.4 self.title_width = 0.8 self.address = "" self.address_fg = Color(0) self.address_scale = 1 self.address_width = 0.8 self.authors = "" self.authors_fg = Color(0) self.authors_scale = 1 self.authors_width = 0.8 self.abstract = "" self.abstract_fg = Color(0) self.abstract_scale = 0.8 self.abstract_width = 0.8 self.logo_height = 0.8 #self.logos = () #self.col1 = Group() #self.col2 = Group() self.signature_fg = self.bg*0.8 area = self.area() # subtract the gutter to get printing area self.printing_area = Area( sw=area.sw+P(1, 1)*self.gutter, width=area.width-2*self.gutter, height=area.height-2*self.gutter ) def add_fig(self, file, width=5.0): """ This method needs to be fixed up. It's not to put a figure on the page, but an eps file... """ fig = Epsf(file) rect1 = Rectangle(c=fig.c, width=fig.bbox().width+0.1, height=fig.bbox().height+0.1, fg=Color('black'), bg=Color('white'), linewidth=0.5, ) out_fig = Group(rect1,fig) out_fig.scale(width/out_fig.bbox().width,width/out_fig.bbox().width) return out_fig def add_epsf(self): """ Add and EPS file to the poster """ pass def _make_logos(self): """ Make and return a Group object of the logos """ #thelogos = Group() thelogos = Align(a1="e", a2="w", angle=90, space=None) for logo in self.logos: thelogos.append(Epsf(logo, height=self.logo_height)) Distribute(thelogos, a1="e", a2="w", p1=self.printing_area.nw, p2=self.printing_area.ne) #Align(thelogos, a1="e", a2="w", angle=90, space=None) return thelogos def _make_title(self): ''' Return a title object ''' return TeXBox(self.title, fg=self.title_fg, fixed_width=self.printing_area.width*self.title_width, tex_scale=self.title_scale, align="c").make() def _make_address(self): """ Return an address object """ return TeXBox(self.address, fg=self.address_fg, fixed_width=self.printing_area.width*self.address_width, tex_scale=self.address_scale, align="c").make() def _make_abstract(self): ''' Return the abstract object ''' return TeXBox(self.abstract, fixed_width=self.printing_area.width*self.abstract_width, tex_scale=self.abstract_scale, fg=self.abstract_fg, align="c").make() def _make_authors(self): ''' Return authorlist object ''' return TeXBox(self.authors, fg=self.authors_fg, tex_scale=self.authors_scale, fixed_width=self.printing_area.width*self.authors_width, align="c").make() def _make_background(self): ''' Return background (block color) ''' area = self.area() return Rectangle(width=area.width, height=area.height, fg=None, bg=self.bg ) def make(self): ''' Create the actual poster aligning everything up. calls make_title(), make_authors() etc ''' # NB: A0 = 4x A4 # vertically align the column items ... no spacing yet! Align(self.col1, a1="s", a2="n", angle=180, space=None) Align(self.col2, a1="s", a2="n", angle=180, space=None) # Distribute the cols horizontally Distribute(Area(width=0, height=0), self.col1, self.col2, Area(width=0, height=0), p1=self.printing_area.w, p2=self.printing_area.e, a1="e", a2="w") # find the distance between the cols pad = (self.col2.bbox().w-self.col1.bbox().e)[0] # vertically align the column items Align(self.col1, a1="s", a2="n", angle=180, space=pad) Align(self.col2, a1="s", a2="n", angle=180, space=pad) # align the two columns themselves cols = Align(self.col1, self.col2, angle=90, space=None, a1="ne", a2="nw") all = Align( self._make_logos(), self._make_title(), self._make_authors(), self._make_address(), self._make_abstract(), cols, a1="s", a2="n", angle=180, space=pad ) all.n = self.printing_area.n-P(0, 0.1) back = self._make_background() p = self.printing_area.se+P(0, 1.2) signature = Text( 'Created with PyScript. http://pyscript.sourceforge.net', size=6, sw=p, fg=self.signature_fg ).rotate(-90, p) self.append(back, all, signature) # return a reference for convenience return self class Talk(Pages): """ A talk class """ def __init__(self, style=None): Pages.__init__(self) self.slides = [] self.bg = Color('RoyalBlue')*0.9 self.fg = self.bg self.logos = [] self.logo_height = 0.8 self.title = "" self.title_fg = Color('white') self.title_scale = 5 self.title_textstyle = "" self.slide_title = "" self.slide_title_fg = Color('white') self.slide_title_scale = 5 self.slide_title_textstyle = "" self.footerScale = 1 self.waitbar_fg = Color('orangered') self.waitbar_bg = Color('black') self.authors = "" self.authors_fg = Color('white') self.authors_scale = 3 self.authors_textstyle = "" self.speaker = "" # i.e. who's actually giving the talk self.speaker_fg = Color(0) self.speaker_textstyle = "" self.address = "" self.address_fg = Color('white') self.address_scale = 2 self.address_textstyle = "" self.box_bg = Color('lavender') self.box_fg = Color(0) self.box_border = 2 self.text_scale = 3 self.text_fg = Color(0) self.text_textstyle = "" self.headings_fgs = { 1 : Color('white'), 2 : Color('white'), 3 : Color('white'), "equation" : Color('white'), "default" : Color('white'), "space" : self.fg, } self.headings_scales = { 1 : 3, 2 : 2.5, 3 : 2.2, "equation" : 2.5, "default" : 1.5, "space" : 3, } self.headings_bullets = { 1 : TeX(r"$\bullet$"), 2 : TeX(r"--"), 3 : TeX(r"$\gg$"), "equation" : Rectangle(height=1, fg=self.bg, bg=self.bg), "default" : TeX(r"$\cdot$"), "space" : Rectangle(height=1, fg=self.bg, bg=self.bg), } self.headings_indent = { 1 : 0, 2 : 0.5, 3 : 1, "equation" : 2, "default" : 2, "space" : 0, } self.headings_textstyle = { 1 : "", 2 : "", 3 : "", "equation" : "", "default" : "", "space" : "", } # process the style option if style is not None: # make sure the file exists in either the .pyscript/styles # directory, or the current directory styleFname = style + ".py" HOME = os.path.expandvars("$HOME") if os.path.exists(HOME + "/.pyscript/styles/" + styleFname): print "Found %s in .pyscript/styles dir" % style self._read_style(HOME + "/.pyscript/styles/" + styleFname) elif os.path.exists(styleFname): print "Found %s in current dir" % style self._read_style(styleFname) else: # barf raise ValueError, "Style %s not found!" % style def _read_style(self, styleFname): """ Read the talk style file @param styleFname: The name of the style file to process @type styleFname: string """ # slurp in the text fp = open(styleFname, "r") lines = fp.readlines() fp.close() # make one big string... styleText = "" for line in lines: styleText += line # exec the text exec(styleText) def set_title(self, title): """ Set the title of the talk as a whole @param title: the title of the talk @type title: string """ self.title = title return def set_authors(self, authors): """ Set the authors of the talk @param authors: the author list for the talk @type authors: string """ self.authors = authors return def set_speaker(self, speaker): """ Set the name of the person actually giving the talk/presentation @param speaker: the name of the person giving the talk @type speaker: string """ self.speaker = speaker return def set_address(self, address): """ Set the address for the institution (or equivalent) of the speaker @param address: the address to use @type address: string """ self.address = address return def add_logo(self, logo, height=None): """ Add a logo to the talk @param logo: eps file name of logo @type logo: string """ if height is None: height = self.logo_height self.logos.append(Epsf(file=logo, height=height)) def _make_authors(self): """ Generate the authors text on the titlepage """ ttext = "%s %s" % (self.authors_textstyle, self.authors) return TeX(ttext, fg=self.authors_fg ).scale(self.authors_scale, self.authors_scale) def _make_address(self): """ Generate the address text on the titlepage """ if isinstance(self.address, types.StringType): ttext = "%s %s" % (self.address_textstyle, self.address) return TeX(ttext, fg=self.address_fg ).scale(self.address_scale, self.address_scale) else: #raise ValueError, "Can't handle non-string arguments yet" return self.address def make(self, *slides, **options): """ Routine to collect all of slides together and render them all as the one document """ # create the titlepage automatically titlepage = Slide(self) titlepage.set_titlepage() self.slides.append(titlepage) # create the list of slides for slide in slides: self.slides.append(slide) # add all the slides to the talk i = 1 temp = Pages() for slide in self.slides: slide.pageNumber = i print 'Adding slide', str(i), '...' temp.append(slide._make(self)) i += 1 # determine the file name to use if not options.has_key('file'): raise ValueError, "No filename given" file = options['file'] # render it! render(temp, file=file) class Slide(Page): """ A slide class. Use this class to generate the individual slides in a talk """ def __init__(self, talk): Page.__init__(self) self.size = "a4" self.orientation = "Landscape" self.pageNumber = None self.pages = 0 # need to set up an initial value self.titlepage = False self.authors = None self.headings = [] self.epsf = [] self.figs = [] self.area = self.area() self.title = None self.logos = talk.logos self.text_scale = talk.text_scale self.text_textstyle = talk.text_textstyle self.text_fg = talk.text_fg self.textObjs = [] def _make_logos(self): """ Put the logos on the page """ if len(self.logos) == 0: return Area(width=0, height=0) elif len(self.logos) == 1: return Group( Area(width=self.area.width-0.4, height=0), self.logos[0] ) width = self.area.width -\ self.logos[0].bbox().width -\ self.logos[-1].bbox().width -\ 0.4 for logo in self.logos[1:-1]: width -= logo.bbox().width space = width/(len(self.logos)-1) a = Align(a1="e", a2="w", angle=90, space=space) for logo in self.logos: a.append(logo) return a def add_fig(self, obj, **options): """ Put an arbitrary figure onto the page, with a white background @param obj: the PyScript object to use for the figure @type obj: PyScript object """ if options.has_key('bg'): backColor = options['bg'] else: backColor = Color('white') if options.has_key('fg'): frontColor = options['fg'] else: frontColor = None if options.has_key('height'): figHeight = options['height'] else: figHeight = None if options.has_key('width'): figWidth = options['width'] else: figWidth = None gutter = 0.1 back = Rectangle(width=obj.bbox().width+gutter, height=obj.bbox().height+gutter, bg=backColor, fg=frontColor) back.sw = obj.bbox().sw-P(gutter/2.0, gutter/2.0) fig = Group(back, obj) # now scale the height/width appropriately if figWidth and/or # figHeight are set if figHeight is not None and figWidth is None: if fig.bbox().height == 0.0: raise ValueError, "Initial figure height is zero!!" else: scale = figHeight/fig.bbox().height fig.scale(scale, scale) elif figHeight is None and figWidth is not None: if fig.bbox().width == 0.0: raise ValueError, "Initial figure width is zero!!" else: scale = figWidth/fig.bbox().width fig.scale(scale, scale) elif figHeight is not None and figWidth is not None: if fig.bbox().height == 0.0: raise ValueError, "Initial figure height is zero!!" elif fig.bbox().width == 0.0: raise ValueError, "Initial figure width is zero!!" else: scalex = figWidth/fig.bbox().width scaley = figHeight/fig.bbox().height fig.scale(scalex, scaley) # there must be a better way to do this!!! if options.has_key('e'): fig.e = options['e'] elif options.has_key('se'): fig.se = options['se'] elif options.has_key('s'): fig.s = options['s'] elif options.has_key('sw'): fig.sw = options['sw'] elif options.has_key('w'): fig.w = options['w'] elif options.has_key('nw'): fig.nw = options['nw'] elif options.has_key('n'): fig.n = options['n'] elif options.has_key('ne'): fig.ne = options['ne'] elif options.has_key('c'): fig.c = options['c'] else: fig.sw = P(0.0, 0.0) # add the figure to the list of figures self.figs.append(fig) def set_titlepage(self): """ Set the current slide to be the titlepage """ self.titlepage = True return def set_title(self, title=None): """ Set the title of the slide """ self.title = title return def _make_title(self, talk): """ Make the title of the slide (note that this is *not* the title of the talk) """ if self.title is None or self.title == "": return Area(width=0, height=0) # if we just get a string, put it in a TeX object in the current style if isinstance(self.title, types.StringType): ttext = "%s %s" % (talk.title_textstyle, self.title) return TeX(ttext, fg=talk.title_fg).scale(talk.title_scale*0.8, talk.title_scale) else: # just return the object itself return self.title def add_heading(self, level, text): """ Add a heading to the slide @param level: the heading level as a number starting from 1 (the most significant level) @type level: int (1,2,3) or string ("space", "equation") @param text: the text to be used for the heading @type text: string """ temp = [ level, text ] self.headings.append(temp) def add_text(self, text, **options): """ Add, and arbitrarily place, text on the slide @param text: the text to place @type text: string, TeX object or Text object """ # process options if options.has_key('fg'): frontColor = options['fg'] else: frontColor = self.text_fg if options.has_key('scale'): scale = options['scale'] else: scale = self.text_scale # check for what kind of object we have... if isinstance(text, types.StringType): # prepend the style if it is just a string text = self.text_textstyle + " " + text obj = TeX(text, fg=frontColor).scale(scale, scale) else: raise ValueError, \ "Cannot yet handle non-string objects in Slide.add_text()" # there must be a better way to do this!!! if options.has_key('e'): obj.e = options['e'] elif options.has_key('se'): obj.se = options['se'] elif options.has_key('s'): obj.s = options['s'] elif options.has_key('sw'): obj.sw = options['sw'] elif options.has_key('w'): obj.w = options['w'] elif options.has_key('nw'): obj.nw = options['nw'] elif options.has_key('n'): obj.n = options['n'] elif options.has_key('ne'): obj.ne = options['ne'] elif options.has_key('c'): obj.c = options['c'] else: obj.sw = P(0.0, 0.0) #obj = TeX(r"test", fg=frontColor) #obj.c = self.area.c self.textObjs.append(obj) def _make_headings(self, talk): """ Make the headings """ heading_block = Align(a1="sw", a2="nw", angle=180, space=0.5) for heading in self.headings: heading_level = heading[0] if not talk.headings_bullets.has_key(heading_level): heading_level = "default" heading_text = "%s %s" % (talk.headings_textstyle[heading_level] , heading[1]) heading_bullet = talk.headings_bullets[heading_level] heading_fg = talk.headings_fgs[heading_level] heading_scale = talk.headings_scales[heading_level] heading_indent = talk.headings_indent[heading_level] tex = Align(a1='ne', a2='nw', angle=90, space=0.2) tex.append(heading_bullet) tex.append(TeXBox(text=heading_text, fixed_width=self.area.width-5, fg=heading_fg, tex_scale=heading_scale)) padding = Area(sw=tex.sw, width=heading_indent, height=0) heading_proper = Align(a1="e", a2="w", angle=90, space=0) heading_proper.append(padding, tex) heading_block.append(heading_proper) return heading_block def _make_waitbar(self, talk): """ Make a waitbar """ waitBarBack = Rectangle(se=self.area.se+P(-0.8, 0.4), width=2.5, height=0.5, r=0.2, fg=talk.waitbar_bg, bg=talk.waitbar_bg) offset = 0.05 waitBarFront = Rectangle(w=waitBarBack.w+P(offset, 0), width=(waitBarBack.width-2*offset)*\ self.pageNumber/self.pages, height=waitBarBack.height-2*offset, r=0.2, fg=talk.waitbar_fg, bg=talk.waitbar_fg) waitBar = Group(waitBarBack, waitBarFront) return waitBar def _make_footer(self, talk): """ Make the footer. A text block giving the title and the name of the person giving the talk """ pageOf = False if pageOf: footerText = " - %s; page %i of %i" % \ (talk.speaker, self.pageNumber, self.pages) else: footerText = " - %s" % (talk.speaker, ) footer = Align(a1="e", a2="w", angle=90, space=0.1) footer.append(TeX(text="%s %s"%(talk.title_textstyle, talk.title), fg=talk.title_fg, ).scale(talk.footerScale, talk.footerScale)) footer.append(TeX(text="%s %s"%(talk.speaker_textstyle, footerText), fg=talk.title_fg ).scale(talk.footerScale, talk.footerScale)) footer.sw = self.area.sw+P(0.4, 0.4) return footer def add_epsf(self, file="", **options): """ Add an eps file to the slide @param file: the filename of the eps file @type file: string @keyword width: the width of the image in the current default units. If only this variable is given, then the aspect ratio of the image is maintained. @type width: float @keyword height: the height of the image in the current default units. If only this variable is given, then the aspect ratio of the image is maintainted. @type height: float @keyword c, n, ne, e, se, s, sw, w, nw: the location of the anchor point """ if options.has_key('width'): picture = Epsf(file, width=options['width']) elif options.has_key('height'): picture = Epsf(file, height=options['height']) elif options.has_key('width') and options.has_key('height'): picture = Epsf(file, width=options['width'], height=options['height']) else: picture = Epsf(file) # there must be a better way to do this!!! if options.has_key('e'): picture.e = options['e'] elif options.has_key('se'): picture.se = options['se'] elif options.has_key('s'): picture.s = options['s'] elif options.has_key('sw'): picture.sw = options['sw'] elif options.has_key('w'): picture.w = options['w'] elif options.has_key('nw'): picture.nw = options['nw'] elif options.has_key('n'): picture.n = options['n'] elif options.has_key('ne'): picture.ne = options['ne'] elif options.has_key('c'): picture.c = options['c'] else: picture.sw = P(0.0, 0.0) offset = 0.2 background = Rectangle(width=picture.bbox().width+offset, height=picture.bbox().height+offset, bg=Color('white'), fg=Color('white'), ) background.sw = picture.sw-P(offset/2.0, offset/2.0) figure = Group(background, picture) self.epsf.append(figure) def _make_epsf(self): """ Collects all of the eps images together """ pictures = Group() for file in self.epsf: pictures.append(file) return pictures def _make_figs(self): """ Collects all of the figures together """ figs = Group() for fig in self.figs: figs.append(fig) return figs def _make_textObjs(self): """ Collects all the text objects together """ textObjs = Group() for text in self.textObjs: textObjs.append(text) return textObjs def _make_titlepage(self, talk): """ Makes the titlepage of the talk """ titlepage = Align(a1="s", a2="n", angle=180, space=0.4) if isinstance(talk.title, types.StringType): ttext = "%s %s" % (talk.title_textstyle, talk.title) titlepage.append(TeX(ttext, fg=talk.title_fg)\ .scale(talk.title_scale, talk.title_scale)) else: #raise ValueError, "Can't yet handle non-string arguments") titlepage.append(Text(ttext)) if talk.authors is not None: titlepage.append(talk._make_authors()) if talk.address is not None: titlepage.append(talk._make_address()) return titlepage def _make_background(self, talk): """ Makes the background of the slide """ back = Group() back.append(Rectangle(sw=self.area.sw, width=self.area.width, height=self.area.height, fg=None, bg=talk.bg, ) ) back.append(Rectangle(sw=self.area.sw, width=2.5, height=self.area.height, fg=None, bg=talk.bg*0.5, ) ) back.append(Rectangle(sw=self.area.sw, width=self.area.width, height=1.5, fg=None, bg=talk.bg*0.5, ) ) back.append(Rectangle(nw=self.area.nw, width=self.area.width, height=2.5, fg=None, bg=talk.bg*0.5, ) ) back.append(Rectangle(nw=self.area.nw, width=2.5, height=2.5, fg=None, bg=Color('firebrick'), ) ) return back def _make(self, talk, scale=1): """ Make the slide. Collect all of the objects together into one Page() object ready for rendering. """ if self.titlepage: all = self._make_titlepage(talk) all.c = self.area.c + P(0.0, 0.8) else: all = Align(a1="s", a2="n", angle=180, space=0.4) all.append(self._make_title(talk)) all.nw = self.area.nw + P(2.5, -0.2) # I'm aware that this isn't a good way to do this, but # it's late at night, and I want to get *something* going headings = self._make_headings(talk) headings.nw = self.area.nw + P(3.0, -3.0) back = self._make_background(talk) p = self.area.se + P(-0.1, 0.1) signature = Text( 'Created with PyScript. http://pyscript.sourceforge.net', size=15, sw=p, fg=talk.bg*0.8 ).rotate(-90, p) logos = self._make_logos() logos.nw = self.area.nw + P(0.2, -0.2) self.pages = len(talk.slides) All = Group( back, all, headings, self._make_epsf(), self._make_figs(), self._make_textObjs(), signature, self._make_footer(talk), logos, self._make_waitbar(talk) ).scale(scale, scale) return Page(All, orientation=self.orientation) # vim: expandtab shiftwidth=4: