#!/usr/bin/env python # -*- coding: utf-8 -*- import docutils.core,docutils.nodes,sys,re import sys import pprint from types import StringType from docutils import __version__, __version_details__, SettingsSpec from docutils import frontend, io, utils, readers, writers from docutils.frontend import OptionParser from docutils.transforms import Transformer import docutils.readers.doctree from urlparse import * from reportlab.platypus import * #from reportlab.platypus.para import Paragraph,FastPara,Para from reportlab.lib.enums import * import reportlab.lib.colors as colors from reportlab.lib.units import * from reportlab.lib.pagesizes import * from copy import copy from cgi import escape from styles import * styles=getStyleSheet() marks="#=-_*^>%&|" lowerroman=['i','ii','iii','iv','v','vi','vii','viii','ix','x','xi'] loweralpha="abcdefghijklmnopqrstuvwxyz" class MyIndenter(Indenter): # Bugs in reportlab? def draw(self): pass width=0 height=0 def depth (node): if node.parent==None: return 0 else: return 1+depth(node.parent) decoration = {'header':None, 'footer':None, 'endnotes':[]} def gather_pdftext (node, depth, in_line_block=False,replaceEnt=True): return ''.join([gen_pdftext(n,depth,in_line_block,replaceEnt) for n in node.children ]) def gen_pdftext(node, depth, in_line_block=False,replaceEnt=True): pre="" post="" if isinstance (node, docutils.nodes.paragraph) \ or isinstance (node, docutils.nodes.title) \ or isinstance (node, docutils.nodes.subtitle) \ : node.pdftext=gather_pdftext(node,depth)+"\n" elif isinstance (node, docutils.nodes.Text): node.pdftext=node.astext() if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.strong): pre="" post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.emphasis): pre="" post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.literal): pre=''%styles['Code'].fontName post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.superscript): pre='' post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.subscript): pre='' post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.title_reference): # FIXME needs to work as a link pre='' post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.reference) : pre='' post="" uri=node.get('refuri') if uri: if urlparse(uri)[0]: pre+=u''%uri post=''+post node.pdftext=node.astext() if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.option_string) \ or isinstance (node, docutils.nodes.option_argument) \ : node.pdftext=node.astext() if replaceEnt: node.pdftext=escape(node.pdftext,True) elif isinstance (node, docutils.nodes.header) \ or isinstance (node, docutils.nodes.footer) \ : node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.system_message) \ or isinstance (node, docutils.nodes.problematic) \ : sys.stderr.write (node.astext()+"\n") sys.stderr.flush() pre='' post="" node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.generated): node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) node.pdftext=pre+node.pdftext+post elif isinstance (node, docutils.nodes.image): node.pdftext=''%node.get('uri') elif isinstance (node, docutils.nodes.footnote_reference): # Fixme link to the right place node.pdftext=u'%s'%node.astext() elif isinstance (node, docutils.nodes.citation_reference): # Fixme link to the right place node.pdftext=u'[%s]'%node.astext() # FIXME nodes we are ignoring for the moment elif isinstance (node, docutils.nodes.target): # FIXME: make it work as a target for links node.pdftext=gather_pdftext(node,depth) if replaceEnt: node.pdftext=escape(node.pdftext,True) else: print "Unkn. node (gen_pdftext): ", node.__class__ print node #print node.transform sys.exit(1) return node.pdftext def gen_elements(node, depth, in_line_block=False, style=styles['BodyText']): global decoration if isinstance (node, docutils.nodes.document): node.elements=gather_elements(node,depth,style=style) ####################### ## Tables ####################### elif isinstance (node, docutils.nodes.table): node.elements=gather_elements(node,depth) elif isinstance (node, docutils.nodes.tgroup): rows=[] hasHead=False for n in node.children: if isinstance (n,docutils.nodes.thead): hasHead=True for row in n.children: r=[] for cell in row.children: r.append(cell) rows.append(r) elif isinstance (n,docutils.nodes.tbody): for row in n.children: r=[] for cell in row.children: r.append(cell) rows.append(r) spans=filltable (rows) data=[] for row in rows: r=[] for cell in row: if isinstance(cell,str): r.append("") else: r.append(gather_elements(cell,depth)) data.append(r) st=spans+tstyleNorm if hasHead: st+=[('BACKGROUND',(0,0),(-1,0),colors.yellow)] node.elements=[Table(data,style=TableStyle(st))] elif isinstance (node, docutils.nodes.title): # Special cases: (Not sure this is right ;-) if isinstance (node.parent, docutils.nodes.document): # FIXME maybe make it a coverpage? node.elements=[Paragraph(gen_pdftext(node,depth), styles['Title'])] elif isinstance (node.parent, docutils.nodes.topic): # FIXME style correctly node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading3'])] elif isinstance (node.parent, docutils.nodes.admonition) or \ isinstance (node.parent, docutils.nodes.sidebar): node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading3'])] else: node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading%d'%min(depth,3)])] elif isinstance (node, docutils.nodes.subtitle): if isinstance (node.parent,docutils.nodes.sidebar): node.elements=[Paragraph(gen_pdftext(node,depth), styles['Heading4'])] elif isinstance (node.parent,docutils.nodes.document): node.elements=[Paragraph(gen_pdftext(node,depth), styles['Subtitle'])] elif isinstance (node, docutils.nodes.paragraph): node.elements=[Paragraph(gen_pdftext(node,depth), style)] elif isinstance (node, docutils.nodes.docinfo): # A docinfo usually contains several fields. # We'll render it as a series of elements, one field each. node.elements=gather_elements(node,depth,style=style) elif isinstance (node, docutils.nodes.field): # A field has two child elements, a field_name and a field_body. # We render as a two-column table, left-column is right-aligned, # bold, and much smaller fn=Paragraph(gather_pdftext(node.children[0],depth)+":",style=styles['FieldName']) fb=gen_elements(node.children[1],depth) node.elements=[Table([[fn,fb]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])] elif isinstance (node, docutils.nodes.decoration): # This is a tricky one. We need to switch our document's # page templates based on this. If decoration contains a # header and/or a footer, we need to use those # right now, we avoid trouble. # FIXME Implement node.elements=gather_elements(node,depth,style=style) elif isinstance (node, docutils.nodes.header): decoration['header']=Paragraph(gather_pdftext(node,depth),style=style) node.elements=[] elif isinstance (node, docutils.nodes.footer): decoration['footer']=Paragraph(gather_pdftext(node,depth),style=style) node.elements=[] elif isinstance (node, docutils.nodes.author): if isinstance (node.parent,docutils.nodes.authors): # Is only one of multiple authors. Return a paragraph node.elements=[Paragraph(gather_pdftext(node,depth), style=style)] else: # A single author: works like a field fb=gather_pdftext(node,depth) node.elements=[Table([[Paragraph("Author:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])] elif isinstance (node, docutils.nodes.authors): # Multiple authors. Create a two-column table. Author references on the right. td=[[Paragraph("Authors:",style=styles['FieldName']),gather_elements(node,depth,style=style)]] node.elements=[Table(td,style=tstyles['Field'],colWidths=[fieldlist_lwidth,None])] elif isinstance (node, docutils.nodes.organization): fb=gather_pdftext(node,depth) t=Table([[Paragraph("Organization:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.contact): fb=gather_pdftext(node,depth) t=Table([[ Paragraph("Contact:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.address): fb=gather_pdftext(node,depth) t=Table([[ Paragraph("Address:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.version): fb=gather_pdftext(node,depth) t=Table([[ Paragraph("Version:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.revision): fb=gather_pdftext(node,depth) t=Table([[ Paragraph("Revision:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.status): fb=gather_pdftext(node,depth) t=Table([[ Paragraph("Version:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.date): fb=gather_pdftext(node,depth) t=Table([[ Paragraph("Date:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.copyright): fb=gather_pdftext(node,depth) t=Table([[Paragraph("Copyright:",style=styles['FieldName']), Paragraph(fb,style) ]],style=tstyles['Field'],colWidths=[fieldlist_lwidth,None]) node.elements=[t] elif isinstance (node, docutils.nodes.topic) \ or isinstance (node, docutils.nodes.field_body) \ : node.elements=gather_elements(node,depth,style=style) elif isinstance (node, docutils.nodes.section): if depth<1: node.elements=[PageBreak()]+gather_elements(node,depth+1) else: node.elements=gather_elements(node,depth+1) elif isinstance (node, docutils.nodes.bullet_list) \ or isinstance (node, docutils.nodes.enumerated_list) \ or isinstance (node, docutils.nodes.definition_list) \ or isinstance (node, docutils.nodes.option_list) \ or isinstance (node, docutils.nodes.field_list) \ or isinstance (node, docutils.nodes.definition) \ : node.elements=gather_elements(node,depth,style=style) elif isinstance (node, docutils.nodes.option): node.elements=[Preformatted(gather_pdftext(node,depth,replaceEnt=False),style=styles['Code'])] elif isinstance (node, docutils.nodes.option_list_item): og = gather_elements(node.children[0],depth,style) desc = gather_elements(node.children[1],depth,style) node.elements=[Table([[og,desc]],style=tstyles['Field'])] elif isinstance (node, docutils.nodes.definition_list_item): # I need to catch the classifiers here tt=[] dt=[] for n in node.children: if isinstance(n,docutils.nodes.term) or \ isinstance(n,docutils.nodes.classifier) : tt.append(gather_pdftext(n,depth,style)) else: dt=dt+gen_elements(n,depth,style) node.elements=[Paragraph(':'.join(tt),style),MyIndenter(left=10)]+dt+[MyIndenter(left=-10)] elif isinstance (node, docutils.nodes.list_item): # A list_item is a table of two columns. # The left one is the bullet itself, the right is the # item content. This way we can nest them. el=gather_elements(node,depth,style=style) b="" if node.parent.get('bullet') or isinstance(node.parent,docutils.nodes.bullet_list): # FIXME: use correct bullet symbols, style, check inter-paragraph spacing b=str(node.parent.get('bullet')) if b=="None": b="" elif node.parent.get ('enumtype')=='arabic': b=str(node.parent.children.index(node)+1) elif node.parent.get ('enumtype')=='lowerroman': b=str(lowerroman[node.parent.children.index(node)]) elif node.parent.get ('enumtype')=='upperroman': b=str(lowerroman[node.parent.children.index(node)].upper()) elif node.parent.get ('enumtype')=='loweralpha': b=str(loweralpha[node.parent.children.index(node)]) elif node.parent.get ('enumtype')=='upperalpha': b=str(loweralpha[node.parent.children.index(node)].upper()) else: print "Unknown kind of list_item" print node.parent sys.exit(1) node.elements=[Table([[b,el]],style=tstyles['List'],colWidths=[list_lwidth,None])] elif isinstance (node, docutils.nodes.transition): node.elements=[Separation()] elif isinstance (node, docutils.nodes.system_message) \ or isinstance (node, docutils.nodes.problematic) \ : # FIXME show the error in the document, red, whatever sys.stderr.write (node.astext()+"\n") sys.stderr.flush() node.elements=[] elif isinstance (node, docutils.nodes.block_quote): node.elements=[MyIndenter(left=20)]+gather_elements(node,depth,style)+[MyIndenter(left=-20)] elif isinstance (node, docutils.nodes.attribution): node.elements=[Paragraph(gather_pdftext(node,depth),styles['Attribution'])] elif isinstance (node, docutils.nodes.comment): # Class that generates no output node.elements=[] elif isinstance (node, docutils.nodes.line_block): # Obsolete? Let's do something anyway. # FIXME: indent or not? qstyle=copy(style) qstyle.leftIndent+=30 node.elements=gather_elements(node,depth,style=qstyle) elif isinstance (node, docutils.nodes.line): # All elements in one line node.elements=[Paragraph(gather_pdftext(node,depth),style=style)] elif isinstance (node, docutils.nodes.literal_block) \ or isinstance (node, docutils.nodes.doctest_block) \ : node.elements=[Preformatted(gather_pdftext(node,depth,replaceEnt=False),style=styles['Code'])] elif isinstance (node, docutils.nodes.attention) \ or isinstance (node, docutils.nodes.caution) \ or isinstance (node, docutils.nodes.danger) \ or isinstance (node, docutils.nodes.error) \ or isinstance (node, docutils.nodes.hint) \ or isinstance (node, docutils.nodes.important) \ or isinstance (node, docutils.nodes.note) \ or isinstance (node, docutils.nodes.tip) \ or isinstance (node, docutils.nodes.warning) \ or isinstance (node, docutils.nodes.admonition) \ : node.elements=[Paragraph(node.tagname.title(),style=styles['Heading3'])]+gather_elements(node,depth,style=style) elif isinstance (node, docutils.nodes.image): # FIXME handle all the other attributes i=Image(filename=str(node.get("uri"))) if node.get('align'): i.hAlign=node.get('align').upper() node.elements=[i] elif isinstance (node, docutils.nodes.figure): # The sub-elements are the figure and the caption, and't ugly if # they separate node.elements=[KeepTogether(gather_elements(node,depth,style=style))] elif isinstance (node, docutils.nodes.caption): node.elements=[Paragraph(''+gather_pdftext(node,depth)+'',style=style)] elif isinstance (node, docutils.nodes.legend): node.elements=gather_elements(node,depth,style=style) elif isinstance (node, docutils.nodes.sidebar): node.elements=[Table([[ gather_elements(node,depth,style=style)]],style=tstyles['Sidebar'])] elif isinstance (node, docutils.nodes.rubric): node.elements=[Paragraph(gather_pdftext(node,depth),styles['Rubric'])] elif isinstance (node, docutils.nodes.compound): # FIXME think if this is even implementable node.elements=gather_elements(node,depth,style) elif isinstance (node, docutils.nodes.container): # FIXME think if this is even implementable node.elements=gather_elements(node,depth,style) elif isinstance (node, docutils.nodes.substitution_definition): node.elements=[] elif isinstance (node, docutils.nodes.tbody): rows=[gen_elements(n,depth) for n in node.children] t=[] for r in rows: if not r: continue t.append(r) node.elements=[Table(t,style=tstyles['Normal'])] elif isinstance (node, docutils.nodes.footnote): # It seems a footnote contains a label and a series of elements label=Paragraph(gather_pdftext(node.children[0],depth),style) contents=gather_elements(node,depth,style)[1:] decoration['endnotes'].append([label,contents]) node.elements=[] elif isinstance (node, docutils.nodes.label): node.elements=[Paragraph(gather_pdftext(node,depth),style)] elif isinstance (node, docutils.nodes.Text): node.elements=[Paragraph(gather_pdftext(node,depth),style)] elif isinstance (node, docutils.nodes.entry): node.elements=gather_elements(node,depth,style) # FIXME nodes we are ignoring for the moment elif isinstance (node, docutils.nodes.target) \ or isinstance (node, docutils.nodes.footnote) \ or isinstance (node, docutils.nodes.citation) \ or isinstance (node, docutils.nodes.reference) \ or isinstance (node, docutils.nodes.raw) \ : node.elements=[] else: print "Unkn. node (gen_elements): ", node.__class__ print node sys.exit(1) return node.elements def gather_elements (node, depth, in_line_block=False,style=styles['BodyText']): r=[] for n in node.children: r=r+(gen_elements(n,depth,in_line_block,style=style)) return r class Separation(Flowable): " A simple
-like thingie" def wrap(self,w,h): self.w=w return (w,1*cm) def draw(self): self.canv.line(0,0.5*cm,self.w,0.5*cm) class FancyPage(PageTemplate): def __init__(self,_id,pw,ph,tm,bm,lm,rm,hh,fh,head,foot): tw=pw-lm-rm #textframe=Frame(lm,tm+hh,tw,ph-tm-bm-hh-fh) textframe=Frame(lm,tm+hh,tw,ph-tm-bm-hh-fh,topPadding=hh,bottomPadding=fh) self.head=head self.hx=lm self.hy=ph-tm self.foot=foot self.fx=lm self.fy=bm PageTemplate.__init__(self,_id,[textframe]) def beforeDrawPage(self,canv,doc): if self.head: self.head.drawOn(canv,self.hx,self.hy) if self.foot: self.foot.drawOn(canv,self.fx,self.fy) def filltable (rows): # If there is a multicol cell, we need to insert Continuation Cells # to make all rows the same length for y in range(0,len( rows)): for x in range (0,len(rows[y])): cell=rows[y][x] if isinstance (cell,str): continue if cell.get("morecols"): for i in range(0,cell.get("morecols")): rows[y].insert(x+1,"") for y in range(0,len( rows)): for x in range (0,len(rows[y])): cell=rows[y][x] if isinstance (cell,str): continue if cell.get("morerows"): for i in range(0,cell.get("morerows")): rows[y+i+1].insert(x,"") # Create spans list for reportlab's table style spans=[] for y in range(0,len( rows)): for x in range (0,len(rows[y])): cell=rows[y][x] if isinstance (cell,str): continue if cell.get("morecols"): mc=cell.get("morecols") else: mc=0 if cell.get("morerows"): mr=cell.get("morerows") else: mr=0 if mc or mr: spans.append(('SPAN',(x,y),(x+mc,y+mr))) return spans if __name__ == "__main__": input=open(sys.argv[1]).read() import docutils.core doc=docutils.core.publish_doctree(input) elements=gen_elements(doc,0) elements.append(Spacer(1,2*cm)) elements.append(Separation()) for n in decoration['endnotes']: elements.append(Table([[n[0],n[1]]],style=tstyles['Endnote'],colWidths=[endnote_lwidth,None])) head=decoration['header'] foot=decoration['footer'] # This is A4 paper, change it as you wish ps=A4 pw=ps[0] ph=ps[1] # Margins tm=2*cm bm=2*cm lm=3*cm rm=1.5*cm # tw is the text width. We need it to calculate header-footer height tw=pw-lm-rm if head: hh=head.wrap(tw,ph)[1] else: hh=0 if foot: fh=foot.wrap(tw,ph)[1] else: fh=0 # So, now, create the FancyPage with the right sizes and elements FP=FancyPage("fancypage",pw,ph,tm,bm,lm,rm,hh,fh,head,foot) # A basic document for us to write to 'rl_hello_platypus.pdf' pdfdoc = BaseDocTemplate(sys.argv[1]+'.pdf',pageTemplates=[FP],showBoundary=0,pagesize=ps) pdfdoc.build(elements)