removed obsolete issues (many of them fixed with AE)
[docutils.git] / sandbox / dreamcatcher / rlpdf / rlpdf.py
blobd0dafc02ecd783e0ebb2ab22e2f8d7c4c017bfc5
1 #! /usr/bin/env python
3 """
4 :Author: Engelbert Gruber
5 :Contact: goodger@users.sourceforge.net
6 :Revision: $Revision$
7 :Date: $Date$
8 :Copyright: This module has been placed in the public domain.
10 Simple pdf writer.
12 The output uses reportlabs module.
14 Some stylesheet is needed.
15 """
17 __docformat__ = 'reStructuredText'
20 import time
21 from types import ListType, TupleType, UnicodeType
22 from docutils import writers, nodes, languages
24 from stylesheet import getStyleSheet
25 from rltemplate import RLDocTemplate
27 from reportlab.lib.styles import ParagraphStyle
28 from reportlab.lib.enums import *
29 from reportlab.lib.pagesizes import A4
30 from reportlab.platypus import *
31 from reportlab.platypus.para import Paragraph
32 from reportlab.lib import colors
33 from reportlab.lib.units import inch
34 from StringIO import StringIO
36 class Writer(writers.Writer):
38 supported = ('pdf','rlpdf')
39 """Formats this writer supports."""
41 settings_spec = (
42 'PDF-Specific Options',
43 None,
44 (('Format for footnote references: one of "superscript" or '
45 '"brackets". Default is "brackets".',
46 ['--footnote-references'],
47 {'choices': ['superscript', 'brackets'], 'default': 'brackets',
48 'metavar': '<FORMAT>'}),))
50 output = None
51 """Final translated form of `document`."""
53 def __init__(self):
54 writers.Writer.__init__(self)
55 self.translator_class = PDFTranslator
57 def translate(self):
58 visitor = self.translator_class(self.document)
59 self.document.walkabout(visitor)
60 self.story = visitor.as_what()
61 self.output = self.record()
63 def record(self):
64 from reportlab.platypus import SimpleDocTemplate
65 out = StringIO()
66 doc = SimpleDocTemplate(out, pagesize=A4)
67 doc.build(self.story)
68 return out.getvalue()
70 def lower(self):
71 return 'pdf'
73 class PDFTranslator(nodes.NodeVisitor):
75 def __init__(self, doctree):
76 self.settings = settings = doctree.settings
77 self.styleSheet = getStyleSheet()
78 nodes.NodeVisitor.__init__(self, doctree)
79 self.language = languages.get_language(doctree.settings.language_code)
80 self.in_docinfo = None
81 self.head = []
82 self.body = []
83 self.foot = []
84 self.sectionlevel = 0
85 self.context = []
86 self.topic_class = ''
87 self.story = []
88 self.bulletText = '\267' # maybe move this into stylesheet.
89 self.bulletlevel = 0
91 def as_what(self):
92 return self.story
94 def encode(self, text):
95 """Encode special characters in `text` & return."""
96 if type(text) is UnicodeType:
97 text = text.replace(u'\u2020', u' ')
98 text = text.replace(u'\xa0', u' ')
99 text = text.encode('utf-8')
100 #text = text.replace("&", "&amp;")
101 #text = text.replace("<", '"')
102 #text = text.replace('"', "(quot)")
103 #text = text.replace(">", '"')
104 # footnotes have character values above 128 ?
105 return text
107 def append_para(self, text, in_style='Normal', bulletText=None):
108 if type(text) in (ListType, TupleType):
109 text = ''.join([self.encode(t) for t in text])
110 style = self.styleSheet[in_style]
111 self.story.append(Paragraph(self.encode(text), style, \
112 bulletText=bulletText, context=self.styleSheet))
114 def starttag(self, node, tagname, suffix='\n', **attributes):
115 atts = {}
116 for (name, value) in attributes.items():
117 atts[name.lower()] = value
118 for att in ('class',): # append to node attribute
119 if node.has_key(att):
120 if atts.has_key(att):
121 atts[att] = node[att] + ' ' + atts[att]
122 for att in ('id',): # node attribute overrides
123 if node.has_key(att):
124 atts[att] = node[att]
125 attlist = atts.items()
126 attlist.sort()
127 parts = [tagname]
128 for name, value in attlist:
129 if value is None: # boolean attribute
130 parts.append(name.lower())
131 elif isinstance(value, ListType):
132 values = [str(v) for v in value]
133 parts.append('%s="%s"' % (name.lower(),
134 self.encode(' '.join(values))))
135 else:
136 parts.append('%s="%s"' % (name.lower(),
137 self.encode(str(value))))
138 return '<%s>%s' % (' '.join(parts), suffix)
140 def visit_Text(self, node):
141 self.context.append('#text')
142 self.body.append(node.astext())
144 def depart_Text(self, node):
145 self.context.pop()
147 def visit_admonition(self, node, name):
148 pass
150 def depart_admonition(self):
151 pass
153 def visit_attention(self, node):
154 self.visit_admonition(node, 'attention')
156 def depart_attention(self, node):
157 self.depart_admonition()
159 def visit_author(self, node):
160 self.visit_docinfo_item(node, 'author')
162 def depart_author(self, node):
163 self.depart_docinfo_item()
165 def visit_address(self, node):
166 self.visit_docinfo_item(node, 'address')
168 def depart_address(self, node):
169 self.depart_docinfo_item()
171 def visit_version(self, node):
172 self.visit_docinfo_item(node, 'version')
174 def depart_version(self, node):
175 self.depart_docinfo_item()
177 def visit_system_message(self, node):
178 pass
180 def depart_system_message(self, node):
181 pass
183 def visit_term(self, node):
184 self.context.append('dt')
185 self.body.append(self.starttag(node, 'dt', ''))
187 def depart_term(self, node):
188 # Closes on visit_definition
189 self.context.pop()
191 def visit_authors(self, node):
192 pass
194 def depart_authors(self, node):
195 pass
197 def visit_block_quote(self, node):
198 pass
200 def depart_block_quote(self, node):
201 pass
203 def visit_bullet_list(self, node):
204 self.context.append(len(self.body))
205 self.context.append('ul')
206 self.body.append('<ul bulletText="%s">' % self.bulletText)
208 def depart_bullet_list(self, node):
209 self.context.pop()
210 self.body.append('</ul>')
211 start = self.context.pop()
212 if not 'ul' in self.context:
213 self.append_para(self.body[start:])
214 self.body = self.body[:start]
216 def visit_caption(self, node):
217 pass
219 def depart_caption(self, node):
220 pass
222 def visit_caution(self, node):
223 self.visit_admonition(node, 'caution')
225 def depart_caution(self, node):
226 self.depart_admonition()
228 def visit_citation(self, node):
229 pass
231 def depart_citation(self, node):
232 pass
234 def visit_citation_reference(self, node):
235 pass
237 def depart_citation_reference(self, node):
238 pass
240 def visit_classifier(self, node):
241 pass
243 def depart_classifier(self, node):
244 pass
246 def visit_colspec(self, node):
247 pass
249 def depart_colspec(self, node):
250 pass
252 def visit_contact(self, node):
253 self.visit_docinfo_item(node, 'contact')
255 def depart_contact(self, node):
256 self.depart_docinfo_item()
258 def visit_copyright(self, node):
259 self.visit_docinfo_item(node, 'copyright')
261 def depart_copyright(self, node):
262 self.depart_docinfo_item()
264 def visit_danger(self, node):
265 self.visit_admonition(node, 'danger')
267 def depart_danger(self, node):
268 self.depart_admonition()
270 def visit_date(self, node):
271 self.visit_docinfo_item(node, 'date')
273 def depart_date(self, node):
274 self.depart_docinfo_item()
276 def visit_definition(self, node):
277 self.body.append('</dt>')
278 self.context.append('dd')
279 self.body.append(self.starttag(node, 'dd'))
281 def depart_definition(self, node):
282 self.context.pop()
283 self.body.append('</dd>')
285 def visit_definition_list(self, node):
286 self.context.append(len(self.body))
287 self.context.append('dl')
288 self.body.append(self.starttag(node, 'dl'))
290 def depart_definition_list(self, node):
291 self.context.pop()
292 self.body.append('</dl>')
293 start = self.context.pop()
294 if not 'dl' in self.context:
295 self.append_para(self.body[start:])
296 self.body = self.body[:start]
298 def visit_definition_list_item(self, node):
299 pass
301 def depart_definition_list_item(self, node):
302 pass
304 def visit_description(self, node):
305 pass
307 def depart_description(self, node):
308 pass
310 def visit_docinfo(self, node):
311 self.context.append(len(self.body))
312 self.in_docinfo = 1
314 def depart_docinfo(self, node):
315 start = self.context.pop()
316 docinfo = self.body[start:]
317 self.body = self.body[:start]
318 self.append_para(docinfo)
319 self.in_docinfo = None
321 def visit_docinfo_item(self, node, name):
322 self.body.append('<para style="DocInfo"><b>%s: </b>' % self.language.labels[name])
324 def depart_docinfo_item(self):
325 self.body.append('</para>')
327 def visit_doctest_block(self, node):
328 self.visit_literal_block(node)
330 def depart_doctest_block(self, node):
331 self.depart_literal_block(node)
333 def visit_line_block(self, node):
334 self.visit_literal_block(node)
336 def depart_line_block(self, node):
337 self.depart_literal_block(node)
339 def visit_document(self, node):
340 pass
342 def depart_document(self, node):
343 pass
345 def visit_emphasis(self, node):
346 self.context.append('i')
347 self.body.append('<i>')
349 def depart_emphasis(self, node):
350 self.context.pop()
351 self.body.append('</i>')
353 def visit_entry(self, node):
354 pass
356 def depart_entry(self, node):
357 pass
359 def visit_enumerated_list(self, node):
360 self.context.append(len(self.body))
361 self.context.append('ol')
362 self.body.append('<ol>')
364 def depart_enumerated_list(self, node):
365 self.context.pop()
366 self.body.append('</ol>')
367 start = self.context.pop()
368 if not 'ol' in self.context:
369 self.append_para(self.body[start:])
370 self.body = self.body[:start]
372 def visit_error(self, node):
373 self.visit_admonition(node, 'error')
375 def depart_error(self, node):
376 self.depart_admonition()
378 def visit_field(self, node):
379 self.body.append('<para>')
381 def depart_field(self, node):
382 self.body.append('</para>')
384 def visit_field_argument(self, node):
385 pass
387 def depart_field_argument(self, node):
388 pass
390 def visit_field_list(self, node):
391 self.context.append(len(self.body))
392 self.body.append('<para>')
394 def depart_field_list(self, node):
395 start = self.context.pop()
396 self.body.append('</para>')
397 self.append_para(self.body[start:])
398 self.body = self.body[:start]
400 def visit_field_name(self, node):
401 self.body.append('<b>')
403 def depart_field_name(self, node):
404 self.body.append(': </b>')
406 def visit_field_body(self, node):
407 pass
409 def depart_field_body(self, node):
410 pass
412 def visit_footnote(self, node):
413 self.context.append('footnotes')
414 self.footnote_backrefs(node)
416 def footnote_backrefs(self, node):
417 if self.settings.footnote_backlinks and node.hasattr('backrefs'):
418 backrefs = node['backrefs']
419 if len(backrefs) == 1:
420 self.context.append("%s%s" % (self.starttag({}, 'setLink', '', destination=node['id']), \
421 '</setLink>'))
422 self.context.append("%s%s" % (self.starttag({}, 'link', '', destination=backrefs[0]), \
423 '</link>'))
424 else:
425 i = 1
426 backlinks = []
427 for backref in backrefs:
428 backlinks.append("%s%s%s" % (self.starttag({}, 'link', '', destination=backref),
430 '</link>'))
431 i += 1
432 self.context.append(' <i>(%s)</i> ' % ', '.join(backlinks))
433 self.context.append("%s%s" % (self.starttag({}, 'setLink', '', destination=node['id']), \
434 '</setLink>'))
435 else:
436 self.context.append("%s%s" % (self.starttag({}, 'setLink', '', destination=node['id']), \
437 '</setLink>'))
438 self.context.append('')
440 def footnote_backrefs_depart(self, node):
441 if not self.context and self.body:
442 self.append_para(self.body)
443 self.body = []
445 def depart_footnote(self, node):
446 self.context.pop()
447 self.footnote_backrefs_depart(node)
449 def visit_footnote_reference(self, node):
450 # for backrefs
451 if self.settings.footnote_backlinks and node.has_key('id'):
452 self.body.append(self.starttag(node, 'setLink', '', destination=node['id']))
453 self.context.append('</setLink>')
454 else:
455 self.context.append('')
457 href = ''
458 if node.has_key('refid'):
459 href = node['refid']
460 elif node.has_key('refname'):
461 href = self.document.nameids[node['refname']]
462 format = self.settings.footnote_references
463 if format == 'brackets':
464 suffix = '['
465 self.context.append(']')
466 elif format == 'superscript':
467 suffix = '<super>'
468 self.context.append('</super>')
469 else: # shouldn't happen
470 suffix = '???'
471 self.content.append('???')
472 self.body.append(self.starttag(node, 'link', suffix, destination=href))
474 def depart_footnote_reference(self, node):
475 self.body.append(self.context.pop())
476 self.body.append('</link>')
477 self.body.append(self.context.pop())
479 def visit_hint(self, node):
480 self.visit_admonition(node, 'hint')
482 def depart_hint(self, node):
483 self.depart_admonition()
485 def visit_image(self, node):
486 pass
488 def depart_image(self, node):
489 pass
491 def visit_important(self, node):
492 self.visit_admonition(node, 'important')
494 def depart_important(self, node):
495 self.depart_admonition()
497 def visit_interpreted(self, node):
498 pass
500 def depart_interpreted(self, node):
501 pass
503 def visit_label(self, node):
504 if 'footnotes' in self.context:
505 self.body.append('[')
507 def depart_label(self, node):
508 if 'footnotes' in self.context:
509 self.body.append(']')
510 self.body.append(self.context.pop())
511 self.body.append(self.context.pop())
512 self.body.append(' ')
514 def visit_legend(self, node):
515 pass
517 def depart_legend(self, node):
518 pass
520 def visit_list_item(self, node):
521 self.context.append('li')
522 self.body.append('<li>')
524 def depart_list_item(self, node):
525 self.context.pop()
526 self.body.append('</li>')
528 def visit_literal(self, node):
529 self.context.append('literal')
531 def depart_literal(self, node):
532 self.context.pop()
534 def visit_literal_block(self, node):
535 self.story.append(Preformatted(node.astext(), self.styleSheet['Code']))
536 raise nodes.SkipNode
538 def depart_literal_block(self, node):
539 pass
541 def visit_meta(self, node):
542 self.head.append(self.starttag(node, 'meta', **node.attributes))
544 def depart_meta(self, node):
545 pass
547 def visit_note(self, node):
548 self.visit_admonition(node, 'note')
550 def depart_note(self, node):
551 self.depart_admonition()
553 def visit_option(self, node):
554 pass
556 def depart_option(self, node):
557 pass
559 def visit_option_argument(self, node):
560 pass
562 def depart_option_argument(self, node):
563 pass
565 def visit_option_group(self, node):
566 pass
568 def depart_option_group(self, node):
569 pass
571 def visit_option_list(self, node):
572 self.context.append(len(self.body))
573 self.context.append('option_list')
575 def depart_option_list(self, node):
576 self.context.pop()
577 start = self.context.pop()
578 if not 'option_list' in self.context:
579 self.append_para(self.body[start:])
580 self.body = self.body[:start]
582 def visit_option_list_item(self, node):
583 pass
585 def depart_option_list_item(self, node):
586 pass
588 def visit_option_string(self, node):
589 pass
591 def depart_option_string(self, node):
592 pass
594 def visit_organization(self, node):
595 self.visit_docinfo_item(node, 'organization')
597 def depart_organization(self, node):
598 self.depart_docinfo_item()
600 def visit_paragraph(self, node):
601 self.context.append(len(self.body))
602 self.context.append('p')
604 def depart_paragraph(self, node):
605 self.context.pop()
606 start = self.context.pop()
607 if not self.context and self.body:
608 self.append_para(self.body[start:])
609 self.body = self.body[:start]
611 def visit_problematic(self, node):
612 pass
614 def depart_problematic(self, node):
615 pass
617 def visit_raw(self, node):
618 if node.has_key('format') and node['format'] == 'html':
619 self.body.append(node.astext())
620 raise nodes.SkipNode
622 def visit_target(self, node):
623 if not (node.has_key('refuri') or node.has_key('refid')
624 or node.has_key('refname')):
625 href = ''
626 if node.has_key('id'):
627 href = node['id']
628 elif node.has_key('name'):
629 href = node['name']
630 self.body.append("%s%s" % (self.starttag(node, 'setLink', '', destination=href), \
631 '</setLink>'))
632 raise nodes.SkipNode
634 def depart_target(self, node):
635 pass
637 def visit_reference(self, node):
638 self.context.append('a')
639 if node.has_key('refuri'):
640 href = node['refuri']
641 self.body.append(self.starttag(node, 'a', '', href=href))
642 self.context.append('</a>')
643 else:
644 if node.has_key('id'):
645 self.body.append(self.starttag({}, 'setLink', '', destination=node['id']))
646 self.context.append('</setLink>')
647 if node.has_key('refid'):
648 href = node['refid']
649 elif node.has_key('refname'):
650 href = self.document.nameids[node['refname']]
651 self.body.append(self.starttag(node, 'link', '', destination=href))
652 self.context.append('</link>')
654 def depart_reference(self, node):
655 if node.has_key('id') and \
656 not node.has_key('refuri'):
657 self.body.append(self.context.pop())
658 self.body.append(self.context.pop())
659 self.context.pop()
661 def visit_revision(self, node):
662 self.visit_docinfo_item(node, 'revision')
664 def depart_revision(self, node):
665 self.depart_docinfo_item()
667 def visit_row(self, node):
668 pass
670 def depart_row(self, node):
671 pass
673 def visit_section(self, node):
674 self.sectionlevel += 1
676 def depart_section(self, node):
677 self.sectionlevel -= 1
679 def visit_status(self, node):
680 self.visit_docinfo_item(node, 'status')
682 def depart_status(self, node):
683 self.depart_docinfo_item()
685 def visit_strong(self, node):
686 self.context.append('b')
687 self.body.append('<b>')
689 def depart_strong(self, node):
690 self.context.pop()
691 self.body.append('</b>')
693 def visit_subtitle(self, node):
694 self.context.append(len(self.body))
695 self.context.append('subtitle')
697 def depart_subtitle(self, node):
698 style = self.context.pop()
699 start = self.context.pop()
700 self.append_para(self.body[start:], style)
701 self.body = self.body[:start]
703 def visit_title(self, node):
704 atts = {}
705 self.context.append(len(self.body))
706 self.context.append('title')
707 if isinstance(node.parent, nodes.topic):
708 self.context.append('')
709 self.topic_class = 'topic-title'
710 elif self.sectionlevel == 0:
711 self.context.append('title')
712 else:
713 self.context.append("h%s" % self.sectionlevel)
715 if self.context[-1] != 'title':
716 if node.parent.hasattr('id'):
717 self.context.append('</setLink>')
718 self.body.append(self.starttag({}, 'setLink', '', destination=node.parent['id']))
719 if node.hasattr('refid'):
720 self.context.append('</link>')
721 self.body.append(self.starttag({}, 'link', '', destination=node['refid']))
722 else:
723 self.context.append('')
725 def depart_title(self, node):
726 if node.hasattr('refid'):
727 self.body.append(self.context.pop())
728 if node.parent.hasattr('id'):
729 self.body.append(self.context.pop())
730 style = self.context.pop()
731 self.context.pop()
732 if isinstance(node.parent, nodes.topic):
733 style = self.topic_class
734 start = self.context.pop()
735 self.append_para(self.body[start:], style)
736 self.body = self.body[:start]
738 def unimplemented_visit(self, node):
739 raise NotImplementedError('visiting unimplemented node type: %s'
740 % node.__class__.__name__)
742 def visit_topic(self, node):
743 if node.hasattr('id'):
744 self.context.append('</setLink>')
745 self.body.append(self.starttag({}, 'setLink', '', destination=node['id']))
747 def depart_topic(self, node):
748 if node.hasattr('id'):
749 self.body.append(self.context.pop())
751 def visit_generated(self, node):
752 pass
754 def depart_generated(self, node):
755 pass
757 def invisible_visit(self, node):
758 """Invisible nodes should be ignored."""
759 pass
761 def visit_comment(self, node):
762 raise nodes.SkipNode
764 depart_comment = invisible_visit
765 visit_substitution_definition = visit_comment
766 depart_substitution_definition = depart_comment
767 visit_figure = visit_comment
768 depart_figure = depart_comment
770 visit_sidebar = invisible_visit
771 visit_warning = invisible_visit
772 visit_tip = invisible_visit
773 visit_tbody = invisible_visit
774 visit_thead = invisible_visit
775 visit_tgroup = invisible_visit
776 visit_table = invisible_visit
777 visit_title_reference = invisible_visit
778 visit_transition = invisible_visit
779 visit_pending = invisible_visit
780 depart_pending = invisible_visit
781 depart_transition = invisible_visit
782 depart_title_reference = invisible_visit
783 depart_table = invisible_visit
784 depart_tgroup = invisible_visit
785 depart_thead = invisible_visit
786 depart_tbody = invisible_visit
787 depart_tip = invisible_visit
788 depart_warning = invisible_visit
789 depart_sidebar = invisible_visit