1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - Export as PDF action 4 5 @copyright: 2013 by Paul Boddie <paul@boddie.org.uk> 6 @license: GNU GPL (v2 or later), see COPYING.txt for details. 7 """ 8 9 from MoinMoin.action import ActionBase 10 from MoinMoin.wikiutil import escape 11 from MoinSupport import ActionSupport, escattr, getFormatterClass, formatText, get_send_headers 12 from os.path import join 13 import subprocess, os, codecs 14 15 __version__ = "0.1" 16 17 Dependencies = ['page'] 18 19 # Configuration settings. 20 21 # Choose one value for the export mode. 22 23 PDF_EXPORT_MODE = "docbook" 24 #PDF_EXPORT_MODE = "htmldoc" 25 26 # Settings for "docbook" mode. 27 28 XSLT_PROCESSOR = "/usr/bin/xsltproc" 29 FO_PROCESSOR = "/usr/bin/fop" 30 DOCBOOK_STYLESHEET_BASE = "/usr/share/xml/docbook/stylesheet" 31 32 # Tool settings for "docbook" mode. 33 34 DOCBOOK_TO_FO_STYLESHEET = "docbook-xsl/fo/docbook.xsl" 35 36 # Settings for "htmldoc" mode. 37 38 HTMLDOC_PROCESSOR = "/usr/bin/htmldoc" 39 40 # NOTE: From docbook-xsl/fo/param.xsl. 41 42 docbook_paper_sizes = [ 43 "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", 44 "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", 45 "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", 46 "A4landscape", "USletter", "USlandscape", "4A0", "2A0", 47 ] 48 49 docbook_paper_size_labels = { 50 "A4landscape" : "A4 landscape", 51 "USletter" : "US letter", 52 "USlandscape" : "US landscape", 53 "4A0" : "Quadruple A0", 54 "2A0" : "Double A0" 55 } 56 57 # NOTE: From the htmldoc man page. 58 59 htmldoc_paper_sizes = [ 60 "a4", "legal", "letter", "universal" 61 ] 62 63 htmldoc_paper_size_labels = { 64 "a4" : "A4", 65 "legal" : "US legal", 66 "letter" : "US letter", 67 "universal" : "US universal" 68 } 69 70 class ExportPDF(ActionBase, ActionSupport): 71 72 "Export the current page as PDF." 73 74 mode = PDF_EXPORT_MODE 75 76 def _get_paper_sizes(self): 77 if self.mode == "docbook": 78 return docbook_paper_sizes 79 else: 80 return htmldoc_paper_sizes 81 82 def _get_paper_size_labels(self): 83 if self.mode == "docbook": 84 return docbook_paper_size_labels 85 else: 86 return htmldoc_paper_size_labels 87 88 def get_form_html(self, buttons_html): 89 90 "Return the action's form incorporating the 'buttons_html'." 91 92 _ = self._ 93 request = self.request 94 form = self.get_form() 95 96 paper_size = form.get("paper-size", ["A4"])[0] 97 98 paper_size_options = [] 99 paper_size_labels = self._get_paper_size_labels() 100 101 for size in self._get_paper_sizes(): 102 paper_size_options.append('<option value="%s" %s>%s</option>' % ( 103 escattr(size), self._get_selected(size, paper_size), 104 escape(_(paper_size_labels.get(size) or size)) 105 )) 106 107 d = { 108 "paper_size_label" : escape(_("Paper size")), 109 "paper_size_options" : u"".join(paper_size_options), 110 "buttons_html" : buttons_html, 111 } 112 113 return u"""\ 114 <table> 115 <tr> 116 <td class="label"><label>%(paper_size_label)s</label></td> 117 <td><select name="paper-size">%(paper_size_options)s</select></td> 118 </tr> 119 <tr> 120 <td></td> 121 <td class="buttons">%(buttons_html)s</td> 122 </tr> 123 </table> 124 """ % d 125 126 def do_action(self): 127 128 "Attempt to post a comment." 129 130 _ = self._ 131 form = self.get_form() 132 133 paper_size = form.get("paper-size", [""])[0] 134 135 if not paper_size in self._get_paper_sizes(): 136 return 0, _("A paper size must be chosen.") 137 138 if self.mode == "docbook": 139 return self._export_using_docbook(paper_size) 140 elif self.mode == "htmldoc": 141 return self._export_using_htmldoc(paper_size) 142 else: 143 return 0, _("The action must be configured to use a particular PDF generation tool.") 144 145 def _export_using_htmldoc(self, paper_size): 146 147 request = self.request 148 page = self.page 149 150 # Get the page in HTML format. 151 152 fmt = getFormatterClass(request, "text_html")(request) 153 fmt.setPage(page) 154 155 page_as_html = [] 156 append = page_as_html.append 157 158 append("""\ 159 <html> 160 <head> 161 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 162 </head> 163 <body> 164 """) 165 append(formatText(page.get_raw_body(), request, fmt, inhibit_p=False)) 166 append("""\ 167 </body> 168 </html> 169 """) 170 171 # Send the HTML to the htmldoc processor. 172 173 os.environ["HTMLDOC_NOCGI"] = "1" 174 175 p = subprocess.Popen([ 176 HTMLDOC_PROCESSOR, 177 "-t", "pdf", "--quiet", "--webpage", 178 "--size", paper_size, 179 "-" 180 ], 181 shell=False, 182 stdin=subprocess.PIPE, 183 stdout=subprocess.PIPE, 184 stderr=subprocess.PIPE) 185 186 writer = codecs.getwriter("utf-8")(p.stdin) 187 writer.write(u"".join(page_as_html)) 188 189 out, err = p.communicate() 190 191 retcode = p.wait() 192 193 if retcode != 0: 194 return 0, err 195 196 self._write_pdf(out) 197 return 1, None 198 199 def _export_using_docbook(self, paper_size): 200 201 request = self.request 202 page = self.page 203 204 # Get the page in DocBook format. 205 206 fmt = getFormatterClass(request, "text_docbook")(request) 207 fmt.setPage(page) 208 209 # The DocBook formatter needs to pretend a full document is being made. 210 211 page_as_docbook = [] 212 append = page_as_docbook.append 213 214 append(fmt.startDocument(page.page_name)) 215 append(fmt.startContent()) 216 append(formatText(page.get_raw_body(), request, fmt, inhibit_p=False).encode("utf-8")) 217 append(fmt.endContent()) 218 append(fmt.endDocument()) 219 220 # Send the DocBook XML to the XSLT processor. 221 222 p1 = subprocess.Popen([ 223 XSLT_PROCESSOR, 224 "-stringparam", "fop1.extensions", "1", 225 "--stringparam", "paper.type", paper_size, 226 join(DOCBOOK_STYLESHEET_BASE, DOCBOOK_TO_FO_STYLESHEET), 227 "-" 228 ], 229 shell=False, 230 stdin=subprocess.PIPE, 231 stdout=subprocess.PIPE, 232 stderr=subprocess.PIPE) 233 234 p1.stdin.write("".join(page_as_docbook)) 235 p1.stdin.close() 236 237 # Pipe the XML-FO output to the FO processor. 238 239 p2 = subprocess.Popen([ 240 FO_PROCESSOR, 241 "-fo", "-", 242 "-pdf", "-", 243 ], 244 shell=False, 245 stdin=p1.stdout, 246 stdout=subprocess.PIPE, 247 stderr=subprocess.PIPE) 248 249 out, err = p2.communicate() 250 251 retcode = p1.wait() 252 253 if retcode != 0: 254 return 0, err 255 256 retcode = p2.wait() 257 258 if retcode != 0: 259 return 0, err 260 261 self._write_pdf(out) 262 return 1, None 263 264 def _write_pdf(self, out): 265 request = self.request 266 267 send_headers = get_send_headers(request) 268 headers = ["Content-Type: application/pdf"] 269 send_headers(headers) 270 request.write(out) 271 272 def render_success(self, msg, msgtype=None): 273 274 """ 275 Render neither 'msg' nor 'msgtype' since a resource has already been 276 produced. 277 NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. 278 """ 279 280 pass 281 282 # Action invocation function. 283 284 def execute(pagename, request): 285 ExportPDF(pagename, request).render() 286 287 # vim: tabstop=4 expandtab shiftwidth=4