diff --git a/rst2pdf/__init__.py b/rst2pdf/__init__.py index 119cd3424..4d66b5bd3 100644 --- a/rst2pdf/__init__.py +++ b/rst2pdf/__init__.py @@ -1,6 +1,7 @@ # See LICENSE.txt for licensing terms try: import pkg_resources + try: version = pkg_resources.get_distribution('rst2pdf').version except pkg_resources.ResolutionError: diff --git a/rst2pdf/aafigure_directive.py b/rst2pdf/aafigure_directive.py index 9a64759b4..86fd6e6c7 100644 --- a/rst2pdf/aafigure_directive.py +++ b/rst2pdf/aafigure_directive.py @@ -34,7 +34,8 @@ from .opt_imports import aafigure from .log import log -WARNED=False +WARNED = False + class Aanode(Element): children = () @@ -52,9 +53,8 @@ def gen_flowable(self, style_options): # explicit :option: always precedes options.update(self.options) visitor = aafigure.process( - '\n'.join(self.content), - aafigure.pdf.PDFOutputVisitor, - options=options) + '\n'.join(self.content), aafigure.pdf.PDFOutputVisitor, options=options, + ) return renderPDF.GraphicsFlowable(visitor.drawing) @@ -62,20 +62,21 @@ class Aafig(rst.Directive): """ Directive to insert an ASCII art figure to be rendered by aafigure. """ + has_content = True required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False option_spec = dict( - scale = float, - line_width = float, - background = str, - foreground = str, - fill = str, - name = str, - aspect = float, - textual = directives.flag, - proportional = directives.flag, + scale=float, + line_width=float, + background=str, + foreground=str, + fill=str, + name=str, + aspect=float, + textual=directives.flag, + proportional=directives.flag, ) def run(self): @@ -87,8 +88,10 @@ def run(self): if aafigure is not None: return [Aanode(self.content, self.options)] if not WARNED: - log.error('To render the aafigure directive correctly, please install aafigure') - WARNED=True + log.error( + 'To render the aafigure directive correctly, please install aafigure' + ) + WARNED = True return [literal_block(text='\n'.join(self.content))] diff --git a/rst2pdf/basenodehandler.py b/rst2pdf/basenodehandler.py index 318a6ea88..172c0ba80 100644 --- a/rst2pdf/basenodehandler.py +++ b/rst2pdf/basenodehandler.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # See LICENSE.txt for licensing terms -#$URL$ -#$Date$ -#$Revision$ +# $URL$ +# $Date$ +# $Revision$ ''' This module provides one useful class: NodeHandler @@ -77,6 +77,7 @@ class creation behavior by defining a couple of helper functions. _baseclass can be explicitly set inside the class definition, in which case MetaHelper will not override it. ''' + def __new__(clstype, name, bases, clsdict): # Our base class is the first base in the class definition which # uses MetaHelper, or None if no such base exists. @@ -144,8 +145,10 @@ def _classinit(cls): t = repr(target) old = repr(cls.dispatchdict[target]) new = repr(self) - log.debug('Dispatch handler %s for node type %s overridden by %s' % - (old, t, new)) + log.debug( + 'Dispatch handler %s for node type %s overridden by %s' + % (old, t, new) + ) cls.dispatchdict[target] = self @staticmethod @@ -159,11 +162,10 @@ def getclassname(obj): def log_unknown(self, node, during): if not hasattr(self, 'unkn_node'): self.unkn_node = set() - cln=self.getclassname(node) + cln = self.getclassname(node) if not cln in self.unkn_node: self.unkn_node.add(cln) - log.warning("Unkn. node (self.%s): %s [%s]", - during, cln, nodeid(node)) + log.warning("Unkn. node (self.%s): %s [%s]", during, cln, nodeid(node)) try: log.debug(node) except (UnicodeDecodeError, UnicodeEncodeError): @@ -214,9 +216,12 @@ def getstyle(self, client, node, style): if client.styles.StyleSheet.has_key(node['classes'][0]): style = client.styles[node['classes'][0]] else: - log.info("Unknown class %s, ignoring. [%s]", - node['classes'][0], nodeid(node)) - except TypeError: # Happens when a docutils.node.Text reaches here + log.info( + "Unknown class %s, ignoring. [%s]", + node['classes'][0], + nodeid(node), + ) + except TypeError: # Happens when a docutils.node.Text reaches here pass if style is None or style == client.styles['bodytext']: @@ -228,13 +233,12 @@ def getelements(self, client, node, style): elements = self.gather_elements(client, node, style) # Make all the sidebar cruft unreachable - #if style.__dict__.get('float','None').lower() !='none': - #node.elements=[Sidebar(node.elements,style)] - #elif 'width' in style.__dict__: + # if style.__dict__.get('float','None').lower() !='none': + # node.elements=[Sidebar(node.elements,style)] + # elif 'width' in style.__dict__: if 'width' in style.__dict__: - elements = [BoundByWidth(style.width, - elements, style, mode="shrink")] + elements = [BoundByWidth(style.width, elements, style, mode="shrink")] return elements @@ -247,13 +251,13 @@ def elemdispatch(self, client, node, style=None): try: for i in node['ids']: client.pending_targets.append(i) - except TypeError: #Happens with docutils.node.Text + except TypeError: # Happens with docutils.node.Text pass elements = self.getelements(client, node, style) if node.line and client.debugLinesPdf: - elements.insert(0,TocEntry(client.depth-1,'LINE-%s'%node.line)) + elements.insert(0, TocEntry(client.depth - 1, 'LINE-%s' % node.line)) node.elements = elements return elements @@ -270,8 +274,11 @@ def get_text(self, client, node, replaceEnt): def apply_smartypants(self, text, smarty, node): # Try to be clever about when to use smartypants - if node.__class__ in (docutils.nodes.paragraph, - docutils.nodes.block_quote, docutils.nodes.title): + if node.__class__ in ( + docutils.nodes.paragraph, + docutils.nodes.block_quote, + docutils.nodes.title, + ): return smartypants(text, smarty) return text diff --git a/rst2pdf/config.py b/rst2pdf/config.py index 2934ce9fd..a461ade31 100644 --- a/rst2pdf/config.py +++ b/rst2pdf/config.py @@ -28,7 +28,6 @@ def getValue(section, key, default=None): class ConfigError(Exception): - def __init__(self, modulename, msg): self.modulename = modulename self.msg = msg diff --git a/rst2pdf/counter_off_role.py b/rst2pdf/counter_off_role.py index b5dfdd99d..d9859a2fa 100644 --- a/rst2pdf/counter_off_role.py +++ b/rst2pdf/counter_off_role.py @@ -2,10 +2,13 @@ from docutils.nodes import Text, target + def counter_fn(name, rawtext, text, lineno, inliner, options={}, content=[]): return [], [] -counter_fn.content=False + +counter_fn.content = False from docutils.parsers.rst import roles + roles.register_canonical_role('counter', counter_fn) diff --git a/rst2pdf/counter_role.py b/rst2pdf/counter_role.py index 74088b9a6..a05cb0f29 100644 --- a/rst2pdf/counter_role.py +++ b/rst2pdf/counter_role.py @@ -7,24 +7,28 @@ class CounterNode(Text): children = () + def __init__(self, data, rawsource=''): if ':' in data: self.name, value = [s.lower() for s in data.split(':')][:2] - self.value=int(value) + self.value = int(value) else: - self.name=data.lower() - self.value=values.get(self.name,1) - values[self.name]=self.value+1 + self.name = data.lower() + self.value = values.get(self.name, 1) + values[self.name] = self.value + 1 def astext(self): return str(self.value) + def counter_fn(name, rawtext, text, lineno, inliner, options={}, content=[]): - n=CounterNode(text) - s='%s-%s'%(n.name, n.value) - return [target(ids=[s]),n], [] + n = CounterNode(text) + s = '%s-%s' % (n.name, n.value) + return [target(ids=[s]), n], [] -counter_fn.content=True + +counter_fn.content = True from docutils.parsers.rst import roles + roles.register_canonical_role('counter', counter_fn) diff --git a/rst2pdf/createpdf.py b/rst2pdf/createpdf.py index b319a1ec0..babdb7148 100644 --- a/rst2pdf/createpdf.py +++ b/rst2pdf/createpdf.py @@ -73,16 +73,17 @@ from reportlab.platypus.doctemplate import IndexingFlowable from reportlab.platypus.flowables import _listWrapOn, _Container from reportlab.pdfbase.pdfdoc import PDFPageLabel -#from reportlab.lib.enums import * -#from reportlab.lib.units import * -#from reportlab.lib.pagesizes import * + +# from reportlab.lib.enums import * +# from reportlab.lib.units import * +# from reportlab.lib.pagesizes import * from . import config from rst2pdf import counter_role, oddeven_directive -from rst2pdf import pygments_code_block_directive # code-block directive +from rst2pdf import pygments_code_block_directive # code-block directive from rst2pdf import flowables -from rst2pdf.flowables import * # our own reportlab flowables +from rst2pdf.flowables import * # our own reportlab flowables from rst2pdf.sinker import Sinker from rst2pdf.image import MyImage, missing from rst2pdf.aafigure_directive import Aanode @@ -91,8 +92,14 @@ from rst2pdf import styles as sty from rst2pdf.nodehandlers import nodehandlers from rst2pdf.languages import get_language_available -from rst2pdf.opt_imports import Paragraph, BaseHyphenator, PyHyphenHyphenator, \ - DCWHyphenator, sphinx as sphinx_module, wordaxe +from rst2pdf.opt_imports import ( + Paragraph, + BaseHyphenator, + PyHyphenHyphenator, + DCWHyphenator, + sphinx as sphinx_module, + wordaxe, +) # Template engine for covers import jinja2 @@ -100,78 +107,81 @@ if six.PY3: unicode = str -numberingstyles={ 'arabic': 'ARABIC', - 'roman': 'ROMAN_UPPER', - 'lowerroman': 'ROMAN_LOWER', - 'alpha': 'LETTERS_UPPER', - 'loweralpha': 'LETTERS_LOWER' } +numberingstyles = { + 'arabic': 'ARABIC', + 'roman': 'ROMAN_UPPER', + 'lowerroman': 'ROMAN_LOWER', + 'alpha': 'LETTERS_UPPER', + 'loweralpha': 'LETTERS_LOWER', +} class RstToPdf(object): - - def __init__(self, stylesheets=[], - language='en_US', - header=None, - footer=None, - inlinelinks=False, - breaklevel=1, - font_path=[], - style_path=[], - fit_mode='shrink', - background_fit_mode='center', - sphinx=False, - smarty='0', - baseurl=None, - repeat_table_rows=False, - footnote_backlinks=True, - inline_footnotes=False, - real_footnotes=False, - def_dpi=300, - show_frame=False, - highlightlang='python', # this one is only used by Sphinx - basedir=os.getcwd(), - splittables=False, - blank_first_page=False, - first_page_on_right=False, - breakside='odd', - custom_cover='cover.tmpl', - floating_images=False, - numbered_links=False, - section_header_depth=2, - raw_html=False, - strip_elements_with_classes=[] - ): - self.debugLinesPdf=False - self.depth=0 - self.breakside=breakside - self.first_page_on_right=first_page_on_right - self.blank_first_page=blank_first_page - self.splittables=splittables - self.basedir=basedir - self.language, self.docutils_language = get_language_available( - language)[:2] + def __init__( + self, + stylesheets=[], + language='en_US', + header=None, + footer=None, + inlinelinks=False, + breaklevel=1, + font_path=[], + style_path=[], + fit_mode='shrink', + background_fit_mode='center', + sphinx=False, + smarty='0', + baseurl=None, + repeat_table_rows=False, + footnote_backlinks=True, + inline_footnotes=False, + real_footnotes=False, + def_dpi=300, + show_frame=False, + highlightlang='python', # this one is only used by Sphinx + basedir=os.getcwd(), + splittables=False, + blank_first_page=False, + first_page_on_right=False, + breakside='odd', + custom_cover='cover.tmpl', + floating_images=False, + numbered_links=False, + section_header_depth=2, + raw_html=False, + strip_elements_with_classes=[], + ): + self.debugLinesPdf = False + self.depth = 0 + self.breakside = breakside + self.first_page_on_right = first_page_on_right + self.blank_first_page = blank_first_page + self.splittables = splittables + self.basedir = basedir + self.language, self.docutils_language = get_language_available(language)[:2] self.doc_title = "" self.doc_title_clean = "" self.doc_subtitle = "" self.doc_author = "" self.header = header self.footer = footer - self.custom_cover=custom_cover - self.floating_images=floating_images - self.decoration = {'header': header, - 'footer': footer, - 'endnotes': [], - 'extraflowables':[]} + self.custom_cover = custom_cover + self.floating_images = floating_images + self.decoration = { + 'header': header, + 'footer': footer, + 'endnotes': [], + 'extraflowables': [], + } # find base path if hasattr(sys, 'frozen'): self.PATH = abspath(dirname(sys.executable)) else: self.PATH = abspath(dirname(__file__)) - - self.font_path=font_path - self.style_path=style_path - self.def_dpi=def_dpi + self.font_path = font_path + self.style_path = style_path + self.def_dpi = def_dpi self.loadStyles(stylesheets) self.docutils_languages = {} @@ -213,18 +223,23 @@ def __init__(self, stylesheets=[], if sphinx and sphinx_module: import sphinx.roles from rst2pdf.sphinxnodes import sphinxhandlers + self.highlightlang = highlightlang self.gen_pdftext, self.gen_elements = sphinxhandlers(self) else: # These rst2pdf extensions conflict with sphinx - directives.register_directive('code-block', pygments_code_block_directive.code_block_directive) - directives.register_directive('code', pygments_code_block_directive.code_block_directive) + directives.register_directive( + 'code-block', pygments_code_block_directive.code_block_directive, + ) + directives.register_directive( + 'code', pygments_code_block_directive.code_block_directive + ) self.gen_pdftext, self.gen_elements = nodehandlers(self) self.sphinx = sphinx if not self.styles.languages: - self.styles.languages=[] + self.styles.languages = [] if self.language: self.styles.languages.append(self.language) self.styles['bodytext'].language = self.language @@ -245,37 +260,43 @@ def __init__(self, stylesheets=[], except Exception: # hyphenators may not always be available or crash, # e.g. wordaxe issue 2809074 (http://is.gd/16lqs) - log.warning("Can't load wordaxe DCW hyphenator" - " for German language, trying Py hyphenator instead") + log.warning( + "Can't load wordaxe DCW hyphenator" + " for German language, trying Py hyphenator instead" + ) else: continue try: wordaxe.hyphRegistry[lang] = PyHyphenHyphenator(lang) except Exception: - log.warning("Can't load wordaxe Py hyphenator" - " for language %s, trying base hyphenator", lang) + log.warning( + "Can't load wordaxe Py hyphenator" + " for language %s, trying base hyphenator", + lang, + ) else: continue try: wordaxe.hyphRegistry[lang] = BaseHyphenator(lang) except Exception: log.warning("Can't even load wordaxe base hyphenator") - log.info('hyphenation by default in %s , loaded %s', + log.info( + 'hyphenation by default in %s , loaded %s', self.styles['bodytext'].language, - ','.join(self.styles.languages)) + ','.join(self.styles.languages), + ) - self.pending_targets=[] - self.targets=[] + self.pending_targets = [] + self.targets = [] - def loadStyles(self, styleSheets=None ): + def loadStyles(self, styleSheets=None): if styleSheets is None: - styleSheets=[] + styleSheets = [] - self.styles = sty.StyleSheet(styleSheets, - self.font_path, - self.style_path, - def_dpi=self.def_dpi) + self.styles = sty.StyleSheet( + styleSheets, self.font_path, self.style_path, def_dpi=self.def_dpi + ) def style_language(self, style): """Return language corresponding to this style.""" @@ -298,8 +319,7 @@ def style_language(self, style): def text_for_label(self, label, style): """Translate text for label.""" try: - text = self.docutils_languages[ - self.style_language(style)].labels[label] + text = self.docutils_languages[self.style_language(style)].labels[label] except KeyError: text = label.capitalize() return text @@ -308,7 +328,8 @@ def text_for_bib_field(self, field, style): """Translate text for bibliographic fields.""" try: text = self.docutils_languages[ - self.style_language(style)].bibliographic_fields[field] + self.style_language(style) + ].bibliographic_fields[field] except KeyError: text = field return text + ":" @@ -316,8 +337,9 @@ def text_for_bib_field(self, field, style): def author_separator(self, style): """Return separator string for authors.""" try: - sep = self.docutils_languages[ - self.style_language(style)].author_separators[0] + sep = self.docutils_languages[self.style_language(style)].author_separators[ + 0 + ] except KeyError: sep = ';' return sep + " " @@ -329,29 +351,29 @@ def styleToTags(self, style): try: s = self.styles[style] - r1=['') - r2=[''] + r2 = [''] if s.strike: r1.append('') - r2.insert(0,'') + r2.insert(0, '') if s.underline: r1.append('') - r2.insert(0,'') + r2.insert(0, '') return [''.join(r1), ''.join(r2)] except KeyError: log.warning('Unknown class %s', style) return None - def styleToFont(self, style): '''Takes a style name, returns a font tag for it, like "". Used for inline @@ -359,13 +381,14 @@ def styleToFont(self, style): try: s = self.styles[style] - r=['') return ''.join(r) except KeyError: @@ -373,15 +396,14 @@ def styleToFont(self, style): return None def gather_pdftext(self, node, replaceEnt=True): - return ''.join([self.gen_pdftext(n, replaceEnt) - for n in node.children]) + return ''.join([self.gen_pdftext(n, replaceEnt) for n in node.children]) def gather_elements(self, node, style=None): if style is None: style = self.styles.styleForNode(node) r = [] if 'float' in style.__dict__: - style = None # Don't pass floating styles to children! + style = None # Don't pass floating styles to children! for n in node.children: r.extend(self.gen_elements(n, style=style)) return r @@ -398,7 +420,8 @@ def bullet_for_node(self, node): start = 1 if node.parent.get('bullet') or isinstance( - node.parent, docutils.nodes.bullet_list): + node.parent, docutils.nodes.bullet_list + ): b = node.parent.get('bullet', '*') if b == "None": b = "" @@ -412,14 +435,21 @@ def bullet_for_node(self, node): elif node.parent.get('enumtype') == 'upperroman': b = toRoman(node.parent.children.index(node) + start).upper() + '.' elif node.parent.get('enumtype') == 'loweralpha': - b = 'abcdefghijklmnopqrstuvwxyz'[node.parent.children.index(node) - + start - 1] + '.' + b = ( + 'abcdefghijklmnopqrstuvwxyz'[ + node.parent.children.index(node) + start - 1 + ] + + '.' + ) elif node.parent.get('enumtype') == 'upperalpha': - b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[node.parent.children.index(node) - + start - 1] + '.' + b = ( + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[ + node.parent.children.index(node) + start - 1 + ] + + '.' + ) else: - log.critical("Unknown kind of list_item %s [%s]", - node.parent, nodeid(node)) + log.critical("Unknown kind of list_item %s [%s]", node.parent, nodeid(node)) return b, t def filltable(self, rows): @@ -443,14 +473,14 @@ def filltable(self, rows): # to make all rows the same length for y in range(0, len(rows)): - for x in range(len(rows[y])-1, -1, -1): + for x in range(len(rows[y]) - 1, -1, -1): cell = rows[y][x] if isinstance(cell, str): continue if cell.get("morecols"): for i in range(0, cell.get("morecols")): - e=docutils.nodes.entry("") - e["morerows"] = cell.get("morerows",0) + e = docutils.nodes.entry("") + e["morerows"] = cell.get("morerows", 0) rows[y].insert(x + 1, e) for y in range(0, len(rows)): @@ -462,7 +492,6 @@ def filltable(self, rows): for i in range(0, cell.get("morerows")): rows[y + i + 1].insert(x, "") - # If a row is shorter, add empty cells at the right end maxw = max([len(r) for r in rows]) for r in rows: @@ -492,18 +521,24 @@ def PreformattedFit(self, text, style): """Preformatted section that gets horizontally compressed if needed.""" # Pass a ridiculous size, then it will shrink to what's available # in the frame - return BoundByWidth(2000*cm, + return BoundByWidth( + 2000 * cm, content=[XXPreformatted(text, style)], - mode=self.fit_mode, style=style) - - def createPdf(self, text=None, - source_path=None, - output=None, - doctree=None, - compressed=False, - # This adds entries to the PDF TOC - # matching the rst source lines - debugLinesPdf=False): + mode=self.fit_mode, + style=style, + ) + + def createPdf( + self, + text=None, + source_path=None, + output=None, + doctree=None, + compressed=False, + # This adds entries to the PDF TOC + # matching the rst source lines + debugLinesPdf=False, + ): """Create a PDF from text (ReST input), or doctree (docutil nodes) and save it in outfile. @@ -512,26 +547,32 @@ def createPdf(self, text=None, or a file object), the data is saved there. """ - self.decoration = {'header': self.header, - 'footer': self.footer, - 'endnotes': [], - 'extraflowables': []} + self.decoration = { + 'header': self.header, + 'footer': self.footer, + 'endnotes': [], + 'extraflowables': [], + } - self.pending_targets=[] - self.targets=[] + self.pending_targets = [] + self.targets = [] self.debugLinesPdf = debugLinesPdf if doctree is None: if text is not None: if self.language: - settings_overrides={'language_code': self.docutils_language} + settings_overrides = {'language_code': self.docutils_language} else: - settings_overrides={} - settings_overrides['strip_elements_with_classes']=self.strip_elements_with_classes - self.doctree = docutils.core.publish_doctree(text, + settings_overrides = {} + settings_overrides[ + 'strip_elements_with_classes' + ] = self.strip_elements_with_classes + self.doctree = docutils.core.publish_doctree( + text, source_path=source_path, - settings_overrides=settings_overrides) + settings_overrides=settings_overrides, + ) log.debug(self.doctree) else: log.error('Error: createPdf needs a text or a doctree') @@ -542,12 +583,14 @@ def createPdf(self, text=None, if self.numbered_links: # Transform all links to sections so they show numbers from .sectnumlinks import SectNumFolder, SectRefExpander + snf = SectNumFolder(self.doctree) self.doctree.walk(snf) srf = SectRefExpander(self.doctree, snf.sectnums) self.doctree.walk(srf) if self.strip_elements_with_classes: from docutils.transforms.universal import StripClassesAndElements + sce = StripClassesAndElements(self.doctree) sce.apply() @@ -555,53 +598,61 @@ def createPdf(self, text=None, # Find cover template, save it in cover_file jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader([ - self.basedir, os.path.expanduser('~/.rst2pdf'), - os.path.join(self.PATH,'templates')]), - autoescape=jinja2.select_autoescape(['html', 'xml']) + loader=jinja2.FileSystemLoader( + [ + self.basedir, + os.path.expanduser('~/.rst2pdf'), + os.path.join(self.PATH, 'templates'), + ] + ), + autoescape=jinja2.select_autoescape(['html', 'xml']), ) try: template = jinja_env.get_template(self.custom_cover) except jinja2.TemplateNotFound: - log.error("Can't find cover template %s, using default"%self.custom_cover) + log.error("Can't find cover template %s, using default" % self.custom_cover) template = jinja_env.get_template('cover.tmpl') # Feed data to the template, get restructured text. - cover_text = template.render(title=self.doc_title, - subtitle=self.doc_subtitle) + cover_text = template.render(title=self.doc_title, subtitle=self.doc_subtitle) # This crashes sphinx because .. class:: in sphinx is # something else. Ergo, pdfbuilder does it in its own way. if not self.sphinx: - elements = self.gen_elements( - publish_secondary_doctree(cover_text, self.doctree, source_path)) + elements + elements = ( + self.gen_elements( + publish_secondary_doctree(cover_text, self.doctree, source_path) + ) + + elements + ) if self.blank_first_page: - elements.insert(0,PageBreak()) + elements.insert(0, PageBreak()) # Put the endnotes at the end ;-) endnotes = self.decoration['endnotes'] if endnotes: - elements.append(MySpacer(1, 2*cm)) + elements.append(MySpacer(1, 2 * cm)) elements.append(Separation()) for n in self.decoration['endnotes']: t_style = TableStyle(self.styles['endnote'].commands) colWidths = self.styles['endnote'].colWidths - elements.append(DelayedTable([[n[0], n[1]]], - style=t_style, colWidths=colWidths)) + elements.append( + DelayedTable([[n[0], n[1]]], style=t_style, colWidths=colWidths) + ) if self.floating_images: # Handle images with alignment more like in HTML - new_elem=[] - for i,e in enumerate(elements[::-1]): - if (isinstance (e, MyImage) and e.image.hAlign != 'CENTER' - and new_elem): + new_elem = [] + for i, e in enumerate(elements[::-1]): + if isinstance(e, MyImage) and e.image.hAlign != 'CENTER' and new_elem: # This is an image where flowables should wrap # around it - popped=new_elem.pop() - new_elem.append(ImageAndFlowables(e,popped, - imageSide=e.image.hAlign.lower())) + popped = new_elem.pop() + new_elem.append( + ImageAndFlowables(e, popped, imageSide=e.image.hAlign.lower()) + ) else: new_elem.append(e) @@ -615,8 +666,7 @@ def createPdf(self, text=None, FP = FancyPage("fancypage", head, foot, self) def cleantags(s): - re.sub(r'<[^>]*?>', '', - unicode(s).strip()) + re.sub(r'<[^>]*?>', '', unicode(s).strip()) pdfdoc = FancyDocTemplate( output, @@ -625,16 +675,17 @@ def cleantags(s): pagesize=self.styles.ps, title=self.doc_title_clean, author=self.doc_author, - pageCompression=compressed) - pdfdoc.client =self + pageCompression=compressed, + ) + pdfdoc.client = self # Handle totally empty documents (Issue #547) if not elements: elements.append(Paragraph("", style=self.styles['base'])) if getattr(self, 'mustMultiBuild', False): # Force a multibuild pass - if not isinstance(elements[-1],UnhappyOnce): - log.info ('Forcing second pass so Total pages work') + if not isinstance(elements[-1], UnhappyOnce): + log.info('Forcing second pass so Total pages work') elements.append(UnhappyOnce()) while True: try: @@ -650,54 +701,53 @@ def cleantags(s): if getattr(self, 'mustMultiBuild', False): # Force a multibuild pass - if not isinstance(elements[-1],UnhappyOnce): - log.info ('Forcing second pass so Total pages work') + if not isinstance(elements[-1], UnhappyOnce): + log.info('Forcing second pass so Total pages work') elements.append(UnhappyOnce()) continue ## Rearrange footnotes if needed if self.real_footnotes: - newStory=[] - fnPile=[] + newStory = [] + fnPile = [] for e in elements: - if getattr(e,'isFootnote',False): + if getattr(e, 'isFootnote', False): # Add it to the pile - #if not isinstance (e, MySpacer): + # if not isinstance (e, MySpacer): fnPile.append(e) elif getattr(e, '_atTop', False) or isinstance( - e, (UnhappyOnce, MyPageBreak)): + e, (UnhappyOnce, MyPageBreak) + ): if fnPile: fnPile.insert(0, Separation()) newStory.append(Sinker(fnPile)) newStory.append(e) - fnPile=[] + fnPile = [] else: newStory.append(e) - elements = newStory+fnPile + elements = newStory + fnPile for e in elements: if hasattr(e, '_postponed'): - delattr(e,'_postponed') + delattr(e, '_postponed') self.real_footnotes = False continue - - break except ValueError as v: # FIXME: cross-document links come through here, which means # an extra pass per cross-document reference. Which sucks. - #if v.args and str(v.args[0]).startswith('format not resolved'): - #missing=str(v.args[0]).split(' ')[-1] - #log.error('Adding missing reference to %s and rebuilding. This is slow!'%missing) - #elements.append(Reference(missing)) - #for e in elements: - #if hasattr(e,'_postponed'): - #delattr(e,'_postponed') - #else: - #raise + # if v.args and str(v.args[0]).startswith('format not resolved'): + # missing=str(v.args[0]).split(' ')[-1] + # log.error('Adding missing reference to %s and rebuilding. This is slow!'%missing) + # elements.append(Reference(missing)) + # for e in elements: + # if hasattr(e,'_postponed'): + # delattr(e,'_postponed') + # else: + # raise raise - #doc = SimpleDocTemplate("phello.pdf") - #doc.build(elements) + # doc = SimpleDocTemplate("phello.pdf") + # doc.build(elements) for fn in self.to_unlink: try: os.unlink(fn) @@ -707,8 +757,8 @@ def cleantags(s): from reportlab.platypus import doctemplate -class FancyDocTemplate(BaseDocTemplate): +class FancyDocTemplate(BaseDocTemplate): def afterFlowable(self, flowable): if isinstance(flowable, Heading): @@ -719,14 +769,13 @@ def afterFlowable(self, flowable): pagenum = setPageCounter() self.notify('TOCEntry', (level, text, pagenum, parent_id, node)) - - def handle_flowable(self,flowables): + def handle_flowable(self, flowables): '''try to handle one flowable from the front of list flowables.''' # this method is copied from reportlab - #allow document a chance to look at, modify or ignore - #the object(s) about to be processed + # allow document a chance to look at, modify or ignore + # the object(s) about to be processed self.filterFlowables(flowables) self.handle_breakBefore(flowables) @@ -736,82 +785,95 @@ def handle_flowable(self,flowables): if f is None: return - if isinstance(f,PageBreak): - if isinstance(f,SlowPageBreak): + if isinstance(f, PageBreak): + if isinstance(f, SlowPageBreak): self.handle_pageBreak(slow=1) else: self.handle_pageBreak() self.afterFlowable(f) - elif isinstance(f,ActionFlowable): + elif isinstance(f, ActionFlowable): f.apply(self) self.afterFlowable(f) else: frame = self.frame canv = self.canv - #try to fit it then draw it + # try to fit it then draw it if frame.add(f, canv, trySplit=self.allowSplitting): - if not isinstance(f,FrameActionFlowable): + if not isinstance(f, FrameActionFlowable): self._curPageFlowableCount += 1 self.afterFlowable(f) - doctemplate._addGeneratedContent(flowables,frame) + doctemplate._addGeneratedContent(flowables, frame) else: if self.allowSplitting: # see if this is a splittable thing - S = frame.split(f,canv) + S = frame.split(f, canv) n = len(S) else: n = 0 if n: - if not isinstance(S[0],(PageBreak,SlowPageBreak,ActionFlowable)): + if not isinstance(S[0], (PageBreak, SlowPageBreak, ActionFlowable)): if frame.add(S[0], canv, trySplit=0): self._curPageFlowableCount += 1 self.afterFlowable(S[0]) - doctemplate._addGeneratedContent(flowables,frame) + doctemplate._addGeneratedContent(flowables, frame) else: ident = "Splitting error(n==%d) on page %d in\n%s" % ( - n, self.page, self._fIdent(f, 60, frame)) - #leave to keep apart from the raise + n, + self.page, + self._fIdent(f, 60, frame), + ) + # leave to keep apart from the raise raise LayoutError(ident) del S[0] - for i,f in enumerate(S): - flowables.insert(i,f) # put split flowables back on the list + for i, f in enumerate(S): + flowables.insert(i, f) # put split flowables back on the list else: - if hasattr(f,'_postponed') and f._postponed > 4: - ident = "Flowable %s%s too large on page %d in frame %r%s of template %r" % ( - self._fIdent(f, 60, frame), doctemplate._fSizeString(f),self.page, - self.frame.id, self.frame._aSpaceString(), self.pageTemplate.id) - #leave to keep apart from the raise + if hasattr(f, '_postponed') and f._postponed > 4: + ident = ( + "Flowable %s%s too large on page %d in frame %r%s of template %r" + % ( + self._fIdent(f, 60, frame), + doctemplate._fSizeString(f), + self.page, + self.frame.id, + self.frame._aSpaceString(), + self.pageTemplate.id, + ) + ) + # leave to keep apart from the raise raise LayoutError(ident) # this ought to be cleared when they are finally drawn! f._postponed = 1 mbe = getattr(self, '_multiBuildEdits', None) if mbe: mbe((delattr, f, '_postponed')) - flowables.insert(0, f) # put the flowable back + flowables.insert(0, f) # put the flowable back self.handle_frameEnd() -_counter=0 -_counterStyle='arabic' +_counter = 0 +_counterStyle = 'arabic' -class PageCounter(Flowable): +class PageCounter(Flowable): def __init__(self, number=0, style='arabic'): - self.style=str(style).lower() - self.number=int(number) + self.style = str(style).lower() + self.number = int(number) Flowable.__init__(self) def wrap(self, availWidth, availHeight): global _counter, _counterStyle - _counterStyle=self.style - _counter=self.number + _counterStyle = self.style + _counter = self.number return (self.width, self.height) def drawOn(self, canvas, x, y, _sW): pass + flowables.PageCounter = PageCounter + def setPageCounter(counter=None, style=None): global _counter, _counterStyle @@ -821,40 +883,46 @@ def setPageCounter(counter=None, style=None): if style is not None: _counterStyle = style - if _counterStyle=='lowerroman': - ptext=toRoman(_counter).lower() - elif _counterStyle=='roman': - ptext=toRoman(_counter).upper() - elif _counterStyle=='alpha': - ptext='ABCDEFGHIJKLMNOPQRSTUVWXYZ'[_counter%26] - elif _counterStyle=='loweralpha': - ptext='abcdefghijklmnopqrstuvwxyz'[_counter%26] + if _counterStyle == 'lowerroman': + ptext = toRoman(_counter).lower() + elif _counterStyle == 'roman': + ptext = toRoman(_counter).upper() + elif _counterStyle == 'alpha': + ptext = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[_counter % 26] + elif _counterStyle == 'loweralpha': + ptext = 'abcdefghijklmnopqrstuvwxyz'[_counter % 26] else: - ptext=str(_counter) + ptext = str(_counter) return ptext + class MyContainer(_Container, Flowable): pass + class UnhappyOnce(IndexingFlowable): '''An indexing flowable that is only unsatisfied once. If added to a story, it will make multiBuild run at least two passes. Useful for ###Total###''' - _unhappy=True + + _unhappy = True + def isSatisfied(self): if self._unhappy: - self._unhappy= False + self._unhappy = False return False return True def draw(self): pass + class HeaderOrFooter(object): """ A helper object for FancyPage (below) HeaderOrFooter handles operations which are common to both headers and footers """ + def __init__(self, items=None, isfooter=False, client=None): self.items = items if isfooter: @@ -874,7 +942,9 @@ def prepare(self, pageobj, canv, doc): if not items: items = pageobj.template.get(self.defaultloc) if items: - items = self.client.gen_elements(publish_secondary_doctree(items, self.client.doctree, None)) + items = self.client.gen_elements( + publish_secondary_doctree(items, self.client.doctree, None) + ) if items: if isinstance(items, list): items = items[:] @@ -886,7 +956,7 @@ def prepare(self, pageobj, canv, doc): items.insert(0, Separation()) else: items.append(Separation()) - _, height = _listWrapOn(items, pageobj.tw, canv) + _, height = _listWrapOn(items, pageobj.tw, canv) self.prepared = height and items return height @@ -894,7 +964,7 @@ def replaceTokens(self, elems, canv, doc, smarty): """Put doc_title/page number/etc in text of header/footer.""" # Make sure page counter is up to date - pnum=setPageCounter() + pnum = setPageCounter() def replace(text): # Ensure text is unicode @@ -907,29 +977,27 @@ def replace(text): text = text.replace(u'###Page###', pnum) if '###Total###' in text: text = text.replace(u'###Total###', str(self.totalpages)) - self.client.mustMultiBuild=True + self.client.mustMultiBuild = True text = text.replace(u"###Title###", doc.title) - text = text.replace(u"###Section###", - getattr(canv, 'sectName', '')) - text = text.replace(u"###SectNum###", - getattr(canv, 'sectNum', '')) + text = text.replace(u"###Section###", getattr(canv, 'sectName', '')) + text = text.replace(u"###SectNum###", getattr(canv, 'sectNum', '')) text = smartypants(text, smarty) return text - for i,e in enumerate(elems): + for i, e in enumerate(elems): # TODO: implement a search/replace for arbitrary things if isinstance(e, Paragraph): text = replace(e.text) elems[i] = Paragraph(text, e.style) elif isinstance(e, DelayedTable): - data=deepcopy(e.data) - for r,row in enumerate(data): - for c,cell in enumerate(row): - if isinstance (cell, list): - data[r][c]=self.replaceTokens(cell, canv, doc, smarty) + data = deepcopy(e.data) + for r, row in enumerate(data): + for c, cell in enumerate(row): + if isinstance(cell, list): + data[r][c] = self.replaceTokens(cell, canv, doc, smarty) else: - row[c]=self.replaceTokens([cell,], canv, doc, smarty)[0] - elems[i]=DelayedTable(data, e._colWidths, e.style) + row[c] = self.replaceTokens([cell,], canv, doc, smarty)[0] + elems[i] = DelayedTable(data, e._colWidths, e.style) elif isinstance(e, BoundByWidth): for index, item in enumerate(e.content): @@ -938,9 +1006,9 @@ def replace(text): elems[i] = e elif isinstance(e, OddEven): - odd=self.replaceTokens([e.odd,], canv, doc, smarty)[0] - even=self.replaceTokens([e.even,], canv, doc, smarty)[0] - elems[i]=OddEven(odd, even) + odd = self.replaceTokens([e.odd,], canv, doc, smarty)[0] + even = self.replaceTokens([e.even,], canv, doc, smarty)[0] + elems[i] = OddEven(odd, even) return elems def draw(self, pageobj, canv, doc, x, y, width, height): @@ -969,7 +1037,6 @@ def __init__(self, _id, _head, _foot, client): self.image_cache = {} PageTemplate.__init__(self, _id, []) - def draw_background(self, which, canv): ''' Draws a background and/or foreground image on each page which uses the template. @@ -984,7 +1051,7 @@ def draw_background(self, which, canv): on the page, using stylesheets to align and/or set the offset. ''' - uri=self.template[which] + uri = self.template[which] info = self.image_cache.get(uri) if info is None: fname, _, _ = MyImage.split_uri(uri) @@ -993,10 +1060,10 @@ def draw_background(self, which, canv): log.error("Missing %s image file: %s", which, uri) return try: - w, h, kind = MyImage.size_for_node(dict(uri=uri, ), self.client) + w, h, kind = MyImage.size_for_node(dict(uri=uri,), self.client) except ValueError: # Broken image, return arbitrary stuff - uri=missing + uri = missing w, h, kind = 100, 100, 'direct' pw, ph = self.styles.pw, self.styles.ph @@ -1011,7 +1078,9 @@ def draw_background(self, which, canv): x, y = 0, 0 sw, sh = pw, h else: - log.error('Unknown background fit mode: %s'% self.client.background_fit_mode) + log.error( + 'Unknown background fit mode: %s' % self.client.background_fit_mode + ) # Do scale anyway x, y = 0, 0 sw, sh = pw, ph @@ -1031,7 +1100,6 @@ def is_left(self, page_num): val = not val return val - def beforeDrawPage(self, canv, doc): """Do adjustments to the page according to where we are in the document. @@ -1044,13 +1112,12 @@ def beforeDrawPage(self, canv, doc): styles = self.styles self.tw = styles.pw - styles.lm - styles.rm - styles.gm # What page template to use? - tname = canv.__dict__.get('templateName', - self.styles.firstTemplate) + tname = canv.__dict__.get('templateName', self.styles.firstTemplate) self.template = self.styles.pageTemplates[tname] - canv.templateName=tname + canv.templateName = tname doct = getattr(canv, '_doctemplate', None) - canv._doctemplate = None # to make _listWrapOn work + canv._doctemplate = None # to make _listWrapOn work if doc.page == 1: _counter = 0 @@ -1069,12 +1136,19 @@ def beforeDrawPage(self, canv, doc): self.fx = styles.lm self.fy = styles.bm - self.th = styles.ph - styles.tm - styles.bm - self.hh \ - - self.fh - styles.ts - styles.bs + self.th = ( + styles.ph + - styles.tm + - styles.bm + - self.hh + - self.fh + - styles.ts + - styles.bs + ) # Adjust gutter margins - if self.is_left(doc.page): # Left page + if self.is_left(doc.page): # Left page x1 = styles.lm - else: # Right page + else: # Right page x1 = styles.lm + styles.gm y1 = styles.bm + self.fh + styles.bs @@ -1089,31 +1163,37 @@ def beforeDrawPage(self, canv, doc): # This is the default in SmartFrame. At some point in the future we # may want to change this to 0. frame.append(6) - self.frames.append(SmartFrame(self, - styles.adjustUnits(frame[0], self.tw) + x1, - styles.adjustUnits(frame[1], self.th) + y1, - styles.adjustUnits(frame[2], self.tw), - styles.adjustUnits(frame[3], self.th), - leftPadding=styles.adjustUnits(frame[4], self.tw), - bottomPadding=styles.adjustUnits(frame[5], self.th), - rightPadding=styles.adjustUnits(frame[6], self.tw), - topPadding=styles.adjustUnits(frame[7], self.th), - showBoundary=self.show_frame)) + self.frames.append( + SmartFrame( + self, + styles.adjustUnits(frame[0], self.tw) + x1, + styles.adjustUnits(frame[1], self.th) + y1, + styles.adjustUnits(frame[2], self.tw), + styles.adjustUnits(frame[3], self.th), + leftPadding=styles.adjustUnits(frame[4], self.tw), + bottomPadding=styles.adjustUnits(frame[5], self.th), + rightPadding=styles.adjustUnits(frame[6], self.tw), + topPadding=styles.adjustUnits(frame[7], self.th), + showBoundary=self.show_frame, + ) + ) canv.firstSect = True canv._pagenum = doc.page for frame in self.frames: - frame._pagenum=doc.page + frame._pagenum = doc.page def afterDrawPage(self, canv, doc): """Draw header/footer.""" # Adjust for gutter margin - canv.addPageLabel(canv._pageNumber-1,numberingstyles[_counterStyle],_counter) + canv.addPageLabel( + canv._pageNumber - 1, numberingstyles[_counterStyle], _counter + ) - log.info('Page %s [%s]'%(_counter,doc.page)) - if self.is_left(doc.page): # Left page + log.info('Page %s [%s]' % (_counter, doc.page)) + if self.is_left(doc.page): # Left page hx = self.hx fx = self.fx - else: # Right Page + else: # Right Page hx = self.hx + self.styles.gm fx = self.fx + self.styles.gm @@ -1129,215 +1209,386 @@ def parse_commandline(): parser = OptionParser() - parser.add_option('--config', dest='configfile', metavar='FILE', - help='Config file to use. Default=~/.rst2pdf/config') - - parser.add_option('-o', '--output', dest='output', metavar='FILE', - help='Write the PDF to FILE') - - def_ssheets = ','.join([expanduser(p) for p in - config.getValue("general", "stylesheets", "").split(',')]) - parser.add_option('-s', '--stylesheets', dest='style', - type='string', action='append', - metavar='STYLESHEETS', default=[def_ssheets], - help='A comma-separated list of custom stylesheets. Default="%s"' - % def_ssheets) - - def_sheetpath = os.pathsep.join([expanduser(p) for p in - config.getValue("general", "stylesheet_path", "").split(os.pathsep)]) - parser.add_option('--stylesheet-path', dest='stylepath', - metavar='FOLDER%sFOLDER%s...%sFOLDER'%((os.pathsep, )*3), + parser.add_option( + '--config', + dest='configfile', + metavar='FILE', + help='Config file to use. Default=~/.rst2pdf/config', + ) + + parser.add_option( + '-o', '--output', dest='output', metavar='FILE', help='Write the PDF to FILE', + ) + + def_ssheets = ','.join( + [ + expanduser(p) + for p in config.getValue("general", "stylesheets", "").split(',') + ] + ) + parser.add_option( + '-s', + '--stylesheets', + dest='style', + type='string', + action='append', + metavar='STYLESHEETS', + default=[def_ssheets], + help='A comma-separated list of custom stylesheets. Default="%s"' % def_ssheets, + ) + + def_sheetpath = os.pathsep.join( + [ + expanduser(p) + for p in config.getValue("general", "stylesheet_path", "").split(os.pathsep) + ] + ) + parser.add_option( + '--stylesheet-path', + dest='stylepath', + metavar='FOLDER%sFOLDER%s...%sFOLDER' % ((os.pathsep,) * 3), default=def_sheetpath, help='A list of folders to search for stylesheets,' - ' separated using "%s". Default="%s"' %(os.pathsep, def_sheetpath)) + ' separated using "%s". Default="%s"' % (os.pathsep, def_sheetpath), + ) def_compressed = config.getValue("general", "compressed", False) - parser.add_option('-c', '--compressed', dest='compressed', - action="store_true", default=def_compressed, - help='Create a compressed PDF. Default=%s'%def_compressed) - - parser.add_option('--print-stylesheet', dest='printssheet', - action="store_true", default=False, - help='Print the default stylesheet and exit') - - parser.add_option('--font-folder', dest='ffolder', metavar='FOLDER', - help='Search this folder for fonts. (Deprecated)') - - def_fontpath = os.pathsep.join([expanduser(p) for p in - config.getValue("general", "font_path", "").split(os.pathsep)]) - parser.add_option('--font-path', dest='fpath', - metavar='FOLDER%sFOLDER%s...%sFOLDER'%((os.pathsep, )*3), + parser.add_option( + '-c', + '--compressed', + dest='compressed', + action="store_true", + default=def_compressed, + help='Create a compressed PDF. Default=%s' % def_compressed, + ) + + parser.add_option( + '--print-stylesheet', + dest='printssheet', + action="store_true", + default=False, + help='Print the default stylesheet and exit', + ) + + parser.add_option( + '--font-folder', + dest='ffolder', + metavar='FOLDER', + help='Search this folder for fonts. (Deprecated)', + ) + + def_fontpath = os.pathsep.join( + [ + expanduser(p) + for p in config.getValue("general", "font_path", "").split(os.pathsep) + ] + ) + parser.add_option( + '--font-path', + dest='fpath', + metavar='FOLDER%sFOLDER%s...%sFOLDER' % ((os.pathsep,) * 3), default=def_fontpath, help='A list of folders to search for fonts, separated using "%s".' - ' Default="%s"' % (os.pathsep, def_fontpath)) - - def_baseurl = urlunparse(['file',os.getcwd()+os.sep,'','','','']) - parser.add_option('--baseurl', dest='baseurl', metavar='URL', + ' Default="%s"' % (os.pathsep, def_fontpath), + ) + + def_baseurl = urlunparse(['file', os.getcwd() + os.sep, '', '', '', '']) + parser.add_option( + '--baseurl', + dest='baseurl', + metavar='URL', default=def_baseurl, - help='The base URL for relative URLs. Default="%s"'%def_baseurl) + help='The base URL for relative URLs. Default="%s"' % def_baseurl, + ) def_lang = config.getValue("general", "language", 'en_US') - parser.add_option('-l', '--language', metavar='LANG', - default=def_lang, dest='language', + parser.add_option( + '-l', + '--language', + metavar='LANG', + default=def_lang, + dest='language', help='Language to be used for hyphenation' - ' and docutils localizations. Default="%s"' % def_lang) + ' and docutils localizations. Default="%s"' % def_lang, + ) def_header = config.getValue("general", "header") - parser.add_option('--header', metavar='HEADER', - default=def_header, dest='header', + parser.add_option( + '--header', + metavar='HEADER', + default=def_header, + dest='header', help='Page header if not specified in the document.' - ' Default="%s"' % def_header) + ' Default="%s"' % def_header, + ) def_footer = config.getValue("general", "footer") - parser.add_option('--footer', metavar='FOOTER', - default=def_footer, dest='footer', + parser.add_option( + '--footer', + metavar='FOOTER', + default=def_footer, + dest='footer', help='Page footer if not specified in the document.' - ' Default="%s"' % def_footer) - - def_section_header_depth = config.getValue("general","section_header_depth",2) - parser.add_option('--section-header-depth', metavar='N', - default=def_section_header_depth, dest='section_header_depth', - help = '''Sections up to this depth will be used in the header and footer's replacement of ###Section###. Default=%s''' % def_section_header_depth) + ' Default="%s"' % def_footer, + ) + + def_section_header_depth = config.getValue("general", "section_header_depth", 2) + parser.add_option( + '--section-header-depth', + metavar='N', + default=def_section_header_depth, + dest='section_header_depth', + help='''Sections up to this depth will be used in the header and footer's replacement of ###Section###. Default=%s''' + % def_section_header_depth, + ) def_smartquotes = config.getValue("general", "smartquotes", "0") - parser.add_option("--smart-quotes", metavar="VALUE", - default=def_smartquotes, dest="smarty", + parser.add_option( + "--smart-quotes", + metavar="VALUE", + default=def_smartquotes, + dest="smarty", help='Try to convert ASCII quotes, ellipses and dashes' - ' to the typographically correct equivalent. For details,' - ' read the man page or the manual. Default="%s"' % def_smartquotes) + ' to the typographically correct equivalent. For details,' + ' read the man page or the manual. Default="%s"' % def_smartquotes, + ) def_fit = config.getValue("general", "fit_mode", "shrink") - parser.add_option('--fit-literal-mode', metavar='MODE', - default=def_fit, dest='fit_mode', + parser.add_option( + '--fit-literal-mode', + metavar='MODE', + default=def_fit, + dest='fit_mode', help='What to do when a literal is too wide. One of error,' - ' overflow,shrink,truncate. Default="%s"' % def_fit) - - def_fit_background = config.getValue("general", "background_fit_mode", - "center") - parser.add_option('--fit-background-mode', metavar='MODE', - default=def_fit_background, dest='background_fit_mode', + ' overflow,shrink,truncate. Default="%s"' % def_fit, + ) + + def_fit_background = config.getValue("general", "background_fit_mode", "center") + parser.add_option( + '--fit-background-mode', + metavar='MODE', + default=def_fit_background, + dest='background_fit_mode', help='How to fit the background image to the page.' - ' One of scale, scale_width or center. Default="%s"' % def_fit_background) - - parser.add_option('--inline-links', action="store_true", - dest='inlinelinks', default=False, - help='Shows target between parentheses instead of active link.') - - parser.add_option('--repeat-table-rows', action="store_true", - dest='repeattablerows', default=False, - help='Repeats header row for each split table.') + ' One of scale, scale_width or center. Default="%s"' % def_fit_background, + ) + + parser.add_option( + '--inline-links', + action="store_true", + dest='inlinelinks', + default=False, + help='Shows target between parentheses instead of active link.', + ) + + parser.add_option( + '--repeat-table-rows', + action="store_true", + dest='repeattablerows', + default=False, + help='Repeats header row for each split table.', + ) def_raw_html = config.getValue("general", "raw_html", False) - parser.add_option('--raw-html', action="store_true", - dest='raw_html', default=def_raw_html, - help='Support embeddig raw HTML. Default=%s' % def_raw_html) - - parser.add_option('-q', '--quiet', action="store_true", - dest='quiet', default=False, - help='Print less information.') - - parser.add_option('-v', '--verbose', action="store_true", - dest='verbose', default=False, - help='Print debug information.') - - parser.add_option('--very-verbose', action="store_true", - dest='vverbose', default=False, - help='Print even more debug information.') - - parser.add_option('--version', action="store_true", - dest='version', default=False, - help='Print version number and exit.') - - def_footnote_backlinks = config.getValue("general", - "footnote_backlinks", True) - parser.add_option('--no-footnote-backlinks', action='store_false', - dest='footnote_backlinks', default=def_footnote_backlinks, + parser.add_option( + '--raw-html', + action="store_true", + dest='raw_html', + default=def_raw_html, + help='Support embeddig raw HTML. Default=%s' % def_raw_html, + ) + + parser.add_option( + '-q', + '--quiet', + action="store_true", + dest='quiet', + default=False, + help='Print less information.', + ) + + parser.add_option( + '-v', + '--verbose', + action="store_true", + dest='verbose', + default=False, + help='Print debug information.', + ) + + parser.add_option( + '--very-verbose', + action="store_true", + dest='vverbose', + default=False, + help='Print even more debug information.', + ) + + parser.add_option( + '--version', + action="store_true", + dest='version', + default=False, + help='Print version number and exit.', + ) + + def_footnote_backlinks = config.getValue("general", "footnote_backlinks", True) + parser.add_option( + '--no-footnote-backlinks', + action='store_false', + dest='footnote_backlinks', + default=def_footnote_backlinks, help='Disable footnote backlinks.' - ' Default=%s' % str(not def_footnote_backlinks)) - - def_inline_footnotes = config.getValue("general", - "inline_footnotes", False) - parser.add_option('--inline-footnotes', action='store_true', - dest='inline_footnotes', default=def_inline_footnotes, - help='Show footnotes inline.' - ' Default=%s' % str(not def_inline_footnotes)) - - def_real_footnotes = config.getValue("general", - "real_footnotes", False) - parser.add_option('--real-footnotes', action='store_true', - dest='real_footnotes', default=def_real_footnotes, + ' Default=%s' % str(not def_footnote_backlinks), + ) + + def_inline_footnotes = config.getValue("general", "inline_footnotes", False) + parser.add_option( + '--inline-footnotes', + action='store_true', + dest='inline_footnotes', + default=def_inline_footnotes, + help='Show footnotes inline.' ' Default=%s' % str(not def_inline_footnotes), + ) + + def_real_footnotes = config.getValue("general", "real_footnotes", False) + parser.add_option( + '--real-footnotes', + action='store_true', + dest='real_footnotes', + default=def_real_footnotes, help='Show footnotes at the bottom of the page where they are defined.' - ' Default=%s' % str(def_real_footnotes)) + ' Default=%s' % str(def_real_footnotes), + ) def_dpi = config.getValue("general", "default_dpi", 300) - parser.add_option('--default-dpi', dest='def_dpi', metavar='NUMBER', + parser.add_option( + '--default-dpi', + dest='def_dpi', + metavar='NUMBER', default=def_dpi, - help='DPI for objects sized in pixels. Default=%d'%def_dpi) - - parser.add_option('--show-frame-boundary', dest='show_frame', - action='store_true', default=False, - help='Show frame borders (only useful for debugging). Default=False') - - parser.add_option('--disable-splittables', dest='splittables', - action='store_false', default=True, + help='DPI for objects sized in pixels. Default=%d' % def_dpi, + ) + + parser.add_option( + '--show-frame-boundary', + dest='show_frame', + action='store_true', + default=False, + help='Show frame borders (only useful for debugging). Default=False', + ) + + parser.add_option( + '--disable-splittables', + dest='splittables', + action='store_false', + default=True, help="Don't use splittable flowables in some elements." - " Only try this if you can't process a document any other way.") + " Only try this if you can't process a document any other way.", + ) def_break = config.getValue("general", "break_level", 0) - parser.add_option('-b', '--break-level', dest='breaklevel', - metavar='LEVEL', default=def_break, + parser.add_option( + '-b', + '--break-level', + dest='breaklevel', + metavar='LEVEL', + default=def_break, help='Maximum section level that starts in a new page.' - ' Default: %d' % def_break) + ' Default: %d' % def_break, + ) def_blankfirst = config.getValue("general", "blank_first_page", False) - parser.add_option('--blank-first-page', dest='blank_first_page', - action='store_true', default=def_blankfirst, - help='Add a blank page at the beginning of the document.') + parser.add_option( + '--blank-first-page', + dest='blank_first_page', + action='store_true', + default=def_blankfirst, + help='Add a blank page at the beginning of the document.', + ) def_first_page_on_right = config.getValue("general", "first_page_on_right", False) - parser.add_option('--first-page-on-right', dest='first_page_on_right', - action='store_true', default=def_first_page_on_right, - help='Two-sided book style (where first page starts on the right side)') + parser.add_option( + '--first-page-on-right', + dest='first_page_on_right', + action='store_true', + default=def_first_page_on_right, + help='Two-sided book style (where first page starts on the right side)', + ) def_breakside = config.getValue("general", "break_side", 'any') - parser.add_option('--break-side', dest='breakside', metavar='VALUE', + parser.add_option( + '--break-side', + dest='breakside', + metavar='VALUE', default=def_breakside, help='How section breaks work. Can be "even", and sections start' - ' in an even page, "odd", and sections start in odd pages,' - ' or "any" and sections start in the next page, be it even or odd.' - ' See also the -b option.') - - parser.add_option('--date-invariant', dest='invariant', - action='store_true', default=False, + ' in an even page, "odd", and sections start in odd pages,' + ' or "any" and sections start in the next page, be it even or odd.' + ' See also the -b option.', + ) + + parser.add_option( + '--date-invariant', + dest='invariant', + action='store_true', + default=False, help="Don't store the current date in the PDF." - " Useful mainly for the test suite," - " where we don't want the PDFs to change.") - - parser.add_option('-e', '--extension-module', dest='extensions', action="append", type="string", - default = ['vectorpdf'], + " Useful mainly for the test suite," + " where we don't want the PDFs to change.", + ) + + parser.add_option( + '-e', + '--extension-module', + dest='extensions', + action="append", + type="string", + default=['vectorpdf'], help="Add a helper extension module to this invocation of rst2pdf " - "(module must end in .py and be on the python path)") + "(module must end in .py and be on the python path)", + ) def_cover = config.getValue("general", "custom_cover", 'cover.tmpl') - parser.add_option('--custom-cover', dest='custom_cover', - metavar='FILE', default= def_cover, - help='Template file used for the cover page. Default: %s'%def_cover) + parser.add_option( + '--custom-cover', + dest='custom_cover', + metavar='FILE', + default=def_cover, + help='Template file used for the cover page. Default: %s' % def_cover, + ) def_floating_images = config.getValue("general", "floating_images", False) - parser.add_option('--use-floating-images', action='store_true', default=def_floating_images, - help='Makes images with :align: attribute work more like in rst2html. Default: %s'%def_floating_images, - dest='floating_images') + parser.add_option( + '--use-floating-images', + action='store_true', + default=def_floating_images, + help='Makes images with :align: attribute work more like in rst2html. Default: %s' + % def_floating_images, + dest='floating_images', + ) def_numbered_links = config.getValue("general", "numbered_links", False) - parser.add_option('--use-numbered-links', action='store_true', default=def_numbered_links, - help='When using numbered sections, adds the numbers to all links referring to the section headers. Default: %s'%def_numbered_links, - dest='numbered_links') - - parser.add_option('--strip-elements-with-class', action='append', dest='strip_elements_with_classes', - metavar='CLASS', help='Remove elements with this CLASS from the output. Can be used multiple times.') + parser.add_option( + '--use-numbered-links', + action='store_true', + default=def_numbered_links, + help='When using numbered sections, adds the numbers to all links referring to the section headers. Default: %s' + % def_numbered_links, + dest='numbered_links', + ) + + parser.add_option( + '--strip-elements-with-class', + action='append', + dest='strip_elements_with_classes', + metavar='CLASS', + help='Remove elements with this CLASS from the output. Can be used multiple times.', + ) return parser + def main(_args=None): """Parse command line and call createPdf with the correct data.""" @@ -1355,6 +1606,7 @@ def main(_args=None): if options.version: from rst2pdf import version + six.print_(version) sys.exit(0) @@ -1379,7 +1631,9 @@ def main(_args=None): filename = False if len(args) == 0: - args = [ '-', ] + args = [ + '-', + ] elif len(args) > 2: log.critical('Usage: %s [ file.txt [ file.pdf ] ]', sys.argv[0]) sys.exit(1) @@ -1392,13 +1646,13 @@ def main(_args=None): close_infile = False if args[0] == '-': infile = sys.stdin - options.basedir=os.getcwd() + options.basedir = os.getcwd() elif len(args) > 1: log.critical('Usage: %s file.txt [ -o file.pdf ]', sys.argv[0]) sys.exit(1) else: filename = args[0] - options.basedir=os.path.dirname(os.path.abspath(filename)) + options.basedir = os.path.dirname(os.path.abspath(filename)) try: infile = open(filename, 'rb') close_infile = True @@ -1412,7 +1666,7 @@ def main(_args=None): if outfile == '-': outfile = sys.stdout options.compressed = False - #we must stay quiet + # we must stay quiet log.setLevel(logging.CRITICAL) else: if filename: @@ -1423,10 +1677,10 @@ def main(_args=None): else: outfile = sys.stdout options.compressed = False - #we must stay quiet + # we must stay quiet log.setLevel(logging.CRITICAL) - #/reportlab/pdfbase/pdfdoc.py output can - #be a callable (stringio, stdout ...) + # /reportlab/pdfbase/pdfdoc.py output can + # be a callable (stringio, stdout ...) options.outfile = outfile ssheet = [] @@ -1453,8 +1707,10 @@ def main(_args=None): options.inline_footnotes = True if reportlab.Version < '2.3': - log.warning('You are using Reportlab version %s.' - ' The suggested version is 2.3 or higher' % reportlab.Version) + log.warning( + 'You are using Reportlab version %s.' + ' The suggested version is 2.3 or higher' % reportlab.Version + ) if options.invariant: patch_PDFDate() @@ -1465,12 +1721,13 @@ def main(_args=None): RstToPdf( stylesheets=options.style, language=options.language, - header=options.header, footer=options.footer, + header=options.header, + footer=options.footer, inlinelinks=options.inlinelinks, breaklevel=int(options.breaklevel), baseurl=options.baseurl, fit_mode=options.fit_mode, - background_fit_mode = options.background_fit_mode, + background_fit_mode=options.background_fit_mode, smarty=str(options.smarty), font_path=options.fpath, style_path=options.stylepath, @@ -1489,18 +1746,22 @@ def main(_args=None): floating_images=options.floating_images, numbered_links=options.numbered_links, raw_html=options.raw_html, - section_header_depth=int(options.section_header_depth), - strip_elements_with_classes=options.strip_elements_with_classes, - ).createPdf(text=options.infile.read(), - source_path=options.infile.name, - output=options.outfile, - compressed=options.compressed) + section_header_depth=int(options.section_header_depth), + strip_elements_with_classes=options.strip_elements_with_classes, + ).createPdf( + text=options.infile.read(), + source_path=options.infile.name, + output=options.outfile, + compressed=options.compressed, + ) if close_infile: infile.close() + # Ugly hack that fixes Issue 335 -reportlab.lib.utils.ImageReader.__deepcopy__ = lambda self,*x: copy(self) +reportlab.lib.utils.ImageReader.__deepcopy__ = lambda self, *x: copy(self) + def patch_digester(): ''' Patch digester so that we can get the same results when image @@ -1512,34 +1773,40 @@ def patch_digester(): def _digester(s): index = cache.setdefault(s, len(cache)) return 'rst2pdf_image_%s' % index + canvas._digester = _digester + def patch_PDFDate(): '''Patch reportlab.pdfdoc.PDFDate so the invariant dates work correctly''' from reportlab.pdfbase import pdfdoc import reportlab + class PDFDate(pdfdoc.PDFObject): __PDFObject__ = True # gmt offset now suppported def __init__(self, invariant=True, ts=None, dateFormatter=None): - #if six.PY2: + # if six.PY2: # now = (2000,01,01,00,00,00,0) - #else: - now = (2000,0o1,0o1,00,00,00,0) + # else: + now = (2000, 0o1, 0o1, 00, 00, 00, 0) self.date = now[:6] self.dateFormatter = dateFormatter def format(self, doc): from time import timezone + dhh, dmm = timezone // 3600, (timezone % 3600) % 60 dfmt = self.dateFormatter or ( - lambda yyyy,mm,dd,hh,m,s: - "D:%04d%02d%02d%02d%02d%02d%+03d'%02d'" % (yyyy,mm,dd,hh,m,s,0,0)) + lambda yyyy, mm, dd, hh, m, s: "D:%04d%02d%02d%02d%02d%02d%+03d'%02d'" + % (yyyy, mm, dd, hh, m, s, 0, 0) + ) return pdfdoc.format(pdfdoc.PDFString(dfmt(*self.date)), doc) pdfdoc.PDFDate = PDFDate reportlab.rl_config.invariant = 1 + def add_extensions(options): extensions = [] @@ -1551,7 +1818,9 @@ def add_extensions(options): try: extensions.remove(ext) except ValueError: - log.warning('Could not remove extension %s -- no such extension installed' % ext) + log.warning( + 'Could not remove extension %s -- no such extension installed' % ext + ) else: log.info('Removed extension %s' % ext) @@ -1589,12 +1858,15 @@ def __init__(self): except ImportError as e: if str(e).split()[-1].replace("'", '') not in [firstname, modname]: raise - raise SystemExit('\nError: Could not find module %s ' - 'in sys.path [\n %s\n]\nExiting...\n' % - (modname, ',\n '.join(sys.path))) + raise SystemExit( + '\nError: Could not find module %s ' + 'in sys.path [\n %s\n]\nExiting...\n' + % (modname, ',\n '.join(sys.path)) + ) if hasattr(module, 'install'): module.install(createpdf, options) + def monkeypatch(): ''' For initial test purposes, make reportlab 2.4 mostly perform like 2.3. This allows us to compare PDFs more easily. @@ -1643,10 +1915,12 @@ def new_make_preamble(self): # By default, transparency is set, and by default, that changes PDF version # to 1.4 in RL 2.4. - pdfdoc.PDF_SUPPORT_VERSION['transparency'] = 1,3 + pdfdoc.PDF_SUPPORT_VERSION['transparency'] = 1, 3 + monkeypatch() + def publish_secondary_doctree(text, main_tree, source_path): # This is a hack so the text substitutions defined @@ -1663,15 +1937,15 @@ def apply(self): # Use an own reader to modify transformations done. class Reader(standalone.Reader): - def get_transforms(self): default = standalone.Reader.get_transforms(self) - return (default + [ addSubsts, ]) + return default + [ + addSubsts, + ] # End of Issue 322 hack - return docutils.core.publish_doctree(text, - reader = Reader(), source_path=source_path) + return docutils.core.publish_doctree(text, reader=Reader(), source_path=source_path) if __name__ == "__main__": diff --git a/rst2pdf/dumpstyle.py b/rst2pdf/dumpstyle.py index 7027cf630..04e9d8d9e 100755 --- a/rst2pdf/dumpstyle.py +++ b/rst2pdf/dumpstyle.py @@ -14,9 +14,10 @@ from json import loads as jloads try: - basestring # py27 + basestring # py27 except: - basestring = str # Py3+ + basestring = str # Py3+ + def dumps(obj, forcestyledict=True): ''' If forcestyledict is True, will attempt to @@ -75,10 +76,14 @@ def dodict(result, obj, indent): result.append('{}') return obj = sorted(obj.items()) - multiline = indent and ( len(obj) > 2 or - len(obj) == 2 and ( - isinstance(obj[0][-1], (list, dict)) or - isinstance(obj[-1][-1], (list, dict)))) + multiline = indent and ( + len(obj) > 2 + or len(obj) == 2 + and ( + isinstance(obj[0][-1], (list, dict)) + or isinstance(obj[-1][-1], (list, dict)) + ) + ) if not multiline and (not indent or len(obj) != 1): result.append('{') obj = [[x, ', '] for x in obj] @@ -98,8 +103,14 @@ def dodict(result, obj, indent): def donone(result, obj, indent): result.append('null') - dumpfuncs = {float: dofloat, int: doint, basestring: dostr, - list: dolist, dict: dodict, type(None): donone} + dumpfuncs = { + float: dofloat, + int: doint, + basestring: dostr, + list: dolist, + dict: dodict, + type(None): donone, + } dumpfuncs = dumpfuncs.items() @@ -117,6 +128,7 @@ def dumprecurse(result, obj, indent='\n', indentnow=True): dumprecurse(result, obj, indentnow=False) return fixspacing(''.join(result)) + def fixspacing(s): ''' Try to make the output prettier by inserting blank lines in random places. @@ -135,6 +147,7 @@ def fixspacing(s): result.append('') return '\n'.join(result) + def fixstyle(obj): ''' Try to convert styles into a dictionary ''' @@ -147,6 +160,7 @@ def fixstyle(obj): obj['styles'] = dict(obj['styles']) return obj + def convert(srcname): ''' Convert a single file from .json to .style ''' @@ -161,8 +175,11 @@ def convert(srcname): dstf.write(dstr) dstf.close() + if __name__ == '__main__': - _dir = os.path.dirname(sys.argv[0]) + _dir = os.path.dirname(sys.argv[0]) _stylesdir = os.path.join(_dir, 'styles') - for fname in [os.path.join('styles', x) for x in os.listdir(_stylesdir) if x.endswith('.json')]: + for fname in [ + os.path.join('styles', x) for x in os.listdir(_stylesdir) if x.endswith('.json') + ]: convert(fname) diff --git a/rst2pdf/extensions/dotted_toc.py b/rst2pdf/extensions/dotted_toc.py index ef174acfd..5f69c9f59 100644 --- a/rst2pdf/extensions/dotted_toc.py +++ b/rst2pdf/extensions/dotted_toc.py @@ -42,7 +42,7 @@ from reportlab.platypus.tableofcontents import drawPageNumbers import six - + if six.PY3: unicode = str @@ -98,8 +98,7 @@ def wrap(self, availWidth, availHeight): if reportlab.Version <= '2.3': _tempEntries = [(0, 'Placeholder for table of contents', 0)] else: - _tempEntries = [(0, 'Placeholder for table of contents', - 0, None)] + _tempEntries = [(0, 'Placeholder for table of contents', 0, None)] else: _tempEntries = self._lastEntries @@ -142,11 +141,18 @@ def drawTOCEntryEnd(canvas, kind, label): text = unicode(text, 'utf-8') text = u'%s' % (key, text) - para = Paragraph('%s' % (text, funcname, len(end_info)), style) + para = Paragraph( + '%s' % (text, funcname, len(end_info)), + style, + ) end_info.append((style, pageNum, key, dot)) if style.spaceBefore: - tableData.append([Spacer(1, style.spaceBefore), ]) - tableData.append([para, ]) + tableData.append( + [Spacer(1, style.spaceBefore),] + ) + tableData.append( + [para,] + ) self._table = Table(tableData, colWidths=(availWidth,), style=self.tableStyle) diff --git a/rst2pdf/extensions/fancytitles.py b/rst2pdf/extensions/fancytitles.py index b25afb5bb..6e5bc1bb3 100644 --- a/rst2pdf/extensions/fancytitles.py +++ b/rst2pdf/extensions/fancytitles.py @@ -10,6 +10,7 @@ from xml.sax.saxutils import unescape import codecs + class FancyTitleHandler(genelements.HandleParagraph, docutils.nodes.title): ''' This class will handle title nodes. @@ -28,52 +29,64 @@ def gather_elements(self, client, node, style): # Special cases: (Not sure this is right ;-) if isinstance(node.parent, docutils.nodes.document): - #node.elements = [Paragraph(client.gen_pdftext(node), - #client.styles['title'])] + # node.elements = [Paragraph(client.gen_pdftext(node), + # client.styles['title'])] # The visible output is now done by the cover template node.elements = [] client.doc_title = node.rawsource client.doc_title_clean = node.astext().strip() elif isinstance(node.parent, docutils.nodes.topic): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['topic-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['topic-title']) + ] elif isinstance(node.parent, docutils.nodes.Admonition): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['admonition-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['admonition-title']) + ] elif isinstance(node.parent, docutils.nodes.table): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['table-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['table-title']) + ] elif isinstance(node.parent, docutils.nodes.sidebar): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['sidebar-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['sidebar-title']) + ] else: # Section/Subsection/etc. text = client.gen_pdftext(node) fch = node.children[0] - if isinstance(fch, docutils.nodes.generated) and \ - fch['classes'] == ['sectnum']: + if isinstance(fch, docutils.nodes.generated) and fch['classes'] == [ + 'sectnum' + ]: snum = fch.astext() else: snum = None - maxdepth=4 + maxdepth = 4 if reportlab.Version > '2.1': - maxdepth=6 + maxdepth = 6 # The parent ID is the refid + an ID to make it unique for Sphinx - parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-%s' % id(node) + parent_id = (node.parent.get('ids', [None]) or [None])[0] + u'-%s' % id( + node + ) if client.depth > 1: - node.elements = [ Heading(text, - client.styles['heading%d'%min(client.depth, maxdepth)], - level=client.depth-1, + node.elements = [ + Heading( + text, + client.styles['heading%d' % min(client.depth, maxdepth)], + level=client.depth - 1, parent_id=parent_id, node=node, - )] - else: # This is an important title, do our magic ;-) + ) + ] + else: # This is an important title, do our magic ;-) # Hack the title template SVG - tfile = codecs.open('titletemplate.svg','r','utf-8') + tfile = codecs.open('titletemplate.svg', 'r', 'utf-8') tdata = tfile.read() tfile.close() - tfile = tempfile.NamedTemporaryFile(dir='.', delete=False, suffix='.svg') + tfile = tempfile.NamedTemporaryFile( + dir='.', delete=False, suffix='.svg' + ) tfname = tfile.name tfile.write(tdata.replace('TITLEGOESHERE', text).encode('utf-8')) tfile.close() @@ -82,14 +95,16 @@ def gather_elements(self, client, node, style): # Make rst2pdf delete it later. client.to_unlink.append(tfname) - e = FancyHeading(tfname, + e = FancyHeading( + tfname, width=700, height=100, client=client, snum=snum, parent_id=parent_id, text=text, - hstyle=client.styles['heading%d'%min(client.depth, maxdepth)]) + hstyle=client.styles['heading%d' % min(client.depth, maxdepth)], + ) node.elements = [e] @@ -97,6 +112,7 @@ def gather_elements(self, client, node, style): node.elements.insert(0, MyPageBreak(breakTo=client.breakside)) return node.elements + class FancyHeading(MyImage, Heading): '''This is a cross between the Heading flowable, that adds outline entries so you have a PDF TOC, and MyImage, that draws images''' @@ -107,37 +123,34 @@ def __init__(self, *args, **kwargs): level = 0 text = kwargs.pop('text') self.snum = kwargs.pop('snum') - self.parent_id= kwargs.pop('parent_id') - #self.stext = - Heading.__init__(self,text,hstyle,level=level, - parent_id=self.parent_id) + self.parent_id = kwargs.pop('parent_id') + # self.stext = + Heading.__init__(self, text, hstyle, level=level, parent_id=self.parent_id) # Cleanup title text - #self.stext = re.sub(r'<[^>]*?>', '', unescape(self.stext)) - #self.stext = self.stext.strip() + # self.stext = re.sub(r'<[^>]*?>', '', unescape(self.stext)) + # self.stext = self.stext.strip() # Stuff needed for the outline entry MyImage.__init__(self, *args, **kwargs) - def drawOn(self,canv,x,y,_sW): + def drawOn(self, canv, x, y, _sW): ## These two lines are magic. - #if isinstance(self.parent_id, tuple): - #self.parent_id=self.parent_id[0] + # if isinstance(self.parent_id, tuple): + # self.parent_id=self.parent_id[0] # Add outline entry. This is copied from rst2pdf.flowables.heading - canv.bookmarkHorizontal(self.parent_id,0,y+self.image.height) + canv.bookmarkHorizontal(self.parent_id, 0, y + self.image.height) if canv.firstSect: canv.sectName = self.stext - canv.firstSect=False + canv.firstSect = False if self.snum is not None: canv.sectNum = self.snum else: canv.sectNum = "" - canv.addOutlineEntry(self.stext, - self.parent_id, - int(self.level), False) + canv.addOutlineEntry(self.stext, self.parent_id, int(self.level), False) # And let MyImage do all the drawing - MyImage.drawOn(self,canv,x,y,_sW) + MyImage.drawOn(self, canv, x, y, _sW) diff --git a/rst2pdf/extensions/inkscape_r2p.py b/rst2pdf/extensions/inkscape_r2p.py index 85dff0a56..5014e78b5 100644 --- a/rst2pdf/extensions/inkscape_r2p.py +++ b/rst2pdf/extensions/inkscape_r2p.py @@ -86,9 +86,7 @@ def __init__( pdfuri = uri.replace(filename, pdffname) pdfsrc = client, pdfuri - VectorPdf.__init__( - self, pdfuri, width, height, kind, mask, lazy, pdfsrc - ) + VectorPdf.__init__(self, pdfuri, width, height, kind, mask, lazy, pdfsrc) @classmethod def available(self): @@ -143,9 +141,7 @@ def raster(self, filename, client): @staticmethod def run_cmd(cmd): """Execute a command and return exitcode, stdout and stderr.""" - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate() exitcode = proc.returncode return exitcode, out.decode(), err.decode() diff --git a/rst2pdf/extensions/plantuml.py b/rst2pdf/extensions/plantuml.py index 8996d808c..b518231be 100644 --- a/rst2pdf/extensions/plantuml.py +++ b/rst2pdf/extensions/plantuml.py @@ -19,6 +19,7 @@ import tempfile import subprocess + class plantuml(nodes.General, nodes.Element): pass @@ -38,6 +39,7 @@ class UmlDirective(rst.Directive): You can use a :format: option to change between SVG and PNG diagrams, however, the SVG plantuml generates doesn't look very good to me. """ + has_content = True option_spec = { 'alt': directives.unchanged, @@ -55,24 +57,32 @@ def run(self): class PlantUmlError(Exception): pass + class UMLHandler(genelements.NodeHandler, plantuml): """Class to handle UML nodes""" def gather_elements(self, client, node, style): # Create image calling plantuml - tfile = tempfile.NamedTemporaryFile(dir='.', delete=False, suffix='.'+node['format']) + tfile = tempfile.NamedTemporaryFile( + dir='.', delete=False, suffix='.' + node['format'] + ) args = 'plantuml -pipe -charset utf-8' if node['format'].lower() == 'svg': - args+=' -tsvg' + args += ' -tsvg' client.to_unlink.append(tfile.name) try: - p = subprocess.Popen(args.split(), stdout=tfile, - stdin=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + args.split(), + stdout=tfile, + stdin=subprocess.PIPE, + stderr=subprocess.PIPE, + ) except OSError as err: if err.errno != errno.ENOENT: raise - raise PlantUmlError('plantuml command %r cannot be run' - % self.builder.config.plantuml) + raise PlantUmlError( + 'plantuml command %r cannot be run' % self.builder.config.plantuml + ) serr = p.communicate(node['uml'].encode('utf-8'))[1] if p.returncode != 0: raise PlantUmlError('error while running plantuml\n\n' + serr) @@ -80,4 +90,5 @@ def gather_elements(self, client, node, style): # Add Image node with the right image return [MyImage(tfile.name, client=client)] + directives.register_directive("uml", UmlDirective) diff --git a/rst2pdf/extensions/preprocess_r2p.py b/rst2pdf/extensions/preprocess_r2p.py index 3f110ffc1..04b009ad7 100644 --- a/rst2pdf/extensions/preprocess_r2p.py +++ b/rst2pdf/extensions/preprocess_r2p.py @@ -114,6 +114,7 @@ class DummyFile(object): ''' Stores the path and content of a file which may, or may not, have been written to disk. ''' + def __init__(self, name, content): self.name = name self._content = content @@ -130,7 +131,9 @@ def __init__(self, sourcef, incfile=False, widthcount=0): ''' # fix keywords dict for use by the parser. - self.keywords = dict([(x + '::', getattr(self, 'handle_' + x)) for x in self.keywords]) + self.keywords = dict( + [(x + '::', getattr(self, 'handle_' + x)) for x in self.keywords] + ) self.widthcount = widthcount @@ -181,7 +184,7 @@ def __init__(self, sourcef, incfile=False, widthcount=0): if not chunk.endswith('\n'): continue result[-1] = chunk[:-1] - if chunk.index('\n') != len(chunk)-1: + if chunk.index('\n') != len(chunk) - 1: continue # Parse the line to look for one of our keywords. @@ -192,7 +195,12 @@ def __init__(self, sourcef, incfile=False, widthcount=0): if func is None: continue chunk = chunk.split('::', 1)[1] - elif wasblank and len(tokens) == 1 and chunk[0].isalpha() and tokens[0].isalpha(): + elif ( + wasblank + and len(tokens) == 1 + and chunk[0].isalpha() + and tokens[0].isalpha() + ): func = handle_single chunk = tokens[0] else: @@ -259,8 +267,7 @@ def handle_page(self, chunk): ''' Insert a raw pagebreak ''' self.changed = True - self.result.extend(['', '', '.. raw:: pdf', '', - ' PageBreak ' + chunk, '']) + self.result.extend(['', '', '.. raw:: pdf', '', ' PageBreak ' + chunk, '']) def handle_space(self, chunk): ''' Insert a raw space @@ -268,8 +275,7 @@ def handle_space(self, chunk): self.changed = True if len(chunk.replace(',', ' ').split()) == 1: chunk = '0 ' + chunk - self.result.extend(['', '', '.. raw:: pdf', '', - ' Spacer ' + chunk, '']) + self.result.extend(['', '', '.. raw:: pdf', '', ' Spacer ' + chunk, '']) def handle_widths(self, chunk): ''' Insert a unique style in the stylesheet, and reference it @@ -296,7 +302,9 @@ def handle_widths(self, chunk): values = ['%s%%' % x for x in values] self.widthcount += 1 stylename = 'embeddedtablewidth%d' % self.widthcount - self.styles.setdefault('styles', {})[stylename] = dict(parent=parent, colWidths=values) + self.styles.setdefault('styles', {})[stylename] = dict( + parent=parent, colWidths=values + ) self.result.extend(['', '', '.. class:: ' + stylename, '']) def handle_style(self, chunk): @@ -313,9 +321,8 @@ def handle_style(self, chunk): log.error("Empty .. style:: block found") try: styles = rson_loads(mystyles) - except ValueError as e: # Error parsing the JSON data - log.critical('Error parsing stylesheet "%s": %s'%\ - (mystyles, str(e))) + except ValueError as e: # Error parsing the JSON data + log.critical('Error parsing stylesheet "%s": %s' % (mystyles, str(e))) else: self.styles.setdefault('styles', {}).update(styles) @@ -345,7 +352,7 @@ def read_indented(self): keywords = list(x[7:] for x in vars() if x.startswith('handle_')) # Generate the regular expression for parsing, and a split function using it. - blankline = r'^([ \t]*\n)' + blankline = r'^([ \t]*\n)' singleword = r'^([A-Za-z]+[ \t]*\n)(?=[ \t]*\n)' comment = r'^(\.\.[ \t]+(?:%s)\:\:.*\n)' % '|'.join(keywords) expression = '(?:%s)' % '|'.join([blankline, singleword, comment]) @@ -357,13 +364,16 @@ class MyStyles(str): for a stylesheet that is not really a file. It must be callable(), and str(x) must return the name of the stylesheet. ''' + def __new__(cls, styles): self = str.__new__(cls, 'Embedded Preprocess Styles') self.data = styles return self + def __call__(self): return self.data + def install(createpdf, options): ''' This is where we intercept the document conversion. Preprocess the restructured text, and insert our diff --git a/rst2pdf/extensions/sample.py b/rst2pdf/extensions/sample.py index 989579d28..87121a244 100644 --- a/rst2pdf/extensions/sample.py +++ b/rst2pdf/extensions/sample.py @@ -1,4 +1,5 @@ -print(''' +print( + ''' This is a sample rst2pdf extension. Because it is named 'sample.py' you can get rst2pdf to import it by @@ -9,7 +10,9 @@ An extension can live either in the extensions subdirectory, or anywhere on the python path. -''') +''' +) + def install(createpdf, options): ''' This function is called with an object with the createpdf diff --git a/rst2pdf/extensions/vectorpdf_r2p.py b/rst2pdf/extensions/vectorpdf_r2p.py index 7d4b90bb7..68779dfb3 100644 --- a/rst2pdf/extensions/vectorpdf_r2p.py +++ b/rst2pdf/extensions/vectorpdf_r2p.py @@ -29,15 +29,26 @@ class AnyCache(object): sure how to define scope on these cached items. ''' + # This is monkey-patched into reportlab IFF we are using # PDF files inside paragraphs. -def drawImage(self, image, x, y, width=None, height=None, mask=None, - preserveAspectRatio=False, anchor='c'): +def drawImage( + self, + image, + x, + y, + width=None, + height=None, + mask=None, + preserveAspectRatio=False, + anchor='c', +): if not isinstance(image, VectorPdf): - return self._drawImageNotVectorPDF(image, x, y, width, height, mask, - preserveAspectRatio, anchor) + return self._drawImageNotVectorPDF( + image, x, y, width, height, mask, preserveAspectRatio, anchor + ) image.drawOn(self, x, y, width=width, height=height) @@ -58,12 +69,23 @@ def load_xobj(cls, srcinfo): loader = cls.filecache[client] = CacheXObj().load return loader(uri) - def __init__(self, filename, width=None, height=None, kind='direct', - mask=None, lazy=True, srcinfo=None): + def __init__( + self, + filename, + width=None, + height=None, + kind='direct', + mask=None, + lazy=True, + srcinfo=None, + ): Flowable.__init__(self) self._kind = kind self.xobj = xobj = self.load_xobj(srcinfo) - self.imageWidth, self.imageHeight = imageWidth, imageHeight = xobj.w, xobj.h + self.imageWidth, self.imageHeight = imageWidth, imageHeight = ( + xobj.w, + xobj.h, + ) width = width or imageWidth height = height or imageHeight if kind in ['bound', 'proportional']: @@ -80,7 +102,7 @@ def drawOn(self, canv, x, y, _sW=0, width=0, height=0): if _sW > 0 and hasattr(self, 'hAlign'): a = self.hAlign if a in ('CENTER', 'CENTRE', TA_CENTER): - x += 0.5*_sW + x += 0.5 * _sW elif a in ('RIGHT', TA_RIGHT): x += _sW elif a not in ('LEFT', TA_LEFT): @@ -101,7 +123,7 @@ def drawOn(self, canv, x, y, _sW=0, width=0, height=0): canv.restoreState() def _restrictSize(self, aW, aH): - if self.drawWidth > aW+_FUZZ or self.drawHeight > aH+_FUZZ: + if self.drawWidth > aW + _FUZZ or self.drawHeight > aH + _FUZZ: self._oldDrawSize = self.drawWidth, self.drawHeight factor = min(float(aW) / self.drawWidth, float(aH) / self.drawHeight) self.drawWidth *= factor @@ -132,9 +154,11 @@ def raster(cls, fname, client): ''' if cls.OldImageReader is None: import reportlab.platypus.paraparser as p + cls.OldImageReader = p.ImageReader p.ImageReader = cls.NewImageReader from reportlab.pdfgen.canvas import Canvas as c + c._drawImageNotVectorPDF = c.drawImage c.drawImage = drawImage return fname diff --git a/rst2pdf/findfonts.py b/rst2pdf/findfonts.py index b1c18649b..034d911a2 100755 --- a/rst2pdf/findfonts.py +++ b/rst2pdf/findfonts.py @@ -86,7 +86,11 @@ def loadFonts(): baseName = os.path.basename(ttf)[:-4] fullName = make_string(font.fullName).lower() - for k in (fontName, fullName, fullName.replace("italic", "oblique")): + for k in ( + fontName, + fullName, + fullName.replace("italic", "oblique"), + ): fonts[k] = (ttf, ttf, family) bold = FF_FORCEBOLD == FF_FORCEBOLD & font.flags @@ -139,7 +143,11 @@ def loadFonts(): continue # So now we have a font we know we can embed. - for n in (fontName.lower(), fullName.lower(), fullName.lower().replace("italic", "oblique")): + for n in ( + fontName.lower(), + fullName.lower(), + fullName.lower().replace("italic", "oblique"), + ): fonts[n] = (afm, pfbList[baseName], family) # And we can try to build/fill the family mapping @@ -226,7 +234,7 @@ def get_nt_fname(ftname): if six.PY3: import winreg as _w else: - import _winreg as _w + import _winreg as _w fontkey = _w.OpenKey( _w.HKEY_LOCAL_MACHINE, diff --git a/rst2pdf/flowables.py b/rst2pdf/flowables.py index 8b57439c9..740720b23 100644 --- a/rst2pdf/flowables.py +++ b/rst2pdf/flowables.py @@ -27,10 +27,11 @@ class XXPreformatted(XPreformatted): """An extended XPreformattedFit""" + def __init__(self, *args, **kwargs): XPreformatted.__init__(self, *args, **kwargs) - def split (self, aW, aH): + def split(self, aW, aH): # Figure out a nice range of splits # @@ -43,8 +44,8 @@ def split (self, aW, aH): rW, rH = self.wrap(aW, aH) if rH > aH: - minH1=getattr(self.style, 'allowOrphans', 5)*self.style.leading - minH2=getattr(self.style, 'allowWidows', 4)*self.style.leading + minH1 = getattr(self.style, 'allowOrphans', 5) * self.style.leading + minH2 = getattr(self.style, 'allowWidows', 4) * self.style.leading # If there's no way to fid a decent fragment, # refuse to split @@ -58,6 +59,7 @@ def split (self, aW, aH): return XPreformatted.split(self, aW, aH) + class MyIndenter(Indenter): """An indenter that has a width, because otherwise you get crashes if added inside tables""" @@ -68,65 +70,77 @@ class MyIndenter(Indenter): def draw(self): pass + class TocEntry(NullDraw): """A flowable that adds a TOC entry but draws nothing""" - def __init__(self,level,label): - self.level=level - self.label=label - self.width=0 - self.height=0 - self.keepWithNext=True + + def __init__(self, level, label): + self.level = level + self.label = label + self.width = 0 + self.height = 0 + self.keepWithNext = True def draw(self): # Add outline entry - self.canv.bookmarkHorizontal(self.label,0,0+self.height) - self.canv.addOutlineEntry(self.label, - self.label, - max(0,int(self.level)), False) + self.canv.bookmarkHorizontal(self.label, 0, 0 + self.height) + self.canv.addOutlineEntry( + self.label, self.label, max(0, int(self.level)), False + ) + class Heading(Paragraph): """A paragraph that also adds an outline entry in the PDF TOC.""" - def __init__(self, text, style, bulletText=None, caseSensitive=1, level=0, - snum=None, parent_id=None, node=None, section_header_depth=2): + def __init__( + self, + text, + style, + bulletText=None, + caseSensitive=1, + level=0, + snum=None, + parent_id=None, + node=None, + section_header_depth=2, + ): # Issue 114: need to convert "&" to "&" and such. # Issue 140: need to make it plain text - self.stext=re.sub(r'<[^>]*?>', '', unescape(text)) + self.stext = re.sub(r'<[^>]*?>', '', unescape(text)) self.stext = self.stext.strip() self.level = int(level) self.snum = snum - self.parent_id=parent_id - self.node=node + self.parent_id = parent_id + self.node = node self.section_header_depth = section_header_depth Paragraph.__init__(self, text, style, bulletText) def draw(self): # Add outline entry - self.canv.bookmarkHorizontal(self.parent_id,0,0+self.height) + self.canv.bookmarkHorizontal(self.parent_id, 0, 0 + self.height) # self.section_header_depth is for Issue 391 if self.canv.firstSect and self.level < self.section_header_depth: self.canv.sectName = self.stext - self.canv.firstSect=False + self.canv.firstSect = False if self.snum is not None: self.canv.sectNum = self.snum else: self.canv.sectNum = "" - self.canv.addOutlineEntry(self.stext, - self.parent_id, - int(self.level), False) + self.canv.addOutlineEntry(self.stext, self.parent_id, int(self.level), False) Paragraph.draw(self) + class Separation(Flowable): """A simple
-like flowable""" def wrap(self, w, h): self.w = w - return w, 1*cm + return w, 1 * cm def draw(self): - self.canv.line(0, 0.5*cm, self.w, 0.5*cm) + self.canv.line(0, 0.5 * cm, self.w, 0.5 * cm) class Reference(Flowable): @@ -134,7 +148,7 @@ class Reference(Flowable): def __init__(self, refid): self.refid = refid - self.keepWithNext=True + self.keepWithNext = True Flowable.__init__(self) def wrap(self, w, h): @@ -150,6 +164,7 @@ def repr(self): def __str__(self): return "Reference: %s" % self.refid + class OddEven(Flowable): """This flowable takes two lists of flowables as arguments, odd and even. If will draw the "odd" list when drawn in odd pages and the "even" list on @@ -161,17 +176,17 @@ class OddEven(Flowable): """ def __init__(self, odd, even, style=None): - self.odd=DelayedTable([[odd]],['100%'], style) - self.even=DelayedTable([[even]],['100%'], style) + self.odd = DelayedTable([[odd]], ['100%'], style) + self.even = DelayedTable([[even]], ['100%'], style) def wrap(self, w, h): """Return a box large enough for both odd and even""" - w1,h1=self.odd.wrap(w,h) - w2,h2=self.even.wrap(w,h) - return max(w1,w2), max (h1,h2) + w1, h1 = self.odd.wrap(w, h) + w2, h2 = self.even.wrap(w, h) + return max(w1, w2), max(h1, h2) def drawOn(self, canvas, x, y, _sW=0): - if canvas._pagenum %2 == 0: + if canvas._pagenum % 2 == 0: self.even.drawOn(canvas, x, y, _sW) else: self.odd.drawOn(canvas, x, y, _sW) @@ -180,6 +195,7 @@ def split(self): """Makes no sense to split this...""" return [] + class DelayedTable(Table): """A flowable that inserts a table for which it has the data. @@ -192,52 +208,59 @@ def __init__(self, data, colWidths, style=None, repeatrows=False, splitByRow=Tru self.data = data self._colWidths = colWidths if style is None: - style = TableStyle([ - ('LEFTPADDING', (0,0), (-1,-1), 0), - ('RIGHTPADDING', (0,0), (-1,-1), 0), - ('TOPPADDING', (0,0), (-1,-1), 0), - ('BOTTOMPADDING', (0,0), (-1,-1), 0), - ]) + style = TableStyle( + [ + ('LEFTPADDING', (0, 0), (-1, -1), 0), + ('RIGHTPADDING', (0, 0), (-1, -1), 0), + ('TOPPADDING', (0, 0), (-1, -1), 0), + ('BOTTOMPADDING', (0, 0), (-1, -1), 0), + ] + ) self.style = style self.t = None self.repeatrows = repeatrows self.hAlign = TA_CENTER - self.splitByRow=splitByRow + self.splitByRow = splitByRow ## Try to look more like a Table - #self._ncols = 2 - #self._nosplitCmds= [] - #self._nrows= 1 - #self._rowHeights= [None] - #self._spanCmds= [] - #self.ident= None - #self.repeatCols= 0 - #self.repeatRows= 0 - #self.splitByRow= 1 - #self.vAlign= 'MIDDLE' + # self._ncols = 2 + # self._nosplitCmds= [] + # self._nrows= 1 + # self._rowHeights= [None] + # self._spanCmds= [] + # self.ident= None + # self.repeatCols= 0 + # self.repeatRows= 0 + # self.splitByRow= 1 + # self.vAlign= 'MIDDLE' def wrap(self, w, h): # Create the table, with the widths from colWidths reinterpreted # if needed as percentages of frame/cell/whatever width w is. - #_tw = w/sum(self.colWidths) + # _tw = w/sum(self.colWidths) def adjust(*args, **kwargs): - kwargs['total']=w + kwargs['total'] = w return styles.adjustUnits(*args, **kwargs) - #adjust=functools.partial(styles.adjustUnits, total=w) + + # adjust=functools.partial(styles.adjustUnits, total=w) self.colWidths = [adjust(x) for x in self._colWidths] - #colWidths = [_w * _tw for _w in self.colWidths] - self.t = Table(self.data, colWidths=self.colWidths, - style=self.style, repeatRows=self.repeatrows, - splitByRow=True) - #splitByRow=self.splitByRow) + # colWidths = [_w * _tw for _w in self.colWidths] + self.t = Table( + self.data, + colWidths=self.colWidths, + style=self.style, + repeatRows=self.repeatrows, + splitByRow=True, + ) + # splitByRow=self.splitByRow) self.t.hAlign = self.hAlign return self.t.wrap(w, h) def split(self, w, h): if self.splitByRow: if not self.t: - self.wrap(w,h) + self.wrap(w, h) return self.t.split(w, h) else: return [] @@ -246,63 +269,79 @@ def drawOn(self, canvas, x, y, _sW=0): self.t.drawOn(canvas, x, y, _sW) def identity(self, maxLen=None): - return "<%s at %s%s%s> containing: %s" % (self.__class__.__name__, - hex(id(self)), self._frameName(), - getattr(self, 'name', '') - and (' name="%s"' % getattr(self, 'name', '')) or '', - repr(self.data[0]))[:180] + return ( + "<%s at %s%s%s> containing: %s" + % ( + self.__class__.__name__, + hex(id(self)), + self._frameName(), + getattr(self, 'name', '') + and (' name="%s"' % getattr(self, 'name', '')) + or '', + repr(self.data[0]), + )[:180] + ) + def tablepadding(padding): - if not isinstance(padding,(list,tuple)): - padding=[padding,]*4 - return padding, ('TOPPADDING',[0,0],[-1,-1],padding[0]),\ - ('RIGHTPADDING',[-1,0],[-1,-1],padding[1]),\ - ('BOTTOMPADDING',[0,0],[-1,-1],padding[2]),\ - ('LEFTPADDING',[1,0],[1,-1],padding[3]) + if not isinstance(padding, (list, tuple)): + padding = [padding,] * 4 + return ( + padding, + ('TOPPADDING', [0, 0], [-1, -1], padding[0]), + ('RIGHTPADDING', [-1, 0], [-1, -1], padding[1]), + ('BOTTOMPADDING', [0, 0], [-1, -1], padding[2]), + ('LEFTPADDING', [1, 0], [1, -1], padding[3]), + ) + class SplitTable(DelayedTable): def __init__(self, data, colWidths, style, padding=3): if len(data) != 1 or len(data[0]) != 2: log.error('SplitTable can only be 1 row and two columns!') sys.exit(1) - DelayedTable.__init__(self,data,colWidths,style) - self.padding, p1, p2, p3, p4=tablepadding(padding) - self.style._cmds.insert(0,p1) - self.style._cmds.insert(0,p2) - self.style._cmds.insert(0,p3) - self.style._cmds.insert(0,p4) + DelayedTable.__init__(self, data, colWidths, style) + self.padding, p1, p2, p3, p4 = tablepadding(padding) + self.style._cmds.insert(0, p1) + self.style._cmds.insert(0, p2) + self.style._cmds.insert(0, p3) + self.style._cmds.insert(0, p4) def identity(self, maxLen=None): - return "<%s at %s%s%s> containing: %s" % (self.__class__.__name__, - hex(id(self)), self._frameName(), + return "<%s at %s%s%s> containing: %s" % ( + self.__class__.__name__, + hex(id(self)), + self._frameName(), getattr(self, 'name', '') - and (' name="%s"' % getattr(self, 'name', '')) or '', - repr(self.data[0][1])[:180]) + and (' name="%s"' % getattr(self, 'name', '')) + or '', + repr(self.data[0][1])[:180], + ) - def split(self,w,h): - _w,_h=self.wrap(w, h) + def split(self, w, h): + _w, _h = self.wrap(w, h) - if _h > h: # Can't split! + if _h > h: # Can't split! # The right column data mandates the split # Find which flowable exceeds the available height - dw=self.colWidths[0]+self.padding[1]+self.padding[3] - dh=self.padding[0]+self.padding[2] + dw = self.colWidths[0] + self.padding[1] + self.padding[3] + dh = self.padding[0] + self.padding[2] - bullet=self.data[0][0] - text=self.data[0][1] - for l in range(0,len(text)): - _,fh = _listWrapOn(text[:l+1],w-dw,None) - if fh+dh > h: + bullet = self.data[0][0] + text = self.data[0][1] + for l in range(0, len(text)): + _, fh = _listWrapOn(text[: l + 1], w - dw, None) + if fh + dh > h: # The lth flowable is the guilty one # split it - _,lh=_listWrapOn(text[:l],w-dw,None) + _, lh = _listWrapOn(text[:l], w - dw, None) # Workaround for Issue 180 - text[l].wrap(w-dw,h-lh-dh) - l2=text[l].split(w-dw,h-lh-dh) - if l2==[]: # Not splittable, push some to next page - if l==0: # Can't fit anything, push all to next page + text[l].wrap(w - dw, h - lh - dh) + l2 = text[l].split(w - dw, h - lh - dh) + if l2 == []: # Not splittable, push some to next page + if l == 0: # Can't fit anything, push all to next page return l2 # We reduce the number of items we keep on the @@ -319,73 +358,81 @@ def split(self,w,h): # inner table. while l > 0: - if not text[l-1].getKeepWithNext(): - first_t = Table([ - [bullet, - text[:l]] - ], - colWidths=self.colWidths, - style=self.style) - _w,_h = first_t.wrap(w, h) + if not text[l - 1].getKeepWithNext(): + first_t = Table( + [[bullet, text[:l]]], + colWidths=self.colWidths, + style=self.style, + ) + _w, _h = first_t.wrap(w, h) if _h <= h: break l -= 1 - if l>0: + if l > 0: # Workaround for Issue 180 with wordaxe: - #if wordaxe is not None: - #l3=[Table([ - #[bullet, - #text[:l]] - #], - #colWidths=self.colWidths, - #style=self.style), - #Table([['',text[l:]]], - #colWidths=self.colWidths, - #style=self.style)] - #else: - l3=[first_t, - SplitTable([['',text[l:]]], + # if wordaxe is not None: + # l3=[Table([ + # [bullet, + # text[:l]] + # ], + # colWidths=self.colWidths, + # style=self.style), + # Table([['',text[l:]]], + # colWidths=self.colWidths, + # style=self.style)] + # else: + l3 = [ + first_t, + SplitTable( + [['', text[l:]]], colWidths=self.colWidths, style=self.style, - padding=self.padding)] - else: # Everything flows - l3=[] + padding=self.padding, + ), + ] + else: # Everything flows + l3 = [] else: - l3=[Table([[bullet,text[:l]+[l2[0]]]], + l3 = [ + Table( + [[bullet, text[:l] + [l2[0]]]], colWidths=self.colWidths, rowHeights=[h], - style=self.style)] - if l2[1:]+text[l+1:]: + style=self.style, + ) + ] + if l2[1:] + text[l + 1 :]: ## Workaround for Issue 180 with wordaxe: - #if wordaxe is not None: - #l3.append( - #Table([['',l2[1:]+text[l+1:]]], - #colWidths=self.colWidths, - #style=self.style)) - #else: + # if wordaxe is not None: + # l3.append( + # Table([['',l2[1:]+text[l+1:]]], + # colWidths=self.colWidths, + # style=self.style)) + # else: l3.append( - SplitTable([['',l2[1:]+text[l+1:]]], - colWidths=self.colWidths, - style=self.style, - padding=self.padding)) + SplitTable( + [['', l2[1:] + text[l + 1 :]]], + colWidths=self.colWidths, + style=self.style, + padding=self.padding, + ) + ) return l3 log.debug("Can't split splittable") return self.t.split(w, h) else: - return DelayedTable.split(self,w,h) + return DelayedTable.split(self, w, h) class MySpacer(Spacer): - def wrap (self, aW, aH): + def wrap(self, aW, aH): w, h = Spacer.wrap(self, aW, aH) self.height = min(aH, h) return w, self.height - class MyPageBreak(FrameActionFlowable): - def __init__(self, templateName=None, breakTo='any'): '''templateName switches the page template starting in the next page. @@ -404,19 +451,19 @@ def __init__(self, templateName=None, breakTo='any'): ''' self.templateName = templateName - self.breakTo=breakTo - self.forced=False - self.extraContent=[] + self.breakTo = breakTo + self.forced = False + self.extraContent = [] def frameAction(self, frame): frame._generated_content = [] - if self.breakTo=='any': # Break only once. None if at top of page + if self.breakTo == 'any': # Break only once. None if at top of page if not frame._atTop: frame._generated_content.append(SetNextTemplate(self.templateName)) frame._generated_content.append(PageBreak()) - elif self.breakTo=='odd': #Break once if on even page, twice - #on odd page, none if on top of odd page - if frame._pagenum % 2: #odd pageNum + elif self.breakTo == 'odd': # Break once if on even page, twice + # on odd page, none if on top of odd page + if frame._pagenum % 2: # odd pageNum if not frame._atTop: # Blank pages get no heading or footer frame._generated_content.append(SetNextTemplate(self.templateName)) @@ -424,15 +471,15 @@ def frameAction(self, frame): frame._generated_content.append(PageBreak()) frame._generated_content.append(ResetNextTemplate()) frame._generated_content.append(PageBreak()) - else: #even + else: # even frame._generated_content.append(SetNextTemplate(self.templateName)) frame._generated_content.append(PageBreak()) - elif self.breakTo=='even': #Break once if on odd page, twice - #on even page, none if on top of even page - if frame._pagenum % 2: #odd pageNum + elif self.breakTo == 'even': # Break once if on odd page, twice + # on even page, none if on top of even page + if frame._pagenum % 2: # odd pageNum frame._generated_content.append(SetNextTemplate(self.templateName)) frame._generated_content.append(PageBreak()) - else: #even + else: # even if not frame._atTop: # Blank pages get no heading or footer frame._generated_content.append(SetNextTemplate(self.templateName)) @@ -441,6 +488,7 @@ def frameAction(self, frame): frame._generated_content.append(ResetNextTemplate()) frame._generated_content.append(PageBreak()) + class SetNextTemplate(Flowable): """Set canv.templateName when drawing. @@ -460,6 +508,7 @@ def draw(self): self.canv.oldTemplateName = 'oneColumn' self.canv.templateName = self.templateName + class ResetNextTemplate(Flowable): """Go back to the previous template. @@ -478,11 +527,14 @@ def __init__(self): Flowable.__init__(self) def draw(self): - self.canv.templateName, self.canv.oldTemplateName = \ - self.canv.oldTemplateName, self.canv.templateName + self.canv.templateName, self.canv.oldTemplateName = ( + self.canv.oldTemplateName, + self.canv.templateName, + ) def wrap(self, aW, aH): - return 0,0 + return 0, 0 + class TextAnnotation(Flowable): """Add text annotation flowable""" @@ -503,6 +555,7 @@ def draw(self): # textAnnotation("Your content", Rect=[x_begin, y_begin, x_end, y_end], relative=1) self.canv.textAnnotation(self.annotationText, self.position, 1) + class Transition(Flowable): """Wrap canvas.setPageTransition. @@ -516,11 +569,12 @@ class Transition(Flowable): Box=['motion'], Wipe=['direction'], Dissolve=[], - Glitter=['direction']) + Glitter=['direction'], + ) def __init__(self, *args): if len(args) < 1: - args = [None, 1] # No transition + args = [None, 1] # No transition # See if we got a valid transition effect name if args[0] not in self.PageTransitionEffects: log.error('Unknown transition effect name: %s' % args[0]) @@ -536,13 +590,9 @@ def wrap(self, aw, ah): def draw(self): kwargs = dict( - effectname=None, - duration=1, - direction=0, - dimension='H', - motion='I') - ceff = ['effectname', 'duration'] +\ - self.PageTransitionEffects[self.args[0]] + effectname=None, duration=1, direction=0, dimension='H', motion='I' + ) + ceff = ['effectname', 'duration'] + self.PageTransitionEffects[self.args[0]] for argname, argvalue in zip(ceff, self.args): kwargs[argname] = argvalue kwargs['duration'] = int(kwargs['duration']) @@ -558,21 +608,52 @@ class SmartFrame(Frame): """ - def __init__(self, container, x1, y1, width, height, - leftPadding=6, bottomPadding=6, rightPadding=6, topPadding=6, - id=None, showBoundary=0, overlapAttachedSpace=None, _debug=None): + def __init__( + self, + container, + x1, + y1, + width, + height, + leftPadding=6, + bottomPadding=6, + rightPadding=6, + topPadding=6, + id=None, + showBoundary=0, + overlapAttachedSpace=None, + _debug=None, + ): self.container = container self.onSidebar = False - self.__s = '[%s, %s, %s, %s, %s, %s, %s, %s,]'\ - %(x1,y1,width,height, - leftPadding, bottomPadding, - rightPadding, topPadding) - Frame.__init__(self, x1, y1, width, height, - leftPadding, bottomPadding, rightPadding, topPadding, - id, showBoundary, overlapAttachedSpace, _debug) - - def add (self, flowable, canv, trySplit=0): - flowable._atTop=self._atTop + self.__s = '[%s, %s, %s, %s, %s, %s, %s, %s,]' % ( + x1, + y1, + width, + height, + leftPadding, + bottomPadding, + rightPadding, + topPadding, + ) + Frame.__init__( + self, + x1, + y1, + width, + height, + leftPadding, + bottomPadding, + rightPadding, + topPadding, + id, + showBoundary, + overlapAttachedSpace, + _debug, + ) + + def add(self, flowable, canv, trySplit=0): + flowable._atTop = self._atTop return Frame.add(self, flowable, canv, trySplit) def __repr__(self): @@ -581,8 +662,8 @@ def __repr__(self): def __deepcopy__(self, *whatever): return copy(self) -class FrameCutter(FrameActionFlowable): +class FrameCutter(FrameActionFlowable): def __init__(self, dx, width, flowable, padding, lpad, floatLeft=True): self.width = width self.dx = dx @@ -595,51 +676,66 @@ def frameAction(self, frame): idx = frame.container.frames.index(frame) if self.floatLeft: # Don't bother inserting a silly thin frame - if self.width-self.padding > 30: - f1 = SmartFrame(frame.container, - frame._x1 + self.dx - 2*self.padding, - frame._y2 - self.f.height - 3*self.padding, - self.width + 2*self.padding, - self.f.height + 3*self.padding, - bottomPadding=0, topPadding=0, leftPadding=self.lpad) + if self.width - self.padding > 30: + f1 = SmartFrame( + frame.container, + frame._x1 + self.dx - 2 * self.padding, + frame._y2 - self.f.height - 3 * self.padding, + self.width + 2 * self.padding, + self.f.height + 3 * self.padding, + bottomPadding=0, + topPadding=0, + leftPadding=self.lpad, + ) f1._atTop = frame._atTop # This is a frame next to a sidebar. f1.onSidebar = True frame.container.frames.insert(idx + 1, f1) # Don't add silly thin frame - if frame._height-self.f.height - 2*self.padding > 30: - frame.container.frames.insert(idx + 2, - SmartFrame(frame.container, + if frame._height - self.f.height - 2 * self.padding > 30: + frame.container.frames.insert( + idx + 2, + SmartFrame( + frame.container, frame._x1, frame._y1p, self.width + self.dx, - frame._height - self.f.height - 3*self.padding, - topPadding=0)) + frame._height - self.f.height - 3 * self.padding, + topPadding=0, + ), + ) else: # Don't bother inserting a silly thin frame - if self.width-self.padding > 30: - f1 = SmartFrame(frame.container, + if self.width - self.padding > 30: + f1 = SmartFrame( + frame.container, frame._x1 - self.width, - frame._y2 - self.f.height - 2*self.padding, + frame._y2 - self.f.height - 2 * self.padding, self.width, - self.f.height + 2*self.padding, - bottomPadding=0, topPadding=0, rightPadding=self.lpad) + self.f.height + 2 * self.padding, + bottomPadding=0, + topPadding=0, + rightPadding=self.lpad, + ) f1._atTop = frame._atTop # This is a frame next to a sidebar. f1.onSidebar = True frame.container.frames.insert(idx + 1, f1) - if frame._height - self.f.height - 2*self.padding > 30: - frame.container.frames.insert(idx + 2, - SmartFrame(frame.container, + if frame._height - self.f.height - 2 * self.padding > 30: + frame.container.frames.insert( + idx + 2, + SmartFrame( + frame.container, frame._x1 - self.width, frame._y1p, self.width + self.dx, - frame._height - self.f.height - 2*self.padding, - topPadding=0)) + frame._height - self.f.height - 2 * self.padding, + topPadding=0, + ), + ) class Sidebar(FrameActionFlowable): - def __init__(self, flowables, style): self.style = style self.width = self.style.width @@ -648,7 +744,7 @@ def __init__(self, flowables, style): def frameAction(self, frame): if self.style.float not in ('left', 'right'): return - if frame.onSidebar: # We are still on the frame next to a sidebar! + if frame.onSidebar: # We are still on the frame next to a sidebar! frame._generated_content = [FrameBreak(), self] else: w = frame.container.styles.adjustUnits(self.width, frame.width) @@ -656,50 +752,59 @@ def frameAction(self, frame): padding = self.style.borderPadding width = self.style.width self.style.padding = frame.container.styles.adjustUnits( - str(padding), frame.width) + str(padding), frame.width + ) self.style.width = frame.container.styles.adjustUnits( - str(width), frame.width) + str(width), frame.width + ) self.kif = BoxedContainer(self.flowables, self.style) if self.style.float == 'left': self.style.lpad = frame.leftPadding - f1 = SmartFrame(frame.container, + f1 = SmartFrame( + frame.container, frame._x1, frame._y1p, - w - 2*self.style.padding, + w - 2 * self.style.padding, frame._y - frame._y1p, - leftPadding=self.style.lpad, rightPadding=0, - bottomPadding=0, topPadding=0) + leftPadding=self.style.lpad, + rightPadding=0, + bottomPadding=0, + topPadding=0, + ) f1._atTop = frame._atTop - frame.container.frames.insert(idx+1, f1) + frame.container.frames.insert(idx + 1, f1) frame._generated_content = [ FrameBreak(), self.kif, - FrameCutter(w, - frame.width - w, - self.kif, - padding, - self.style.lpad, - True), - FrameBreak()] + FrameCutter( + w, frame.width - w, self.kif, padding, self.style.lpad, True, + ), + FrameBreak(), + ] elif self.style.float == 'right': self.style.lpad = frame.rightPadding - frame.container.frames.insert(idx + 1, - SmartFrame(frame.container, + frame.container.frames.insert( + idx + 1, + SmartFrame( + frame.container, frame._x1 + frame.width - self.style.width, frame._y1p, - w, frame._y-frame._y1p, - rightPadding=self.style.lpad, leftPadding=0, - bottomPadding=0, topPadding=0)) + w, + frame._y - frame._y1p, + rightPadding=self.style.lpad, + leftPadding=0, + bottomPadding=0, + topPadding=0, + ), + ) frame._generated_content = [ FrameBreak(), self.kif, - FrameCutter(w, - frame.width - w, - self.kif, - padding, - self.style.lpad, - False), - FrameBreak()] + FrameCutter( + w, frame.width - w, self.kif, padding, self.style.lpad, False, + ), + FrameBreak(), + ] class BoundByWidth(Flowable): @@ -709,7 +814,7 @@ class BoundByWidth(Flowable): """ - def __init__(self, maxWidth, content=[], style=None, mode=None, scale = None): + def __init__(self, maxWidth, content=[], style=None, mode=None, scale=None): self.maxWidth = maxWidth self.content = content self.style = style @@ -729,34 +834,50 @@ def border_padding(self, useWidth, additional): return [x + additional for x in bp] def identity(self, maxLen=None): - return "<%s at %s%s%s> containing: %s" % (self.__class__.__name__, - hex(id(self)), self._frameName(), + return "<%s at %s%s%s> containing: %s" % ( + self.__class__.__name__, + hex(id(self)), + self._frameName(), getattr(self, 'name', '') - and (' name="%s"' % getattr(self, 'name', '')) or '', - repr([c.identity() for c in self.content])[:80]) + and (' name="%s"' % getattr(self, 'name', '')) + or '', + repr([c.identity() for c in self.content])[:80], + ) def wrap(self, availWidth, availHeight): """If we need more width than we have, complain, keep a scale""" self.pad = self.border_padding(True, 0.1) - maxWidth = float(min( - styles.adjustUnits(self.maxWidth, availWidth) or availWidth, - availWidth)) + maxWidth = float( + min( + styles.adjustUnits(self.maxWidth, availWidth) or availWidth, availWidth, + ) + ) self.maxWidth = maxWidth - maxWidth -= (self.pad[1]+self.pad[3]) - self.width, self.height = _listWrapOn(self.content, maxWidth, None, fakeWidth=False) + maxWidth -= self.pad[1] + self.pad[3] + self.width, self.height = _listWrapOn( + self.content, maxWidth, None, fakeWidth=False + ) if self.width > maxWidth: if self.mode != 'shrink': self.scale = 1.0 - log.warning("BoundByWidth too wide to fit in frame (%s > %s): %s", - self.width,maxWidth,self.identity()) + log.warning( + "BoundByWidth too wide to fit in frame (%s > %s): %s", + self.width, + maxWidth, + self.identity(), + ) if self.mode == 'shrink' and not self.scale: - self.scale = (maxWidth + self.pad[1]+self.pad[3])/\ - (self.width + self.pad[1]+self.pad[3]) + self.scale = (maxWidth + self.pad[1] + self.pad[3]) / ( + self.width + self.pad[1] + self.pad[3] + ) else: self.scale = 1.0 self.height *= self.scale self.width *= self.scale - return self.width, self.height + (self.pad[0]+self.pad[2])*self.scale + return ( + self.width, + self.height + (self.pad[0] + self.pad[2]) * self.scale, + ) def split(self, availWidth, availHeight): if not self.pad: @@ -765,10 +886,13 @@ def split(self, availWidth, availHeight): if len(self.content) == 1: # We need to split the only element we have content = content[0].split( - availWidth - (self.pad[1]+self.pad[3]), - availHeight - (self.pad[0]+self.pad[2])) - result = [BoundByWidth(self.maxWidth, [f], - self.style, self.mode, self.scale) for f in content] + availWidth - (self.pad[1] + self.pad[3]), + availHeight - (self.pad[0] + self.pad[2]), + ) + result = [ + BoundByWidth(self.maxWidth, [f], self.style, self.mode, self.scale) + for f in content + ] return result def draw(self): @@ -780,16 +904,16 @@ def draw(self): _sW = 0 scale = self.scale content = None - #, canv, x, y, _sW=0, scale=1.0, content=None, aW=None): + # , canv, x, y, _sW=0, scale=1.0, content=None, aW=None): pS = 0 aW = self.width - aW = scale*(aW + _sW) + aW = scale * (aW + _sW) if content is None: content = self.content - y += (self.height + self.pad[2])/scale + y += (self.height + self.pad[2]) / scale x += self.pad[3] for c in content: - w, h = c.wrapOn(canv, aW, 0xfffffff) + w, h = c.wrapOn(canv, aW, 0xFFFFFFF) if (w < _FUZZ or h < _FUZZ) and not getattr(c, '_ZEROSIZE', None): continue if c is not content[0]: @@ -800,10 +924,12 @@ def draw(self): canv.scale(scale, scale) elif self.mode == 'truncate': p = canv.beginPath() - p.rect(x-self.pad[3], - y-self.pad[2], - self.maxWidth, - self.height + self.pad[0]+self.pad[2]) + p.rect( + x - self.pad[3], + y - self.pad[2], + self.maxWidth, + self.height + self.pad[0] + self.pad[2], + ) canv.clipPath(p, stroke=0) c.drawOn(canv, x, y, _sW=aW - w) canv.restoreState() @@ -814,19 +940,19 @@ def draw(self): class BoxedContainer(BoundByWidth): - def __init__(self, content, style, mode='shrink'): try: - w=style.width + w = style.width except AttributeError: - w='100%' + w = '100%' BoundByWidth.__init__(self, w, content, mode=mode, style=None) self.style = style self.mode = mode def identity(self, maxLen=None): - return repr([u"BoxedContainer containing: ", - [c.identity() for c in self.content]])[:80] + return repr( + [u"BoxedContainer containing: ", [c.identity() for c in self.content],] + )[:80] def draw(self): canv = self.canv @@ -838,19 +964,18 @@ def draw(self): if self.style and self.style.borderWidth > 0: lw = self.style.borderWidth canv.setLineWidth(self.style.borderWidth) - if self.style.borderColor: # This could be None :-( + if self.style.borderColor: # This could be None :-( canv.setStrokeColor(self.style.borderColor) - stroke=1 + stroke = 1 else: - stroke=0 + stroke = 0 else: - stroke=0 + stroke = 0 if self.style and self.style.backColor: canv.setFillColor(self.style.backColor) - fill=1 + fill = 1 else: - fill=0 - + fill = 0 padding = self.border_padding(False, lw) xpadding = padding[1] + padding[3] @@ -863,7 +988,7 @@ def draw(self): def split(self, availWidth, availHeight): self.wrap(availWidth, availHeight) - padding = (self.pad[1]+self.pad[3])*self.scale + padding = (self.pad[1] + self.pad[3]) * self.scale if self.height + padding <= availHeight: return [self] else: @@ -877,14 +1002,14 @@ def split(self, availWidth, availHeight): if h < availHeight: candidate = b if self.content[p:]: - remainder = BoxedContainer(self.content[p:], - self.style, - self.mode) + remainder = BoxedContainer( + self.content[p:], self.style, self.mode + ) else: break - if not candidate or not remainder: # Nothing fits, break page + if not candidate or not remainder: # Nothing fits, break page return [] - if not remainder: # Everything fits? + if not remainder: # Everything fits? return [self] return [candidate, remainder] @@ -897,8 +1022,8 @@ def _do_post_text(i, t_off, tx): """From reportlab's paragraph.py, patched to avoid underlined links""" xs = tx.XtraState leading = xs.style.leading - ff = 0.125*xs.f.fontSize - y0 = xs.cur_y - i*leading + ff = 0.125 * xs.f.fontSize + y0 = xs.cur_y - i * leading y = y0 - ff ulc = None for x1, x2, c in xs.underlines: @@ -910,7 +1035,7 @@ def _do_post_text(i, t_off, tx): xs.underline = 0 xs.underlineColor = None - ys = y0 + 2*ff + ys = y0 + 2 * ff ulc = None for x1, x2, c in xs.strikes: if c != ulc: @@ -933,6 +1058,7 @@ def _do_post_text(i, t_off, tx): pla_para._do_post_text.func_code = _do_post_text.func_code ############### End of the ugly + class MyTableOfContents(TableOfContents): """ Subclass of reportlab.platypus.tableofcontents.TableOfContents @@ -952,7 +1078,7 @@ def __init__(self, *args, **kwargs): # # Yes, this is gross. - self.parent=kwargs.pop('parent') + self.parent = kwargs.pop('parent') TableOfContents.__init__(self, *args, **kwargs) # reference ids for which this TOC should be notified self.refids = [] @@ -963,7 +1089,7 @@ def __init__(self, *args, **kwargs): def notify(self, kind, stuff): # stuff includes (level, text, pagenum, label) level, text, pageNum, label, node = stuff - rlabel='-'.join(label.split('-')[:-1]) + rlabel = '-'.join(label.split('-')[:-1]) def islocal(_node): '''See if this node is "local enough" for this TOC. @@ -973,7 +1099,7 @@ def islocal(_node): while _node.parent: if _node.parent == self.parent: return True - _node=_node.parent + _node = _node.parent return False if rlabel in self.refids and islocal(node): @@ -983,8 +1109,7 @@ def islocal(_node): def wrap(self, availWidth, availHeight): """Adds hyperlink to toc entry.""" - widths = (availWidth - self.rightColumnWidth, - self.rightColumnWidth) + widths = (availWidth - self.rightColumnWidth, self.rightColumnWidth) # makes an internal table which does all the work. # we draw the LAST RUN's entries! If there are @@ -994,8 +1119,7 @@ def wrap(self, availWidth, availHeight): if reportlab.Version <= '2.3': _tempEntries = [(0, 'Placeholder for table of contents', 0)] else: - _tempEntries = [(0, 'Placeholder for table of contents', - 0, None)] + _tempEntries = [(0, 'Placeholder for table of contents', 0, None)] else: _tempEntries = self._lastEntries @@ -1007,9 +1131,9 @@ def wrap(self, availWidth, availHeight): for entry in _tempEntries: level, text, pageNum = entry[:3] left_col_level = level - base_level - if reportlab.Version > '2.3': # For ReportLab post-2.3 - leftColStyle=self.getLevelStyle(left_col_level) - else: # For ReportLab <= 2.3 + if reportlab.Version > '2.3': # For ReportLab post-2.3 + leftColStyle = self.getLevelStyle(left_col_level) + else: # For ReportLab <= 2.3 leftColStyle = self.levelStyles[left_col_level] label = self.refid_lut.get((level, text, pageNum), None) if label: @@ -1021,11 +1145,15 @@ def wrap(self, availWidth, availHeight): else: pre = '' post = '' - #right col style is right aligned - rightColStyle = ParagraphStyle(name='leftColLevel%d' % left_col_level, - parent=leftColStyle, leftIndent=0, alignment=TA_RIGHT) + # right col style is right aligned + rightColStyle = ParagraphStyle( + name='leftColLevel%d' % left_col_level, + parent=leftColStyle, + leftIndent=0, + alignment=TA_RIGHT, + ) leftPara = Paragraph(text, leftColStyle) - rightPara = Paragraph(pre+str(pageNum)+post, rightColStyle) + rightPara = Paragraph(pre + str(pageNum) + post, rightColStyle) tableData.append([leftPara, rightPara]) self._table = Table(tableData, colWidths=widths, style=self.tableStyle) @@ -1037,7 +1165,7 @@ def split(self, aW, aH): # Make sure _table exists before splitting. # This was only triggered in rare cases using sphinx. if not self._table: - self.wrap(aW,aH) + self.wrap(aW, aH) return TableOfContents.split(self, aW, aH) def isSatisfied(self): @@ -1046,9 +1174,11 @@ def isSatisfied(self): return True else: if len(self._entries) != len(self._lastEntries): - log.info('Number of items in TOC changed '\ - 'from %d to %d, not satisfied'%\ - (len(self._lastEntries),len(self._entries))) + log.info( + 'Number of items in TOC changed ' + 'from %d to %d, not satisfied' + % (len(self._lastEntries), len(self._entries)) + ) return False log.info('TOC entries that moved in this pass:') @@ -1058,4 +1188,3 @@ def isSatisfied(self): log.info(str(self._lastEntries[i])) return False - diff --git a/rst2pdf/genelements.py b/rst2pdf/genelements.py index 42e51d61c..a74c9117a 100644 --- a/rst2pdf/genelements.py +++ b/rst2pdf/genelements.py @@ -54,18 +54,29 @@ from reportlab.platypus import Paragraph, TableStyle from reportlab.lib.units import cm from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT -from .flowables import Table, DelayedTable, SplitTable, Heading, \ - MyIndenter, MyTableOfContents, MySpacer, \ - Separation, BoxedContainer, BoundByWidth, \ - MyPageBreak, Reference, tablepadding, OddEven, \ - XPreformatted +from .flowables import ( + Table, + DelayedTable, + SplitTable, + Heading, + MyIndenter, + MyTableOfContents, + MySpacer, + Separation, + BoxedContainer, + BoundByWidth, + MyPageBreak, + Reference, + tablepadding, + OddEven, + XPreformatted, +) from rst2pdf.math_flowable import Math from .opt_imports import wordaxe, Paragraph, ParagraphStyle class TocBuilderVisitor(docutils.nodes.SparseNodeVisitor): - def __init__(self, document): docutils.nodes.SparseNodeVisitor.__init__(self, document) self.toc = None @@ -73,7 +84,7 @@ def __init__(self, document): # .. contents:: ends up trying to call # visitor.document.reporter.debug # so we need a valid document here. - self.document=docutils.utils.new_document('') + self.document = docutils.utils.new_document('') def visit_reference(self, node): refid = node.attributes.get('refid') @@ -84,15 +95,19 @@ def visit_reference(self, node): class HandleDocument(NodeHandler, docutils.nodes.document): pass + class HandleTable(NodeHandler, docutils.nodes.table): def gather_elements(self, client, node, style): if node['classes']: - style = client.styles.combinedStyle(['table']+node['classes']) + style = client.styles.combinedStyle(['table'] + node['classes']) else: style = client.styles['table'] - return [MySpacer(0, client.styles['table'].spaceBefore)] + \ - client.gather_elements(node, style=style) +\ - [MySpacer(0, client.styles['table'].spaceAfter)] + return ( + [MySpacer(0, client.styles['table'].spaceBefore)] + + client.gather_elements(node, style=style) + + [MySpacer(0, client.styles['table'].spaceAfter)] + ) + class HandleTGroup(NodeHandler, docutils.nodes.tgroup): def gather_elements(self, client, node, style): @@ -101,7 +116,7 @@ def gather_elements(self, client, node, style): # because sometimes it's not passed down. if node.parent['classes']: - style = client.styles.combinedStyle(['table']+node.parent['classes']) + style = client.styles.combinedStyle(['table'] + node.parent['classes']) else: style = client.styles['table'] rows = [] @@ -128,12 +143,12 @@ def gather_elements(self, client, node, style): # colWidths are in no specific unit, really. Maybe ems. # Convert them to % - colWidths=[int(x) for x in colWidths] - tot=sum(colWidths) - colWidths=["%s%%"%((100.*w)/tot) for w in colWidths] + colWidths = [int(x) for x in colWidths] + tot = sum(colWidths) + colWidths = ["%s%%" % ((100.0 * w) / tot) for w in colWidths] if 'colWidths' in style.__dict__: - colWidths[:len(style.colWidths)]=style.colWidths + colWidths[: len(style.colWidths)] = style.colWidths spans = client.filltable(rows) @@ -172,26 +187,27 @@ def gather_elements(self, client, node, style): st.add(*cmd) rtr = client.repeat_table_rows - t=DelayedTable(data, colWidths, st, rtr) + t = DelayedTable(data, colWidths, st, rtr) if style.alignment == TA_LEFT: - t.hAlign='LEFT' + t.hAlign = 'LEFT' elif style.alignment == TA_CENTER: - t.hAlign='CENTER' + t.hAlign = 'CENTER' elif style.alignment == TA_RIGHT: - t.hAlign='RIGHT' + t.hAlign = 'RIGHT' return [t] + class HandleParagraph(NodeHandler, docutils.nodes.paragraph): def gather_elements(self, client, node, style): return [Paragraph(client.gen_pdftext(node), style)] def get_pre_post(self, client, node, replaceEnt): - pre='' - targets=set(node.get('ids',[])+client.pending_targets) - client.pending_targets=[] + pre = '' + targets = set(node.get('ids', []) + client.pending_targets) + client.pending_targets = [] for _id in targets: if _id not in client.targets: - pre+=''%(_id) + pre += '' % (_id) client.targets.append(_id) return pre, '\n' @@ -205,182 +221,253 @@ def gather_elements(self, client, node, style): client.doc_title = node.rawsource client.doc_title_clean = node.astext().strip() elif isinstance(node.parent, docutils.nodes.topic): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['topic-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['topic-title']) + ] elif isinstance(node.parent, docutils.nodes.Admonition): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['admonition-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['admonition-title']) + ] elif isinstance(node.parent, docutils.nodes.table): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['table-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['table-title']) + ] elif isinstance(node.parent, docutils.nodes.sidebar): - node.elements = [Paragraph(client.gen_pdftext(node), - client.styles['sidebar-title'])] + node.elements = [ + Paragraph(client.gen_pdftext(node), client.styles['sidebar-title']) + ] else: # Section/Subsection/etc. text = client.gen_pdftext(node) fch = node.children[0] - if isinstance(fch, docutils.nodes.generated) and \ - fch['classes'] == ['sectnum']: + if isinstance(fch, docutils.nodes.generated) and fch['classes'] == [ + 'sectnum' + ]: snum = fch.astext() else: snum = None - maxdepth=6 + maxdepth = 6 # The parent ID is the refid + an ID to make it unique for Sphinx - parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-'+str(id(node)) - node.elements = [ Heading(text, - client.styles['heading%d'%min(client.depth, maxdepth)], - level=client.depth-1, + parent_id = ( + (node.parent.get('ids', [None]) or [None])[0] + u'-' + str(id(node)) + ) + node.elements = [ + Heading( + text, + client.styles['heading%d' % min(client.depth, maxdepth)], + level=client.depth - 1, parent_id=parent_id, node=node, - section_header_depth=client.section_header_depth - )] + section_header_depth=client.section_header_depth, + ) + ] if client.depth <= client.breaklevel: node.elements.insert(0, MyPageBreak(breakTo=client.breakside)) return node.elements + class HandleSubTitle(HandleParagraph, docutils.nodes.subtitle): def gather_elements(self, client, node, style): if isinstance(node.parent, docutils.nodes.sidebar): - elements = [Paragraph(client.gen_pdftext(node), - client.styles['sidebar-subtitle'])] + elements = [ + Paragraph(client.gen_pdftext(node), client.styles['sidebar-subtitle']) + ] elif isinstance(node.parent, docutils.nodes.document): - #elements = [Paragraph(client.gen_pdftext(node), - #client.styles['subtitle'])] + # elements = [Paragraph(client.gen_pdftext(node), + # client.styles['subtitle'])] # The visible output is now done by the cover template elements = [] # FIXME: looks like subtitles don't have a rawsource like # titles do. # That means that literals and italics etc in subtitles won't # work. - client.doc_subtitle = getattr(node,'rawtext',node.astext()).strip() + client.doc_subtitle = getattr(node, 'rawtext', node.astext()).strip() else: elements = node.elements # FIXME Can we get here??? return elements + class HandleDocInfo(NodeHandler, docutils.nodes.docinfo): # A docinfo usually contains several fields. # We'll render it as a series of elements, one field each. pass + class HandleField(NodeHandler, docutils.nodes.field): def gather_elements(self, client, node, style): # 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(client.gather_pdftext(node.children[0]) + ":", - style=client.styles['fieldname']) - fb = client.gen_elements(node.children[1], - style=client.styles['fieldvalue']) - t_style=TableStyle(client.styles['field-list'].commands) - return [DelayedTable([[fn, fb]], style=t_style, - colWidths=client.styles['field-list'].colWidths)] + fn = Paragraph( + client.gather_pdftext(node.children[0]) + ":", + style=client.styles['fieldname'], + ) + fb = client.gen_elements(node.children[1], style=client.styles['fieldvalue']) + t_style = TableStyle(client.styles['field-list'].commands) + return [ + DelayedTable( + [[fn, fb]], + style=t_style, + colWidths=client.styles['field-list'].colWidths, + ) + ] + class HandleDecoration(NodeHandler, docutils.nodes.decoration): pass + class HandleHeader(NodeHandler, docutils.nodes.header): stylename = 'header' + def gather_elements(self, client, node, style): - client.decoration[self.stylename] = client.gather_elements(node, - style=client.styles[self.stylename]) + client.decoration[self.stylename] = client.gather_elements( + node, style=client.styles[self.stylename] + ) return [] + class HandleFooter(HandleHeader, docutils.nodes.footer): stylename = 'footer' + class HandleAuthor(NodeHandler, docutils.nodes.author): def gather_elements(self, client, node, style): if isinstance(node.parent, docutils.nodes.authors): # Is only one of multiple authors. Return a paragraph - node.elements = [Paragraph(client.gather_pdftext(node), - style=style)] + node.elements = [Paragraph(client.gather_pdftext(node), style=style)] if client.doc_author: - client.doc_author += client.author_separator(style=style) \ - + node.astext().strip() + client.doc_author += ( + client.author_separator(style=style) + node.astext().strip() + ) else: client.doc_author = node.astext().strip() else: # A single author: works like a field fb = client.gather_pdftext(node) - t_style=TableStyle(client.styles['field-list'].commands) - colWidths=[client.styles.adjustUnits(x) for x in client.styles['field-list'].colWidths] + t_style = TableStyle(client.styles['field-list'].commands) + colWidths = [ + client.styles.adjustUnits(x) + for x in client.styles['field-list'].colWidths + ] - node.elements = [Table( - [[Paragraph(client.text_for_label("author", style)+":", - style=client.styles['fieldname']), - Paragraph(fb, style)]], - style=t_style, colWidths=colWidths)] + node.elements = [ + Table( + [ + [ + Paragraph( + client.text_for_label("author", style) + ":", + style=client.styles['fieldname'], + ), + Paragraph(fb, style), + ] + ], + style=t_style, + colWidths=colWidths, + ) + ] client.doc_author = node.astext().strip() return node.elements + class HandleAuthors(NodeHandler, docutils.nodes.authors): def gather_elements(self, client, node, style): # Multiple authors. Create a two-column table. # Author references on the right. - t_style=TableStyle(client.styles['field-list'].commands) + t_style = TableStyle(client.styles['field-list'].commands) colWidths = client.styles['field-list'].colWidths - td = [[Paragraph(client.text_for_label("authors", style)+":", - style=client.styles['fieldname']), - client.gather_elements(node, style=style)]] - return [DelayedTable(td, style=t_style, - colWidths=colWidths)] + td = [ + [ + Paragraph( + client.text_for_label("authors", style) + ":", + style=client.styles['fieldname'], + ), + client.gather_elements(node, style=style), + ] + ] + return [DelayedTable(td, style=t_style, colWidths=colWidths)] + class HandleFList(NodeHandler): adjustwidths = False TableType = DelayedTable + def gather_elements(self, client, node, style): fb = client.gather_pdftext(node) - t_style=TableStyle(client.styles['field-list'].commands) - colWidths=client.styles['field-list'].colWidths + t_style = TableStyle(client.styles['field-list'].commands) + colWidths = client.styles['field-list'].colWidths if self.adjustwidths: colWidths = [client.styles.adjustUnits(x) for x in colWidths] - label=client.text_for_label(self.labeltext, style)+":" - t = self.TableType([[Paragraph(label, style=client.styles['fieldname']), - Paragraph(fb, style)]], - style=t_style, colWidths=colWidths) + label = client.text_for_label(self.labeltext, style) + ":" + t = self.TableType( + [ + [ + Paragraph(label, style=client.styles['fieldname']), + Paragraph(fb, style), + ] + ], + style=t_style, + colWidths=colWidths, + ) return [t] + class HandleOrganization(HandleFList, docutils.nodes.organization): labeltext = "organization" + class HandleContact(HandleFList, docutils.nodes.contact): labeltext = "contact" + class HandleAddress(HandleFList, docutils.nodes.address): labeltext = "address" + def gather_elements(self, client, node, style): fb = client.gather_pdftext(node) - t_style=TableStyle(client.styles['field-list'].commands) - colWidths=client.styles['field-list'].colWidths + t_style = TableStyle(client.styles['field-list'].commands) + colWidths = client.styles['field-list'].colWidths if self.adjustwidths: colWidths = [client.styles.adjustUnits(x) for x in colWidths] - label=client.text_for_label(self.labeltext, style)+":" - t = self.TableType([[Paragraph(label, style=client.styles['fieldname']), - XPreformatted(fb, style)] - ], style=t_style, colWidths=colWidths) + label = client.text_for_label(self.labeltext, style) + ":" + t = self.TableType( + [ + [ + Paragraph(label, style=client.styles['fieldname']), + XPreformatted(fb, style), + ] + ], + style=t_style, + colWidths=colWidths, + ) return [t] + class HandleVersion(HandleFList, docutils.nodes.version): labeltext = "version" + class HandleRevision(HandleFList, docutils.nodes.revision): labeltext = "revision" adjustwidths = True TableType = Table + class HandleStatus(HandleFList, docutils.nodes.status): labeltext = "status" + class HandleDate(HandleFList, docutils.nodes.date): labeltext = "date" + class HandleCopyright(HandleFList, docutils.nodes.copyright): labeltext = "copyright" + class HandleTopic(NodeHandler, docutils.nodes.topic): def gather_elements(self, client, node, style): # toc @@ -395,114 +482,130 @@ def gather_elements(self, client, node, style): toc_visitor.toc.linkColor = cstyles.tocColor or cstyles.linkColor node.walk(toc_visitor) toc = toc_visitor.toc - toc.levelStyles=[cstyles['toc%d'%l] for l in range(1,15)] + toc.levelStyles = [cstyles['toc%d' % l] for l in range(1, 15)] for s in toc.levelStyles: # FIXME: awful slimy hack! - s.__class__=reportlab.lib.styles.ParagraphStyle + s.__class__ = reportlab.lib.styles.ParagraphStyle ## Issue 117: add extra TOC levelStyles. ## 9-deep should be enough. - #for i in range(4): - #ps = toc.levelStyles[-1].__class__(name='Level%d'%(i+5), - #parent=toc.levelStyles[-1], - #leading=toc.levelStyles[-1].leading, - #firstlineIndent=toc.levelStyles[-1].firstLineIndent, - #leftIndent=toc.levelStyles[-1].leftIndent+1*cm) - #toc.levelStyles.append(ps) + # for i in range(4): + # ps = toc.levelStyles[-1].__class__(name='Level%d'%(i+5), + # parent=toc.levelStyles[-1], + # leading=toc.levelStyles[-1].leading, + # firstlineIndent=toc.levelStyles[-1].firstLineIndent, + # leftIndent=toc.levelStyles[-1].leftIndent+1*cm) + # toc.levelStyles.append(ps) ## Override fontnames (defaults to Times-Roman) - #for levelStyle in toc.levelStyles: - #levelStyle.__dict__['fontName'] = \ - #client.styles['tableofcontents'].fontName + # for levelStyle in toc.levelStyles: + # levelStyle.__dict__['fontName'] = \ + # client.styles['tableofcontents'].fontName if 'local' in node_classes: node.elements = [toc] else: - node.elements = \ - [Paragraph(client.gen_pdftext(node.children[0]), - cstyles['heading1']), toc] + node.elements = [ + Paragraph( + client.gen_pdftext(node.children[0]), cstyles['heading1'], + ), + toc, + ] else: node.elements = client.gather_elements(node, style=style) return node.elements + class HandleFieldBody(NodeHandler, docutils.nodes.field_body): pass + class HandleSection(NodeHandler, docutils.nodes.section): def gather_elements(self, client, node, style): - #XXX: should style be passed down here? - client.depth+=1 + # XXX: should style be passed down here? + client.depth += 1 elements = client.gather_elements(node) - client.depth-=1 + client.depth -= 1 return elements + class HandleBulletList(NodeHandler, docutils.nodes.bullet_list): def gather_elements(self, client, node, style): - if node ['classes']: + if node['classes']: style = client.styles[node['classes'][0]] else: style = client.styles["bullet-list"] - node.elements = client.gather_elements(node, - style=style) + node.elements = client.gather_elements(node, style=style) # Here we need to separate the list from the previous element. # Calculate by how much: - sb=style.spaceBefore # list separation - sa=style.spaceAfter # list separation + sb = style.spaceBefore # list separation + sa = style.spaceAfter # list separation node.elements.insert(0, MySpacer(0, sb)) node.elements.append(MySpacer(0, sa)) return node.elements -class HandleDefOrOptList(NodeHandler, docutils.nodes.definition_list, - docutils.nodes.option_list): + +class HandleDefOrOptList( + NodeHandler, docutils.nodes.definition_list, docutils.nodes.option_list +): pass + class HandleFieldList(NodeHandler, docutils.nodes.field_list): def gather_elements(self, client, node, style): - return [MySpacer(0,client.styles['field-list'].spaceBefore)]+\ - client.gather_elements(node, style=style) + return [ + MySpacer(0, client.styles['field-list'].spaceBefore) + ] + client.gather_elements(node, style=style) + class HandleEnumeratedList(NodeHandler, docutils.nodes.enumerated_list): def gather_elements(self, client, node, style): - if node ['classes']: + if node['classes']: style = client.styles[node['classes'][0]] else: style = client.styles["item-list"] - node.elements = client.gather_elements(node, - style = style) + node.elements = client.gather_elements(node, style=style) # Here we need to separate the list from the previous element. # Calculate by how much: - sb=style.spaceBefore # list separation - sa=style.spaceAfter # list separation + sb = style.spaceBefore # list separation + sa = style.spaceAfter # list separation node.elements.insert(0, MySpacer(0, sb)) node.elements.append(MySpacer(0, sa)) return node.elements + class HandleDefinition(NodeHandler, docutils.nodes.definition): def gather_elements(self, client, node, style): - return client.gather_elements(node, - style = style) + return client.gather_elements(node, style=style) + class HandleOptionListItem(NodeHandler, docutils.nodes.option_list_item): def gather_elements(self, client, node, style): - optext = ', '.join([client.gather_pdftext(child) - for child in node.children[0].children]) + optext = ', '.join( + [client.gather_pdftext(child) for child in node.children[0].children] + ) desc = client.gather_elements(node.children[1], style) t_style = TableStyle(client.styles['option-list'].commands) colWidths = client.styles['option-list'].colWidths - node.elements = [DelayedTable([[client.PreformattedFit( - optext, client.styles["literal"]), desc]], style = t_style, - colWidths = colWidths)] + node.elements = [ + DelayedTable( + [[client.PreformattedFit(optext, client.styles["literal"]), desc,]], + style=t_style, + colWidths=colWidths, + ) + ] return node.elements + class HandleDefListItem(NodeHandler, docutils.nodes.definition_list_item): def gather_elements(self, client, node, style): # I need to catch the classifiers here @@ -511,7 +614,7 @@ def gather_elements(self, client, node, style): ids = [] for n in node.children: if isinstance(n, docutils.nodes.term): - for i in n['ids']: # Used by sphinx glossary lists + for i in n['ids']: # Used by sphinx glossary lists if i not in client.targets: ids.append('' % i) client.targets.append(i) @@ -524,20 +627,29 @@ def gather_elements(self, client, node, style): dt.extend(client.gen_elements(n, style)) # FIXME: make this configurable from the stylesheet - t_style = TableStyle (client.styles['definition'].commands) - cw = getattr(client.styles['definition'],'colWidths',[]) + t_style = TableStyle(client.styles['definition'].commands) + cw = getattr(client.styles['definition'], 'colWidths', []) if client.splittables: node.elements = [ - Paragraph(''.join(ids)+' : '.join(tt), client.styles['definition-list-term']), - SplitTable([['',dt]] , colWidths=cw, style = t_style )] + Paragraph( + ''.join(ids) + ' : '.join(tt), + client.styles['definition-list-term'], + ), + SplitTable([['', dt]], colWidths=cw, style=t_style), + ] else: node.elements = [ - Paragraph(''.join(ids)+' : '.join(tt), client.styles['definition-list-term']), - DelayedTable([['',dt]] , colWidths=[10,None], style = t_style )] + Paragraph( + ''.join(ids) + ' : '.join(tt), + client.styles['definition-list-term'], + ), + DelayedTable([['', dt]], colWidths=[10, None], style=t_style), + ] return node.elements + class HandleListItem(NodeHandler, docutils.nodes.list_item): def gather_elements(self, client, node, style): b, t = client.bullet_for_node(node) @@ -554,44 +666,41 @@ def gather_elements(self, client, node, style): # bulletFontSize # bulletFont # This is so the baselines of the bullet and the text align - extra_space= bStyle.bulletFontSize-bStyle.fontSize + extra_space = bStyle.bulletFontSize - bStyle.fontSize - bStyle.fontSize=bStyle.bulletFontSize - bStyle.fontName=bStyle.bulletFontName + bStyle.fontSize = bStyle.bulletFontSize + bStyle.fontName = bStyle.bulletFontName if t == 'bullet': - item_st=client.styles['bullet-list-item'] + item_st = client.styles['bullet-list-item'] else: - item_st=client.styles['item-list-item'] + item_st = client.styles['item-list-item'] el = client.gather_elements(node, item_st) # FIXME: this is really really not good code if not el: el = [Paragraph(u"\xa0", item_st)] - - idx=node.parent.children.index(node) - if idx==0: + idx = node.parent.children.index(node) + if idx == 0: # The first item in the list, so doesn't need # separation (it's provided by the list itself) - sb=0 + sb = 0 # It also doesn't need a first-line-indent - fli=0 + fli = 0 else: # Not the first item, so need to separate from # previous item. Account for space provided by # the item's content, too. - sb=item_st.spaceBefore-item_st.spaceAfter - fli=item_st.firstLineIndent + sb = item_st.spaceBefore - item_st.spaceAfter + fli = item_st.firstLineIndent - bStyle.spaceBefore=0 + bStyle.spaceBefore = 0 t_style = TableStyle(style.commands) # The -3 here is to compensate for padding, 0 doesn't work :-( - t_style._cmds.extend([ - ["BOTTOMPADDING", [ 0, 0 ], [ -1, -1 ], -3 ]] - ) - if extra_space >0: + t_style._cmds.extend([["BOTTOMPADDING", [0, 0], [-1, -1], -3]]) + if extra_space > 0: # The bullet is larger, move down the item text sb += extra_space sbb = 0 @@ -599,24 +708,31 @@ def gather_elements(self, client, node, style): # The bullet is smaller, move down the bullet sbb = -extra_space - colWidths = getattr(style,'colWidths',[]) + colWidths = getattr(style, 'colWidths', []) while len(colWidths) < 2: colWidths.append(client.styles['item_list'].colWidths[len(colWidths)]) if client.splittables: - node.elements = [MySpacer(0,sb), - SplitTable([[Paragraph(b, style = bStyle), el]], - style = t_style, - colWidths = colWidths) - ] + node.elements = [ + MySpacer(0, sb), + SplitTable( + [[Paragraph(b, style=bStyle), el]], + style=t_style, + colWidths=colWidths, + ), + ] else: - node.elements = [MySpacer(0,sb), - DelayedTable([[Paragraph(b, style = bStyle), el]], - style = t_style, - colWidths = colWidths) - ] + node.elements = [ + MySpacer(0, sb), + DelayedTable( + [[Paragraph(b, style=bStyle), el]], + style=t_style, + colWidths=colWidths, + ), + ] return node.elements + class HandleTransition(NodeHandler, docutils.nodes.transition): def gather_elements(self, client, node, style): return [Separation()] @@ -626,49 +742,68 @@ class HandleBlockQuote(NodeHandler, docutils.nodes.block_quote): def gather_elements(self, client, node, style): # This should work, but doesn't look good inside of # table cells (see Issue 173) - #node.elements = [MyIndenter(left=client.styles['blockquote'].leftIndent)]\ - #+ client.gather_elements( node, style) + \ - #[MyIndenter(left=-client.styles['blockquote'].leftIndent)] + # node.elements = [MyIndenter(left=client.styles['blockquote'].leftIndent)]\ + # + client.gather_elements( node, style) + \ + # [MyIndenter(left=-client.styles['blockquote'].leftIndent)] # Workaround for Issue 173 using tables - leftIndent=client.styles['blockquote'].leftIndent - rightIndent=client.styles['blockquote'].rightIndent - spaceBefore=client.styles['blockquote'].spaceBefore - spaceAfter=client.styles['blockquote'].spaceAfter - s=copy(client.styles['blockquote']) - s.leftIndent=style.leftIndent - data=[['',client.gather_elements( node, s)]] + leftIndent = client.styles['blockquote'].leftIndent + rightIndent = client.styles['blockquote'].rightIndent + spaceBefore = client.styles['blockquote'].spaceBefore + spaceAfter = client.styles['blockquote'].spaceAfter + s = copy(client.styles['blockquote']) + s.leftIndent = style.leftIndent + data = [['', client.gather_elements(node, s)]] if client.splittables: - node.elements=[MySpacer(0,spaceBefore),SplitTable(data, - colWidths=[leftIndent,None], - style=TableStyle([["TOPPADDING",[0,0],[-1,-1],0], - ["LEFTPADDING",[0,0],[-1,-1],0], - ["RIGHTPADDING",[0,0],[-1,-1],rightIndent], - ["BOTTOMPADDING",[0,0],[-1,-1],0], - ])), MySpacer(0,spaceAfter)] + node.elements = [ + MySpacer(0, spaceBefore), + SplitTable( + data, + colWidths=[leftIndent, None], + style=TableStyle( + [ + ["TOPPADDING", [0, 0], [-1, -1], 0], + ["LEFTPADDING", [0, 0], [-1, -1], 0], + ["RIGHTPADDING", [0, 0], [-1, -1], rightIndent], + ["BOTTOMPADDING", [0, 0], [-1, -1], 0], + ] + ), + ), + MySpacer(0, spaceAfter), + ] else: - node.elements=[MySpacer(0,spaceBefore),DelayedTable(data, - colWidths=[leftIndent,None], - style=TableStyle([["TOPPADDING",[0,0],[-1,-1],0], - ["LEFTPADDING",[0,0],[-1,-1],0], - ["RIGHTPADDING",[0,0],[-1,-1],rightIndent], - ["BOTTOMPADDING",[0,0],[-1,-1],0], - ])), MySpacer(0,spaceAfter)] + node.elements = [ + MySpacer(0, spaceBefore), + DelayedTable( + data, + colWidths=[leftIndent, None], + style=TableStyle( + [ + ["TOPPADDING", [0, 0], [-1, -1], 0], + ["LEFTPADDING", [0, 0], [-1, -1], 0], + ["RIGHTPADDING", [0, 0], [-1, -1], rightIndent], + ["BOTTOMPADDING", [0, 0], [-1, -1], 0], + ] + ), + ), + MySpacer(0, spaceAfter), + ] return node.elements + class HandleAttribution(NodeHandler, docutils.nodes.attribution): def gather_elements(self, client, node, style): - return [ - Paragraph(client.gather_pdftext(node), - client.styles['attribution'])] + return [Paragraph(client.gather_pdftext(node), client.styles['attribution'])] + class HandleComment(NodeHandler, docutils.nodes.comment): def gather_elements(self, client, node, style): # Class that generates no output return [] + class HandleLineBlock(NodeHandler, docutils.nodes.line_block): def gather_elements(self, client, node, style): - if isinstance(node.parent,docutils.nodes.line_block): + if isinstance(node.parent, docutils.nodes.line_block): qstyle = copy(style) qstyle.leftIndent += client.styles.adjustUnits("1.5em") else: @@ -677,40 +812,49 @@ def gather_elements(self, client, node, style): # space before the lineblock itself # Fix Issue 482: nested lineblocks don't need spacing before/after if not isinstance(node.parent, docutils.nodes.line_block): - return [MySpacer(0,client.styles['lineblock'].spaceBefore)]+client.gather_elements(node, style=qstyle)+[MySpacer(0,client.styles['lineblock'].spaceAfter)] + return ( + [MySpacer(0, client.styles['lineblock'].spaceBefore)] + + client.gather_elements(node, style=qstyle) + + [MySpacer(0, client.styles['lineblock'].spaceAfter)] + ) else: return client.gather_elements(node, style=qstyle) + class HandleLine(NodeHandler, docutils.nodes.line): def gather_elements(self, client, node, style): # line nodes have no classes, they have to inherit from the outermost lineblock (sigh) # For more info see Issue 471 and its test case. parent = node - while isinstance(parent.parent, (docutils.nodes.line, docutils.nodes.line_block)): - parent=parent.parent - p_class = (parent.get('classes') or ['line'])[0] + while isinstance( + parent.parent, (docutils.nodes.line, docutils.nodes.line_block) + ): + parent = parent.parent + p_class = (parent.get('classes') or ['line'])[0] qstyle = copy(client.styles[p_class]) # Indent .5em per indent unit - i=node.__dict__.get('indent',0) - #qstyle = copy(client.styles['line']) - qstyle.leftIndent += client.styles.adjustUnits("0.5em")*i + i = node.__dict__.get('indent', 0) + # qstyle = copy(client.styles['line']) + qstyle.leftIndent += client.styles.adjustUnits("0.5em") * i text = client.gather_pdftext(node) - if not text: # empty line - text=u"\xa0" + if not text: # empty line + text = u"\xa0" return [Paragraph(text, style=qstyle)] -class HandleLiteralBlock(NodeHandler, docutils.nodes.literal_block, - docutils.nodes.doctest_block): + +class HandleLiteralBlock( + NodeHandler, docutils.nodes.literal_block, docutils.nodes.doctest_block +): def gather_elements(self, client, node, style): if node['classes']: - style = client.styles.combinedStyle(['code']+node['classes']) + style = client.styles.combinedStyle(['code'] + node['classes']) else: style = client.styles['code'] - return [client.PreformattedFit( - client.gather_pdftext(node, replaceEnt = True), - style )] + return [ + client.PreformattedFit(client.gather_pdftext(node, replaceEnt=True), style) + ] class HandleFigure(NodeHandler, docutils.nodes.figure): @@ -721,71 +865,88 @@ def gather_elements(self, client, node, style): st_name = 'figure' if node.get('classes'): st_name = node.get('classes')[0] - style=client.styles[st_name] - cmd=getattr(style,'commands',[]) - image=node.children[0] + style = client.styles[st_name] + cmd = getattr(style, 'commands', []) + image = node.children[0] if len(node.children) > 1: caption = node.children[1] else: - caption=None + caption = None if len(node.children) > 2: legend = node.children[2:] else: - legend=[] + legend = [] - w=node.get('width',client.styles['figure'].colWidths[0]) - cw=[w,] + w = node.get('width', client.styles['figure'].colWidths[0]) + cw = [ + w, + ] sub_elems = client.gather_elements(node, style=None) - t_style=TableStyle(cmd) - table = DelayedTable([[e,] for e in sub_elems],style=t_style, - colWidths=cw) - table.hAlign = node.get('align','CENTER').upper() - return [MySpacer(0, style.spaceBefore),table, - MySpacer(0, style.spaceAfter)] + t_style = TableStyle(cmd) + table = DelayedTable([[e,] for e in sub_elems], style=t_style, colWidths=cw) + table.hAlign = node.get('align', 'CENTER').upper() + return [ + MySpacer(0, style.spaceBefore), + table, + MySpacer(0, style.spaceAfter), + ] class HandleCaption(NodeHandler, docutils.nodes.caption): def gather_elements(self, client, node, style): - return [Paragraph(client.gather_pdftext(node), - style=client.styles['figure-caption'])] + return [ + Paragraph( + client.gather_pdftext(node), style=client.styles['figure-caption'], + ) + ] + class HandleLegend(NodeHandler, docutils.nodes.legend): def gather_elements(self, client, node, style): - return client.gather_elements(node, - style=client.styles['figure-legend']) + return client.gather_elements(node, style=client.styles['figure-legend']) + class HandleSidebar(NodeHandler, docutils.nodes.sidebar): def gather_elements(self, client, node, style): - return [BoxedContainer(client.gather_elements(node, style=None), - client.styles['sidebar'])] + return [ + BoxedContainer( + client.gather_elements(node, style=None), client.styles['sidebar'], + ) + ] + class HandleRubric(NodeHandler, docutils.nodes.rubric): def gather_elements(self, client, node, style): # Sphinx uses a rubric as footnote container - if self.sphinxmode and len(node.children) == 1 \ - and node.children[0].astext() == 'Footnotes': - return [] + if ( + self.sphinxmode + and len(node.children) == 1 + and node.children[0].astext() == 'Footnotes' + ): + return [] else: - return [Paragraph(client.gather_pdftext(node), - client.styles['rubric'])] + return [Paragraph(client.gather_pdftext(node), client.styles['rubric'])] + class HandleCompound(NodeHandler, docutils.nodes.compound): # FIXME think if this is even implementable pass -class HandleContainer(NodeHandler, docutils.nodes.container): +class HandleContainer(NodeHandler, docutils.nodes.container): def getelements(self, client, node, style): parent = node.parent if not isinstance(parent, (docutils.nodes.header, docutils.nodes.footer)): return NodeHandler.getelements(self, client, node, style) return self.gather_elements(client, node, style) + class HandleSubstitutionDefinition(NodeHandler, docutils.nodes.substitution_definition): def gather_elements(self, client, node, style): return [] + class HandleTBody(NodeHandler, docutils.nodes.tbody): def gather_elements(self, client, node, style): rows = [client.gen_elements(n) for n in node.children] @@ -798,174 +959,198 @@ def gather_elements(self, client, node, style): colWidths = client.styles['table'].colWidths return [DelayedTable(t, style=t_style, colWidths=colWidths)] -class HandleFootnote(NodeHandler, docutils.nodes.footnote, - docutils.nodes.citation): + +class HandleFootnote(NodeHandler, docutils.nodes.footnote, docutils.nodes.citation): def gather_elements(self, client, node, style): # It seems a footnote contains a label and a series of elements ltext = client.gather_pdftext(node.children[0]) label = None - ids='' - for i in node.get('ids',[]): - ids+=''%(i) - client.targets.extend(node.get('ids',[ltext])) + ids = '' + for i in node.get('ids', []): + ids += '' % (i) + client.targets.extend(node.get('ids', [ltext])) if len(node['backrefs']) > 1 and client.footnote_backlinks: backrefs = [] i = 1 for r in node['backrefs']: - backrefs.append('%d' % ( - r, client.styles.linkColor, i)) + backrefs.append( + '%d' % (r, client.styles.linkColor, i) + ) i += 1 backrefs = '(%s)' % ', '.join(backrefs) if ltext not in client.targets: - label = Paragraph(ids+'%s'%(ltext + backrefs), - client.styles["endnote"]) + label = Paragraph( + ids + '%s' % (ltext + backrefs), client.styles["endnote"] + ) client.targets.append(ltext) - elif len(node['backrefs'])==1 and client.footnote_backlinks: + elif len(node['backrefs']) == 1 and client.footnote_backlinks: if ltext not in client.targets: - label = Paragraph(ids+'%s' % ( - node['backrefs'][0], - client.styles.linkColor, - ltext), client.styles["endnote"]) + label = Paragraph( + ids + + '%s' + % (node['backrefs'][0], client.styles.linkColor, ltext), + client.styles["endnote"], + ) client.targets.append(ltext) else: if ltext not in client.targets: - label = Paragraph(ids+ltext, - client.styles["endnote"]) + label = Paragraph(ids + ltext, client.styles["endnote"]) client.targets.append(ltext) if not label: - label = Paragraph(ids+ltext, - client.styles["endnote"]) + label = Paragraph(ids + ltext, client.styles["endnote"]) contents = client.gather_elements(node, client.styles["endnote"])[1:] if client.inline_footnotes: - st=client.styles['endnote'] + st = client.styles['endnote'] t_style = TableStyle(st.commands) colWidths = client.styles['endnote'].colWidths - node.elements = [MySpacer(0, st.spaceBefore), - DelayedTable([[label, contents]], - style=t_style, colWidths=colWidths), - MySpacer(0, st.spaceAfter)] + node.elements = [ + MySpacer(0, st.spaceBefore), + DelayedTable([[label, contents]], style=t_style, colWidths=colWidths), + MySpacer(0, st.spaceAfter), + ] if client.real_footnotes: client.mustMultiBuild = True for e in node.elements: - e.isFootnote=True + e.isFootnote = True else: client.decoration['endnotes'].append([label, contents]) node.elements = [] return node.elements + class HandleLabel(NodeHandler, docutils.nodes.label): def gather_elements(self, client, node, style): return [Paragraph(client.gather_pdftext(node), style)] + class HandleEntry(NodeHandler, docutils.nodes.entry): pass + class HandleRaw(NodeHandler, docutils.nodes.raw): def gather_elements(self, client, node, style): # Not really raw, but what the heck - if node.get('format','NONE').lower()=='pdf': + if node.get('format', 'NONE').lower() == 'pdf': return parseRaw(str(node.astext()), node) - elif client.raw_html and node.get('format','NONE').lower()=='html': + elif client.raw_html and node.get('format', 'NONE').lower() == 'html': x = parseHTML(str(node.astext()), node) return x else: return [] -class HandleOddEven (NodeHandler, OddEvenNode): +class HandleOddEven(NodeHandler, OddEvenNode): def gather_elements(self, client, node, style): - odd=[] - even=[] + odd = [] + even = [] if node.children: - if isinstance (node.children[0], docutils.nodes.paragraph): + if isinstance(node.children[0], docutils.nodes.paragraph): if node.children[0].get('classes'): s = client.styles[node.children[0].get('classes')[0]] else: s = style - odd=[Paragraph(client.gather_pdftext(node.children[0]), - s)] + odd = [Paragraph(client.gather_pdftext(node.children[0]), s)] else: # A compound element - odd=client.gather_elements(node.children[0]) - if len(node.children)>1: - if isinstance (node.children[1], docutils.nodes.paragraph): + odd = client.gather_elements(node.children[0]) + if len(node.children) > 1: + if isinstance(node.children[1], docutils.nodes.paragraph): if node.children[1].get('classes'): s = client.styles[node.children[1].get('classes')[0]] else: s = style - even=[Paragraph(client.gather_pdftext(node.children[1]), - s)] + even = [Paragraph(client.gather_pdftext(node.children[1]), s)] else: - even=client.gather_elements(node.children[1]) + even = client.gather_elements(node.children[1]) return [OddEven(odd=odd, even=even)] + class HandleAanode(NodeHandler, Aanode): def gather_elements(self, client, node, style): style_options = { 'font': client.styles['aafigure'].fontName, - } + } return [node.gen_flowable(style_options)] -class HandleAdmonition(NodeHandler, docutils.nodes.attention, - docutils.nodes.caution, docutils.nodes.danger, - docutils.nodes.error, docutils.nodes.hint, - docutils.nodes.important, docutils.nodes.note, - docutils.nodes.tip, docutils.nodes.warning, - docutils.nodes.Admonition): +class HandleAdmonition( + NodeHandler, + docutils.nodes.attention, + docutils.nodes.caution, + docutils.nodes.danger, + docutils.nodes.error, + docutils.nodes.hint, + docutils.nodes.important, + docutils.nodes.note, + docutils.nodes.tip, + docutils.nodes.warning, + docutils.nodes.Admonition, +): def gather_elements(self, client, node, style): if node.children and isinstance(node.children[0], docutils.nodes.title): - title=[] + title = [] else: - title= [Paragraph(client.text_for_label(node.tagname, style), - style=client.styles['%s-heading'%node.tagname])] - rows=title + client.gather_elements(node, style=style) - st=client.styles[node.tagname] + title = [ + Paragraph( + client.text_for_label(node.tagname, style), + style=client.styles['%s-heading' % node.tagname], + ) + ] + rows = title + client.gather_elements(node, style=style) + st = client.styles[node.tagname] if 'commands' in dir(st): t_style = TableStyle(st.commands) else: t_style = TableStyle() - t_style.add("ROWBACKGROUNDS", [0, 0], [-1, -1],[st.backColor]) - t_style.add("BOX", [ 0, 0 ], [ -1, -1 ], st.borderWidth , st.borderColor) + t_style.add("ROWBACKGROUNDS", [0, 0], [-1, -1], [st.backColor]) + t_style.add("BOX", [0, 0], [-1, -1], st.borderWidth, st.borderColor) if client.splittables: - node.elements = [MySpacer(0,st.spaceBefore), - SplitTable([['',rows]], - style=t_style, - colWidths=[0,None], - padding=st.borderPadding), - MySpacer(0,st.spaceAfter)] + node.elements = [ + MySpacer(0, st.spaceBefore), + SplitTable( + [['', rows]], + style=t_style, + colWidths=[0, None], + padding=st.borderPadding, + ), + MySpacer(0, st.spaceAfter), + ] else: - padding, p1, p2, p3, p4=tablepadding(padding=st.borderPadding) + padding, p1, p2, p3, p4 = tablepadding(padding=st.borderPadding) t_style.add(*p1) t_style.add(*p2) t_style.add(*p3) t_style.add(*p4) - node.elements = [MySpacer(0,st.spaceBefore), - DelayedTable([['',rows]], - style=t_style, - colWidths=[0,None]), - MySpacer(0,st.spaceAfter)] + node.elements = [ + MySpacer(0, st.spaceBefore), + DelayedTable([['', rows]], style=t_style, colWidths=[0, None]), + MySpacer(0, st.spaceAfter), + ] return node.elements -class HandleMath(NodeHandler, docutils.nodes.math_block, docutils.nodes.math): +class HandleMath(NodeHandler, docutils.nodes.math_block, docutils.nodes.math): def gather_elements(self, client, node, style): label = node.attributes.get('label') return [Math(node.astext(), label, style.fontSize, style.textColor.rgb())] def get_text(self, client, node, replaceEnt): - #get style for current node - sty=client.styles.styleForNode(node) - node_fontsize=sty.fontSize - node_color='#'+sty.textColor.hexval()[2:] + # get style for current node + sty = client.styles.styleForNode(node) + node_fontsize = sty.fontSize + node_color = '#' + sty.textColor.hexval()[2:] label = node.attributes.get('label') - mf = Math(node.astext(),label=label,fontsize=node_fontsize,color=node_color) + mf = Math(node.astext(), label=label, fontsize=node_fontsize, color=node_color,) w, h = mf.wrap(0, 0) descent = mf.descent() img = mf.genImage() client.to_unlink.append(img) return '' % ( - img, w, h, -descent) + img, + w, + h, + -descent, + ) diff --git a/rst2pdf/genpdftext.py b/rst2pdf/genpdftext.py index d2047a614..2eab5d2b1 100644 --- a/rst2pdf/genpdftext.py +++ b/rst2pdf/genpdftext.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -#$URL$ -#$Date$ -#$Revision$ +# $URL$ +# $Date$ +# $Revision$ # See LICENSE.txt for licensing terms @@ -25,6 +25,7 @@ from .image import MyImage, missing from .log import log, nodeid + class FontHandler(NodeHandler): def get_pre_post(self, client, node, replaceEnt): return self.get_font_prefix(client, node, replaceEnt), '
' @@ -32,6 +33,7 @@ def get_pre_post(self, client, node, replaceEnt): def get_font_prefix(self, client, node, replaceEnt): return client.styleToFont(self.fontstyle) + class HandleText(NodeHandler, docutils.nodes.Text): def gather_elements(self, client, node, style): return [Paragraph(client.gather_pdftext(node), style)] @@ -42,14 +44,17 @@ def get_text(self, client, node, replaceEnt): text = escape(text) return text + class HandleStrong(NodeHandler, docutils.nodes.strong): pre = "" post = "" + class HandleEmphasis(NodeHandler, docutils.nodes.emphasis): pre = "" post = "" + class HandleLiteral(NodeHandler, docutils.nodes.literal): def get_pre_post(self, client, node, replaceEnt): @@ -69,57 +74,65 @@ def get_text(self, client, node, replaceEnt): text = text.replace(' ', ' ') return text + class HandleSuper(NodeHandler, docutils.nodes.superscript): pre = '' post = "" + class HandleSub(NodeHandler, docutils.nodes.subscript): pre = '' post = "" + class HandleTitleReference(FontHandler, docutils.nodes.title_reference): fontstyle = 'title_reference' + class HandleReference(NodeHandler, docutils.nodes.reference): def get_pre_post(self, client, node, replaceEnt): pre, post = '', '' uri = node.get('refuri') if uri: # Issue 366: links to "#" make no sense in a PDF - if uri =="#": + if uri == "#": return "", "" - if uri.startswith ('#'): + if uri.startswith('#'): pass - elif client.baseurl: # Need to join the uri with the base url + elif client.baseurl: # Need to join the uri with the base url uri = urljoin(client.baseurl, uri) if urlparse(uri)[0] and client.inlinelinks: # external inline reference - if uri in [node.astext(),"mailto:"+node.astext()]: + if uri in [node.astext(), "mailto:" + node.astext()]: # No point on repeating it post = u'' elif uri.startswith('mailto:'): - #No point on showing "mailto:" + # No point on showing "mailto:" post = u' (%s)' % uri[7:] else: post = u' (%s)' % uri else: # A plain old link - pre += u'' %\ - (uri, client.styles.linkColor) + pre += u'' % (uri, client.styles.linkColor,) post = '' + post else: uri = node.get('refid') if uri: - pre += u'' %\ - (uri, client.styles.linkColor) + pre += u'' % (uri, client.styles.linkColor,) post = '' + post return pre, post -class HandleOptions(HandleText, docutils.nodes.option_string, docutils.nodes.option_argument): + +class HandleOptions( + HandleText, docutils.nodes.option_string, docutils.nodes.option_argument +): pass -class HandleSysMessage(HandleText, docutils.nodes.system_message, docutils.nodes.problematic): + +class HandleSysMessage( + HandleText, docutils.nodes.system_message, docutils.nodes.problematic +): pre = '' post = "" @@ -131,6 +144,8 @@ def gather_elements(self, client, node, style): class HandleGenerated(HandleText, docutils.nodes.generated): pass + + # def get_text(self, client, node, replaceEnt): # if 'sectnum' in node['classes']: # # This is the child of a title with a section number @@ -138,6 +153,7 @@ class HandleGenerated(HandleText, docutils.nodes.generated): # node.parent['_sectnum'] = node.astext() # return node.astext() + class HandleImage(NodeHandler, docutils.nodes.image): def gather_elements(self, client, node, style): # FIXME: handle alt @@ -148,25 +164,32 @@ def gather_elements(self, client, node, style): st_name = 'image' if node.get('classes'): st_name = node.get('classes')[0] - style=client.styles[st_name] + style = client.styles[st_name] uri = str(node.get("uri")) - if uri.split("://")[0].lower() not in ('http','ftp','https'): - imgname = os.path.join(client.basedir,uri) + if uri.split("://")[0].lower() not in ('http', 'ftp', 'https'): + imgname = os.path.join(client.basedir, uri) else: imgname = uri try: w, h, kind = MyImage.size_for_node(node, client=client) except ValueError: # Broken image, return arbitrary stuff - imgname=missing + imgname = missing w, h, kind = 100, 100, 'direct' node.elements = [ - MyImage(filename=imgname, height=h, width=w, - kind=kind, client=client, target=target)] + MyImage( + filename=imgname, + height=h, + width=w, + kind=kind, + client=client, + target=target, + ) + ] alignment = node.get('align', '').upper() if not alignment: # There is no JUSTIFY for flowables, of course, so 4:LEFT - alignment = {0:'LEFT', 1:'CENTER', 2:'RIGHT', 4:'LEFT'}[style.alignment] + alignment = {0: 'LEFT', 1: 'CENTER', 2: 'RIGHT', 4: 'LEFT'}[style.alignment] if not alignment: alignment = 'CENTER' node.elements[0].image.hAlign = alignment @@ -181,39 +204,45 @@ def gather_elements(self, client, node, style): def get_text(self, client, node, replaceEnt): # First see if the image file exists, or else, # use image-missing.png - imgname = os.path.join(client.basedir,str(node.get("uri"))) + imgname = os.path.join(client.basedir, str(node.get("uri"))) try: w, h, kind = MyImage.size_for_node(node, client=client) except ValueError: # Broken image, return arbitrary stuff - imgname=missing + imgname = missing w, h, kind = 100, 100, 'direct' - alignment=node.get('align', 'CENTER').lower() + alignment = node.get('align', 'CENTER').lower() if alignment in ('top', 'middle', 'bottom'): - align='valign="%s"'%alignment + align = 'valign="%s"' % alignment else: - align='' + align = '' # TODO: inline images don't support SVG, vectors and PDF, # which may be surprising. So, work on converting them # previous to passing to reportlab. # Try to rasterize using the backend w, h, kind = MyImage.size_for_node(node, client=client) - uri=MyImage.raster(imgname, client) - return ''%\ - (uri, w, h, align) + uri = MyImage.raster(imgname, client) + return '' % (uri, w, h, align) + -class HandleFootRef(NodeHandler, docutils.nodes.footnote_reference,docutils.nodes.citation_reference): +class HandleFootRef( + NodeHandler, docutils.nodes.footnote_reference, docutils.nodes.citation_reference, +): def get_text(self, client, node, replaceEnt): # TODO: when used in Sphinx, all footnotes are autonumbered - anchors='' + anchors = '' for i in node.get('ids'): if i not in client.targets: - anchors+='' % i + anchors += '' % i client.targets.append(i) - return u'%s%s'%\ - (anchors, '#' + node.get('refid',node.astext()), - client.styles.linkColor, node.astext()) + return u'%s%s' % ( + anchors, + '#' + node.get('refid', node.astext()), + client.styles.linkColor, + node.astext(), + ) + class HandleTarget(NodeHandler, docutils.nodes.target): def gather_elements(self, client, node, style): @@ -238,6 +267,7 @@ def get_pre_post(self, client, node, replaceEnt): client.targets.append(node['refuri']) return pre, '' + class HandleInline(NodeHandler, docutils.nodes.inline): def get_pre_post(self, client, node, replaceEnt): r = client.styleToTags(node['classes'][0]) diff --git a/rst2pdf/image.py b/rst2pdf/image.py index 642f9e81f..d2f0917f5 100644 --- a/rst2pdf/image.py +++ b/rst2pdf/image.py @@ -37,8 +37,9 @@ missing = os.path.join(PATH, 'images', 'image-missing.jpg') -def defaultimage(filename, width=None, height=None, kind='direct', - mask="auto", lazy=1, srcinfo=None): +def defaultimage( + filename, width=None, height=None, kind='direct', mask="auto", lazy=1, srcinfo=None, +): ''' We have multiple image backends, including the stock reportlab one. This wrapper around the reportlab one allows us to pass the client RstToPdf object and the uri into all our backends, which they can @@ -47,7 +48,7 @@ def defaultimage(filename, width=None, height=None, kind='direct', return Image(filename, width, height, kind, mask, lazy) -class MyImage (Flowable): +class MyImage(Flowable): """A Image subclass that can: 1. Take a 'percentage_of_container' kind, @@ -65,8 +66,10 @@ def support_warning(cls): if cls.warned or PILImage: return cls.warned = True - log.warning("Support for images other than JPG," - " is now limited. Please install Pillow.") + log.warning( + "Support for images other than JPG," + " is now limited. Please install Pillow." + ) @staticmethod def split_uri(uri): @@ -82,13 +85,22 @@ def split_uri(uri): options = extra[1] return fname, extension, options - def __init__(self, filename, width=None, height=None, - kind='direct', mask="auto", lazy=1, client=None, target=None): + def __init__( + self, + filename, + width=None, + height=None, + kind='direct', + mask="auto", + lazy=1, + client=None, + target=None, + ): # Client is mandatory. Perhaps move it farther up if we refactor assert client is not None - self.__kind=kind + self.__kind = kind - if filename.split("://")[0].lower() in ('http','ftp','https'): + if filename.split("://")[0].lower() in ('http', 'ftp', 'https'): try: filename2, _ = urlretrieve(filename) if filename != filename2: @@ -96,21 +108,23 @@ def __init__(self, filename, width=None, height=None, filename = filename2 except IOError: filename = missing - self.filename, self._backend=self.get_backend(filename, client) + self.filename, self._backend = self.get_backend(filename, client) srcinfo = client, self.filename if kind == 'percentage_of_container': - self.image=self._backend(self.filename, width, height, - 'direct', mask, lazy, srcinfo) - self.image.drawWidth=width - self.image.drawHeight=height - self.__width=width - self.__height=height + self.image = self._backend( + self.filename, width, height, 'direct', mask, lazy, srcinfo + ) + self.image.drawWidth = width + self.image.drawHeight = height + self.__width = width + self.__height = height else: - self.image=self._backend(self.filename, width, height, - kind, mask, lazy, srcinfo) - self.__ratio=float(self.image.imageWidth)/self.image.imageHeight - self.__wrappedonce=False + self.image = self._backend( + self.filename, width, height, kind, mask, lazy, srcinfo + ) + self.__ratio = float(self.image.imageWidth) / self.image.imageHeight + self.__wrappedonce = False self.target = target @classmethod @@ -119,7 +133,7 @@ def raster(self, filename, client): reportlab can process""" if not os.path.exists(filename): - log.error("Missing image file: %s",filename) + log.error("Missing image file: %s", filename) return missing try: @@ -132,13 +146,13 @@ def raster(self, filename, client): # Last resort: try everything if PILImage: - ext='.png' + ext = '.png' else: - ext='.jpg' + ext = '.jpg' extension = os.path.splitext(filename)[-1][1:].lower() - if PILImage: # See if pil can process it + if PILImage: # See if pil can process it try: PILImage.open(filename) return filename @@ -148,10 +162,9 @@ def raster(self, filename, client): # PIL can't, so we can't self.support_warning() - log.error("Couldn't load image [%s]"%filename) + log.error("Couldn't load image [%s]" % filename) return missing - @classmethod def get_backend(self, uri, client): '''Given the filename of an image, returns (fname, backend) @@ -170,26 +183,25 @@ def get_backend(self, uri, client): backend = defaultimage - # Extract all the information from the URI filename, extension, options = self.split_uri(uri) if '*' in filename: - preferred=['gif','jpg','png', 'svg', 'pdf'] + preferred = ['gif', 'jpg', 'png', 'svg', 'pdf'] # Find out what images are available available = glob.glob(filename) - cfn=available[0] - cv=-10 + cfn = available[0] + cv = -10 for fn in available: - ext=fn.split('.')[-1] + ext = fn.split('.')[-1] if ext in preferred: - v=preferred.index(ext) + v = preferred.index(ext) else: - v=-1 + v = -1 if v > cv: - cv=v - cfn=fn + cv = v + cfn = fn # cfn should have our favourite type of # those available filename = cfn @@ -198,28 +210,32 @@ def get_backend(self, uri, client): # If the image doesn't exist, we use a 'missing' image if not os.path.exists(filename): - log.error("Missing image file: %s",filename) + log.error("Missing image file: %s", filename) filename = missing - if extension in ['svg','svgz']: - log.info('Backend for %s is SVGIMage'%filename) - backend=SVGImage + if extension in ['svg', 'svgz']: + log.info('Backend for %s is SVGIMage' % filename) + backend = SVGImage elif extension in ['pdf']: if VectorPdf is not None and filename is not missing: backend = VectorPdf filename = uri else: - log.warning("Minimal PDF image support "\ - "requires the vectorpdf extension [%s]", filename) + log.warning( + "Minimal PDF image support " + "requires the vectorpdf extension [%s]", + filename, + ) filename = missing elif extension != 'jpg' and not PILImage: # No way to make this work - log.error('To use a %s image you need Pillow installed [%s]',extension,filename) - filename=missing + log.error( + 'To use a %s image you need Pillow installed [%s]', extension, filename, + ) + filename = missing return filename, backend - @classmethod def size_for_node(self, node, client): '''Given a docutils image node, returns the size the image should have @@ -227,8 +243,8 @@ def size_for_node(self, node, client): That involves lots of guesswork''' uri = str(node.get("uri")) - if uri.split("://")[0].lower() not in ('http','ftp','https'): - uri = os.path.join(client.basedir,uri) + if uri.split("://")[0].lower() not in ('http', 'ftp', 'https'): + uri = os.path.join(client.basedir, uri) else: uri, _ = urlretrieve(uri) client.to_unlink.append(uri) @@ -240,7 +256,7 @@ def size_for_node(self, node, client): if not os.path.isfile(imgname): imgname = missing - scale = float(node.get('scale', 100))/100 + scale = float(node.get('scale', 100)) / 100 size_known = False # Figuring out the size to display of an image is ... annoying. @@ -255,7 +271,7 @@ def size_for_node(self, node, client): kind = 'direct' xdpi, ydpi = client.styles.def_dpi, client.styles.def_dpi extension = imgname.split('.')[-1].lower() - if extension in ['svg','svgz']: + if extension in ['svg', 'svgz']: iw, ih = SVGImage(imgname, srcinfo=srcinfo).wrap(0, 0) # These are in pt, so convert to px iw = iw * xdpi / 72 @@ -283,17 +299,23 @@ def size_for_node(self, node, client): iw, ih = img.size xdpi, ydpi = img.info.get('dpi', (xdpi, ydpi)) keeptrying = False - except IOError: # PIL throws this when it's a broken/unknown image + except IOError: # PIL throws this when it's a broken/unknown image pass if keeptrying: if extension not in ['jpg', 'jpeg']: - log.error("The image (%s, %s) is broken or in an unknown format" - , imgname, nodeid(node)) + log.error( + "The image (%s, %s) is broken or in an unknown format", + imgname, + nodeid(node), + ) raise ValueError else: # Can be handled by reportlab - log.warning("Can't figure out size of the image (%s, %s). Install PIL for better results." - , imgname, nodeid(node)) + log.warning( + "Can't figure out size of the image (%s, %s). Install PIL for better results.", + imgname, + nodeid(node), + ) iw = 1000 ih = 1000 @@ -309,60 +331,71 @@ def size_for_node(self, node, client): w = node.get('width') h = node.get('height') - if h is None and w is None: # Nothing specified + if h is None and w is None: # Nothing specified # Guess from iw, ih - log.debug("Using image %s without specifying size." + log.debug( + "Using image %s without specifying size." "Calculating based on image size at %ddpi [%s]", - imgname, xdpi, nodeid(node)) - w = iw*inch/xdpi - h = ih*inch/ydpi + imgname, + xdpi, + nodeid(node), + ) + w = iw * inch / xdpi + h = ih * inch / ydpi elif w is not None: # Node specifies only w # In this particular case, we want the default unit # to be pixels so we work like rst2html if w[-1] == '%': kind = 'percentage_of_container' - w=int(w[:-1]) + w = int(w[:-1]) else: # This uses default DPI setting because we # are not using the image's "natural size" # this is what LaTeX does, according to the # docutils mailing list discussion - w = client.styles.adjustUnits(w, client.styles.tw, - default_unit='px') + w = client.styles.adjustUnits(w, client.styles.tw, default_unit='px') if h is None: # h is set from w with right aspect ratio - h = w*ih/iw + h = w * ih / iw else: - h = client.styles.adjustUnits(h, ih*inch/ydpi, default_unit='px') + h = client.styles.adjustUnits(h, ih * inch / ydpi, default_unit='px') elif h is not None and w is None: if h[-1] != '%': - h = client.styles.adjustUnits(h, ih*inch/ydpi, default_unit='px') + h = client.styles.adjustUnits(h, ih * inch / ydpi, default_unit='px') # w is set from h with right aspect ratio - w = h*iw/ih + w = h * iw / ih else: - log.error('Setting height as a percentage does **not** work. '\ - 'ignoring height parameter [%s]', nodeid(node)) + log.error( + 'Setting height as a percentage does **not** work. ' + 'ignoring height parameter [%s]', + nodeid(node), + ) # Set both from image data - w = iw*inch/xdpi - h = ih*inch/ydpi + w = iw * inch / xdpi + h = ih * inch / ydpi # Apply scale factor - w = w*scale - h = h*scale + w = w * scale + h = h * scale # And now we have this probably completely bogus size! - log.info("Image %s size calculated: %fcm by %fcm [%s]", - imgname, w/cm, h/cm, nodeid(node)) + log.info( + "Image %s size calculated: %fcm by %fcm [%s]", + imgname, + w / cm, + h / cm, + nodeid(node), + ) return w, h, kind - def _restrictSize(self,aW,aH): + def _restrictSize(self, aW, aH): return self.image._restrictSize(aW, aH) - def _unRestrictSize(self,aW,aH): + def _unRestrictSize(self, aW, aH): return self.image._unRestrictSize(aW, aH) def __deepcopy__(self, *whatever): @@ -371,15 +404,17 @@ def __deepcopy__(self, *whatever): return copy(self) def wrap(self, availWidth, availHeight): - if self.__kind=='percentage_of_container': - w, h= self.__width, self.__height + if self.__kind == 'percentage_of_container': + w, h = self.__width, self.__height if not w: - log.warning('Scaling image as % of container with w unset.' - 'This should not happen, setting to 100') + log.warning( + 'Scaling image as % of container with w unset.' + 'This should not happen, setting to 100' + ) w = 100 - scale=w/100. - w = availWidth*scale - h = w/self.__ratio + scale = w / 100.0 + w = availWidth * scale + h = w / self.__ratio self.image.drawWidth, self.image.drawHeight = w, h return w, h else: @@ -392,14 +427,16 @@ def wrap(self, availWidth, availHeight): # adjust by height # FIXME get rst file info (line number) # here for better error message - log.warning('image %s is too tall for the '\ - 'frame, rescaling'%\ - self.filename) + log.warning( + 'image %s is too tall for the ' + 'frame, rescaling' % self.filename + ) self.image.drawHeight = availHeight - self.image.drawWidth = availHeight*self.__ratio + self.image.drawWidth = availHeight * self.__ratio elif self.image.drawWidth > availWidth: - log.warning('image %s is too wide for the frame, rescaling'%\ - self.filename) + log.warning( + 'image %s is too wide for the frame, rescaling' % self.filename + ) self.image.drawWidth = availWidth self.image.drawHeight = availWidth / self.__ratio return self.image.wrap(availWidth, availHeight) @@ -408,15 +445,18 @@ def drawOn(self, canv, x, y, _sW=0): if self.target: offset = 0 if self.image.hAlign == 'CENTER': - offset = _sW / 2. + offset = _sW / 2.0 elif self.image.hAlign == 'RIGHT': offset = _sW - canv.linkURL(self.target, - ( - x + offset, y, + canv.linkURL( + self.target, + ( + x + offset, + y, x + offset + self.image.drawWidth, - y + self.image.drawHeight), - relative = True, - #thickness = 3, - ) + y + self.image.drawHeight, + ), + relative=True, + # thickness = 3, + ) return self.image.drawOn(canv, x, y, _sW) diff --git a/rst2pdf/languages.py b/rst2pdf/languages.py index c15945322..8eccbe8e3 100644 --- a/rst2pdf/languages.py +++ b/rst2pdf/languages.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # See LICENSE.txt for licensing terms -#$URL$ -#$Date$ -#$Revision$ +# $URL$ +# $Date$ +# $Revision$ from docutils.languages import get_language as get_language @@ -13,14 +13,16 @@ def get_language_silent(lang): """Docutils get_language() with patches for older versions.""" try: return get_language(lang) - except TypeError as err: # Docutils 0.8.1 + except TypeError as err: # Docutils 0.8.1 if 'get_language() takes exactly 2 arguments' in str(err): + class SilentReporter(object): def warning(self, msg): pass + return get_language(lang, SilentReporter()) - raise # re-raise any other TypeError - except ImportError: # Docutils < 0.8 + raise # re-raise any other TypeError + except ImportError: # Docutils < 0.8 return get_language('en') @@ -28,15 +30,19 @@ def get_language_available(lang): """Docutils get_language() also returning the available language.""" module = get_language_silent(lang) docutils_lang = module.__name__.rsplit('.', 1)[-1] - if (docutils_lang == 'en' and docutils_lang != lang - and '_' in lang): + if docutils_lang == 'en' and docutils_lang != lang and '_' in lang: module = get_language_silent(lang.split('_', 1)[0]) docutils_lang = module.__name__.rsplit('.', 1)[-1] if docutils_lang != lang: - warn = (docutils_lang.split('_', 1)[0] == lang.split('_', 1)[0] - and log.info or log.warning) - warn("Language '%s' not supported by Docutils," - " using '%s' instead." % (lang, docutils_lang)) + warn = ( + docutils_lang.split('_', 1)[0] == lang.split('_', 1)[0] + and log.info + or log.warning + ) + warn( + "Language '%s' not supported by Docutils," + " using '%s' instead." % (lang, docutils_lang) + ) if docutils_lang == 'en' and lang.split('_', 1)[0] != 'en': lang = 'en_US' return lang, docutils_lang, module diff --git a/rst2pdf/log.py b/rst2pdf/log.py index d467c6180..2294c1c3e 100644 --- a/rst2pdf/log.py +++ b/rst2pdf/log.py @@ -15,14 +15,16 @@ def nodeid(node): """Given a node, tries to return a way to see where it was in the source text""" - fname='UNKNOWN' - line='UNKNOWN' + fname = 'UNKNOWN' + line = 'UNKNOWN' try: - if node.line: line=str(node.line) + if node.line: + line = str(node.line) except: pass try: - if node.source: fname=str(node.source) + if node.source: + fname = str(node.source) except: pass - return 'near line %s in file %s'%(line,fname) + return 'near line %s in file %s' % (line, fname) diff --git a/rst2pdf/math_flowable.py b/rst2pdf/math_flowable.py index 1d3900782..c67159847 100644 --- a/rst2pdf/math_flowable.py +++ b/rst2pdf/math_flowable.py @@ -23,15 +23,16 @@ from matplotlib.colors import ColorConverter fonts = {} + def enclose(s): """Enclose the string in $...$ if needed""" if not re.match(r'.*\$.+\$.*', s, re.MULTILINE | re.DOTALL): s = u"$%s$" % s return s -class Math(Flowable): - def __init__(self, s, label=None, fontsize=12,color='black'): +class Math(Flowable): + def __init__(self, s, label=None, fontsize=12, color='black'): self.s = s self.label = label self.fontsize = fontsize @@ -39,18 +40,27 @@ def __init__(self, s, label=None, fontsize=12,color='black'): if HAS_MATPLOTLIB: self.parser = mathtext.MathTextParser("Pdf") else: - log.error("Math support not available," + log.error( + "Math support not available," " some parts of this document will be rendered incorrectly." - " Install matplotlib.") + " Install matplotlib." + ) Flowable.__init__(self) - self.hAlign='CENTER' + self.hAlign = 'CENTER' def wrap(self, aW, aH): if HAS_MATPLOTLIB: try: - width, height, descent, glyphs, \ - rects, used_characters = self.parser.parse( - enclose(self.s), 72, prop=FontProperties(size=self.fontsize)) + ( + width, + height, + descent, + glyphs, + rects, + used_characters, + ) = self.parser.parse( + enclose(self.s), 72, prop=FontProperties(size=self.fontsize), + ) return width, height except: pass @@ -58,32 +68,45 @@ def wrap(self, aW, aH): return 10, 10 def drawOn(self, canv, x, y, _sW=0): - if _sW and hasattr(self,'hAlign'): - from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY + if _sW and hasattr(self, 'hAlign'): + from reportlab.lib.enums import ( + TA_LEFT, + TA_CENTER, + TA_RIGHT, + TA_JUSTIFY, + ) + a = self.hAlign - if a in ('CENTER','CENTRE', TA_CENTER): - x = x + 0.5*_sW - elif a in ('RIGHT',TA_RIGHT): + if a in ('CENTER', 'CENTRE', TA_CENTER): + x = x + 0.5 * _sW + elif a in ('RIGHT', TA_RIGHT): x = x + _sW - elif a not in ('LEFT',TA_LEFT): - raise ValueError("Bad hAlign value "+str(a)) + elif a not in ('LEFT', TA_LEFT): + raise ValueError("Bad hAlign value " + str(a)) height = 0 if HAS_MATPLOTLIB: global fonts canv.saveState() canv.translate(x, y) try: - width, height, descent, glyphs, \ - rects, used_characters = self.parser.parse( - enclose(self.s), 72, prop=FontProperties(size=self.fontsize)) + ( + width, + height, + descent, + glyphs, + rects, + used_characters, + ) = self.parser.parse( + enclose(self.s), 72, prop=FontProperties(size=self.fontsize), + ) for ox, oy, fontname, fontsize, num, symbol_name in glyphs: if not fontname in fonts: fonts[fontname] = fontname pdfmetrics.registerFont(TTFont(fontname, fontname)) canv.setFont(fontname, fontsize) - col_conv=ColorConverter() - rgb_color=col_conv.to_rgb(self.color) - canv.setFillColorRGB(rgb_color[0],rgb_color[1],rgb_color[2]) + col_conv = ColorConverter() + rgb_color = col_conv.to_rgb(self.color) + canv.setFillColorRGB(rgb_color[0], rgb_color[1], rgb_color[2]) if six.PY2: canv.drawString(ox, oy, unichr(num)) else: @@ -92,28 +115,36 @@ def drawOn(self, canv, x, y, _sW=0): canv.setLineWidth(0) canv.setDash([]) for ox, oy, width, height in rects: - canv.rect(ox, oy+2*height, width, height, fill=1) + canv.rect(ox, oy + 2 * height, width, height, fill=1) except: # FIXME: report error - col_conv=ColorConverter() - rgb_color=col_conv.to_rgb(self.color) - canv.setFillColorRGB(rgb_color[0],rgb_color[1],rgb_color[2]) - canv.drawString(0,0,self.s) + col_conv = ColorConverter() + rgb_color = col_conv.to_rgb(self.color) + canv.setFillColorRGB(rgb_color[0], rgb_color[1], rgb_color[2]) + canv.drawString(0, 0, self.s) canv.restoreState() else: canv.saveState() canv.drawString(x, y, self.s) canv.restoreState() if self.label: - log.info('Drawing equation-%s'%self.label) - canv.bookmarkHorizontal('equation-%s'%self.label,0,height) + log.info('Drawing equation-%s' % self.label) + canv.bookmarkHorizontal('equation-%s' % self.label, 0, height) def descent(self): """Return the descent of this flowable, useful to align it when used inline.""" if HAS_MATPLOTLIB: - width, height, descent, glyphs, rects, used_characters = \ - self.parser.parse(enclose(self.s), 72, prop=FontProperties(size=self.fontsize)) + ( + width, + height, + descent, + glyphs, + rects, + used_characters, + ) = self.parser.parse( + enclose(self.s), 72, prop=FontProperties(size=self.fontsize) + ) return descent return 0 @@ -143,15 +174,24 @@ def genImage(self): ) if not HAS_MATPLOTLIB: - img = Image.new('RGBA', (120, 120), (255,255,255,0)) + img = Image.new('RGBA', (120, 120), (255, 255, 255, 0)) else: - width, height, descent, glyphs,\ - rects, used_characters = self.parser.parse( - enclose(self.s), dpi, prop=FontProperties(size=self.fontsize)) - img = Image.new('RGBA', (int(width*scale), int(height*scale)),(255,255,255,0)) + ( + width, + height, + descent, + glyphs, + rects, + used_characters, + ) = self.parser.parse( + enclose(self.s), dpi, prop=FontProperties(size=self.fontsize) + ) + img = Image.new( + 'RGBA', (int(width * scale), int(height * scale)), (255, 255, 255, 0), + ) draw = ImageDraw.Draw(img) for ox, oy, fontname, fontsize, num, symbol_name in glyphs: - font = ImageFont.truetype(fontname, int(fontsize*scale)) + font = ImageFont.truetype(fontname, int(fontsize * scale)) if six.PY2: tw, th = draw.textsize(unichr(num), font=font) else: @@ -159,21 +199,33 @@ def genImage(self): # No, I don't understand why that 4 is there. # As we used to say in the pure math # department, that was a numerical solution. - col_conv=ColorConverter() - fc=col_conv.to_rgb(self.color) - rgb_color=(int(fc[0]*255),int(fc[1]*255),int(fc[2]*255)) + col_conv = ColorConverter() + fc = col_conv.to_rgb(self.color) + rgb_color = ( + int(fc[0] * 255), + int(fc[1] * 255), + int(fc[2] * 255), + ) if six.PY2: - draw.text((ox*scale, (height - oy - fontsize + 4)*scale), - unichr(num), font=font,fill=rgb_color) + draw.text( + (ox * scale, (height - oy - fontsize + 4) * scale), + unichr(num), + font=font, + fill=rgb_color, + ) else: - draw.text((ox*scale, (height - oy - fontsize + 4)*scale), - chr(num), font=font,fill=rgb_color) + draw.text( + (ox * scale, (height - oy - fontsize + 4) * scale), + chr(num), + font=font, + fill=rgb_color, + ) for ox, oy, w, h in rects: - x1 = ox*scale - x2 = x1 + w*scale - y1 = (height - oy)*scale - y2 = y1 + h*scale - draw.rectangle([x1, y1, x2, y2],(0,0,0)) + x1 = ox * scale + x2 = x1 + w * scale + y1 = (height - oy) * scale + y2 = y1 + h * scale + draw.rectangle([x1, y1, x2, y2], (0, 0, 0)) fh, fn = tempfile.mkstemp(suffix=".png") os.close(fh) @@ -183,6 +235,7 @@ def genImage(self): if __name__ == "__main__": doc = SimpleDocTemplate("mathtest.pdf") - Story = [Math(r'\mathcal{R}\prod_{i=\alpha\mathcal{B}}'\ - r'^\infty a_i\sin(2 \pi f x_i)')] + Story = [ + Math(r'\mathcal{R}\prod_{i=\alpha\mathcal{B}}' r'^\infty a_i\sin(2 \pi f x_i)') + ] doc.build(Story) diff --git a/rst2pdf/nodehandlers.py b/rst2pdf/nodehandlers.py index b56131072..14efdf64a 100644 --- a/rst2pdf/nodehandlers.py +++ b/rst2pdf/nodehandlers.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # See LICENSE.txt for licensing terms -#$URL$ -#$Date$ -#$Revision$ +# $URL$ +# $Date$ +# $Revision$ # Import all node handler modules here. # The act of importing them wires them in. @@ -10,7 +10,7 @@ from . import genelements from . import genpdftext -#sphinxnodes needs these +# sphinxnodes needs these from .genpdftext import NodeHandler, FontHandler, HandleEmphasis # createpdf needs this diff --git a/rst2pdf/noop_directive.py b/rst2pdf/noop_directive.py index 02964bead..72f5808cd 100644 --- a/rst2pdf/noop_directive.py +++ b/rst2pdf/noop_directive.py @@ -1,8 +1,19 @@ from docutils.parsers import rst -def noop_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): + +def noop_directive( + name, + arguments, + options, + content, + lineno, + content_offset, + block_text, + state, + state_machine, +): node_list = [] return node_list - + + noop_directive.content = ["*"] diff --git a/rst2pdf/oddeven_directive.py b/rst2pdf/oddeven_directive.py index 4b13d0abb..1a9490056 100644 --- a/rst2pdf/oddeven_directive.py +++ b/rst2pdf/oddeven_directive.py @@ -7,9 +7,11 @@ from docutils.nodes import Admonition, Element from docutils.parsers.rst import directives + class OddEvenNode(Admonition, Element): pass + class OddEven(rst.Directive): """A custom directive that allows alternative contents to be generated on odd and even pages. It can contain only two children, so use containers @@ -30,4 +32,3 @@ def run(self): directives.register_directive("oddeven", OddEven) - diff --git a/rst2pdf/opt_imports.py b/rst2pdf/opt_imports.py index e1a088f48..a7ba56ecc 100644 --- a/rst2pdf/opt_imports.py +++ b/rst2pdf/opt_imports.py @@ -21,17 +21,18 @@ from wordaxe import version as wordaxe_version from wordaxe.rl.paragraph import Paragraph from wordaxe.rl.styles import ParagraphStyle, getSampleStyleSheet + # PyHnjHyphenator is broken for non-ascii characters, so # let's not use it and avoid useless crashes (http://is.gd/19efQ) - #from wordaxe.PyHnjHyphenator import PyHnjHyphenator + # from wordaxe.PyHnjHyphenator import PyHnjHyphenator # If basehyphenator doesn't load, wordaxe is broken # pyhyphenator and DCW *may* not load. from wordaxe.BaseHyphenator import BaseHyphenator + try: - from wordaxe.plugins.PyHyphenHyphenator \ - import PyHyphenHyphenator + from wordaxe.plugins.PyHyphenHyphenator import PyHyphenHyphenator except: pass try: @@ -61,7 +62,7 @@ try: from reportlab.platypus.flowables import NullDraw -except ImportError: # Probably RL 2.1 +except ImportError: # Probably RL 2.1 from reportlab.platypus.flowables import Flowable as NullDraw try: diff --git a/rst2pdf/pdfbuilder.py b/rst2pdf/pdfbuilder.py index 4f62281c0..45837a6cb 100644 --- a/rst2pdf/pdfbuilder.py +++ b/rst2pdf/pdfbuilder.py @@ -15,6 +15,7 @@ import six import logging + if six.PY2: try: import parser @@ -35,6 +36,7 @@ from traceback import print_exc from io import BytesIO + if six.PY2: from urlparse import urljoin, urlparse, urlunparse else: @@ -54,6 +56,7 @@ from sphinx.builders import Builder from sphinx.util.console import darkgreen, red from sphinx.util import SEP + if sphinx.__version__ >= '2.1': from sphinx.errors import NoUri else: @@ -62,6 +65,7 @@ from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.locale import admonitionlabels, versionlabels from sphinx.transforms import SphinxTransform + if sphinx.__version__ >= '1.': from sphinx.locale import _ @@ -73,6 +77,7 @@ # Template engine for covers import jinja2 + class PDFBuilder(Builder): name = 'pdf' out_suffix = '.pdf' @@ -95,52 +100,83 @@ def write(self, *ignored): try: docname, targetname, title, author = entry[:4] # Custom options per document - if len(entry)>4 and isinstance(entry[4],dict): - opts=entry[4] + if len(entry) > 4 and isinstance(entry[4], dict): + opts = entry[4] else: - opts={} + opts = {} self.spinx_logger.info("processing " + targetname + "... ") self.opts = opts + class dummy: - extensions=self.config.pdf_extensions + extensions = self.config.pdf_extensions createpdf.add_extensions(dummy()) - self.page_template=opts.get('pdf_page_template',self.config.pdf_page_template) - - docwriter = PDFWriter(self, - stylesheets=opts.get('pdf_stylesheets',self.config.pdf_stylesheets), - language=opts.get('pdf_language',self.config.pdf_language), - breaklevel=opts.get('pdf_break_level',self.config.pdf_break_level), - breakside=opts.get('pdf_breakside',self.config.pdf_breakside), - fontpath=opts.get('pdf_font_path',self.config.pdf_font_path), - fitmode=opts.get('pdf_fit_mode',self.config.pdf_fit_mode), - compressed=opts.get('pdf_compressed',self.config.pdf_compressed), - inline_footnotes=opts.get('pdf_inline_footnotes',self.config.pdf_inline_footnotes), - splittables=opts.get('pdf_splittables',self.config.pdf_splittables), - repeat_table_rows=opts.get('pdf_repeat_table_rows',self.config.pdf_repeat_table_rows), - default_dpi=opts.get('pdf_default_dpi',self.config.pdf_default_dpi), - page_template=self.page_template, - invariant=opts.get('pdf_invariant',self.config.pdf_invariant), - real_footnotes=opts.get('pdf_real_footnotes',self.config.pdf_real_footnotes), - use_toc=opts.get('pdf_use_toc',self.config.pdf_use_toc), - toc_depth=opts.get('pdf_toc_depth',self.config.pdf_toc_depth), - use_coverpage=opts.get('pdf_use_coverpage',self.config.pdf_use_coverpage), - use_numbered_links=opts.get('pdf_use_numbered_links',self.config.pdf_use_numbered_links), - fit_background_mode=opts.get('pdf_fit_background_mode',self.config.pdf_fit_background_mode), - baseurl=opts.get('pdf_baseurl',self.config.pdf_baseurl), - section_header_depth=opts.get('section_header_depth',self.config.section_header_depth), - srcdir=self.srcdir, - style_path=opts.get('pdf_style_path', self.config.pdf_style_path), - config=self.config, - ) + self.page_template = opts.get( + 'pdf_page_template', self.config.pdf_page_template + ) + + docwriter = PDFWriter( + self, + stylesheets=opts.get( + 'pdf_stylesheets', self.config.pdf_stylesheets + ), + language=opts.get('pdf_language', self.config.pdf_language), + breaklevel=opts.get('pdf_break_level', self.config.pdf_break_level), + breakside=opts.get('pdf_breakside', self.config.pdf_breakside), + fontpath=opts.get('pdf_font_path', self.config.pdf_font_path), + fitmode=opts.get('pdf_fit_mode', self.config.pdf_fit_mode), + compressed=opts.get('pdf_compressed', self.config.pdf_compressed), + inline_footnotes=opts.get( + 'pdf_inline_footnotes', self.config.pdf_inline_footnotes, + ), + splittables=opts.get( + 'pdf_splittables', self.config.pdf_splittables + ), + repeat_table_rows=opts.get( + 'pdf_repeat_table_rows', self.config.pdf_repeat_table_rows, + ), + default_dpi=opts.get( + 'pdf_default_dpi', self.config.pdf_default_dpi + ), + page_template=self.page_template, + invariant=opts.get('pdf_invariant', self.config.pdf_invariant), + real_footnotes=opts.get( + 'pdf_real_footnotes', self.config.pdf_real_footnotes + ), + use_toc=opts.get('pdf_use_toc', self.config.pdf_use_toc), + toc_depth=opts.get('pdf_toc_depth', self.config.pdf_toc_depth), + use_coverpage=opts.get( + 'pdf_use_coverpage', self.config.pdf_use_coverpage + ), + use_numbered_links=opts.get( + 'pdf_use_numbered_links', self.config.pdf_use_numbered_links, + ), + fit_background_mode=opts.get( + 'pdf_fit_background_mode', self.config.pdf_fit_background_mode, + ), + baseurl=opts.get('pdf_baseurl', self.config.pdf_baseurl), + section_header_depth=opts.get( + 'section_header_depth', self.config.section_header_depth, + ), + srcdir=self.srcdir, + style_path=opts.get('pdf_style_path', self.config.pdf_style_path), + config=self.config, + ) tgt_file = path.join(self.outdir, targetname + self.out_suffix) - destination = FileOutput(destination=open(tgt_file,'wb'), encoding='utf-8') - doctree = self.assemble_doctree(docname,title,author, - appendices=opts.get('pdf_appendices', self.config.pdf_appendices) or []) - doctree.settings.author=author - doctree.settings.title=title + destination = FileOutput( + destination=open(tgt_file, 'wb'), encoding='utf-8' + ) + doctree = self.assemble_doctree( + docname, + title, + author, + appendices=opts.get('pdf_appendices', self.config.pdf_appendices) + or [], + ) + doctree.settings.author = author + doctree.settings.title = title self.spinx_logger.info("done") self.spinx_logger.info("writing " + targetname + "... ") docwriter.write(doctree, destination) @@ -152,19 +188,22 @@ class dummy: def init_document_data(self): preliminary_document_data = map(list, self.config.pdf_documents) if not preliminary_document_data: - self.warn('no "pdf_documents" config value found; no documents ' - 'will be written') + self.warn( + 'no "pdf_documents" config value found; no documents ' 'will be written' + ) return # assign subdirs to titles self.titles = [] for entry in preliminary_document_data: docname = entry[0] if docname not in self.env.all_docs: - self.warn('"pdf_documents" config value references unknown ' - 'document %s' % docname) + self.warn( + '"pdf_documents" config value references unknown ' + 'document %s' % docname + ) continue self.document_data.append(entry) - if docname.endswith(SEP+'index'): + if docname.endswith(SEP + 'index'): docname = docname[:-5] self.titles.append((docname, entry[2])) @@ -175,6 +214,7 @@ def assemble_doctree(self, docname, title, author, appendices): self.docnames = set([docname]) self.spinx_logger.info(darkgreen(docname) + " ") + def process_tree(docname, tree): tree = tree.deepcopy() for toctreenode in tree.traverse(addnodes.toctree): @@ -183,12 +223,15 @@ def process_tree(docname, tree): for includefile in includefiles: try: self.spinx_logger.info(darkgreen(includefile) + " ") - subtree = process_tree(includefile, - self.env.get_doctree(includefile)) + subtree = process_tree( + includefile, self.env.get_doctree(includefile) + ) self.docnames.add(includefile) except Exception: - self.warn('%s: toctree contains ref to nonexisting file %r'\ - % (docname, includefile)) + self.warn( + '%s: toctree contains ref to nonexisting file %r' + % (docname, includefile) + ) else: sof = addnodes.start_of_file(docname=includefile) sof.children = subtree.children @@ -201,29 +244,32 @@ def process_tree(docname, tree): self.docutils_languages = {} if self.config.language: - self.docutils_languages[self.config.language] = \ - get_language_available(self.config.language)[2] + self.docutils_languages[self.config.language] = get_language_available( + self.config.language + )[2] - if self.opts.get('pdf_use_index',self.config.pdf_use_index): + if self.opts.get('pdf_use_index', self.config.pdf_use_index): # Add index at the end of the document # This is a hack. create_index creates an index from # ALL the documents data, not just this one. # So, we preserve a copy, use just what we need, then # restore it. - t=copy(self.env.indexentries) + t = copy(self.env.indexentries) try: - self.env.indexentries={docname:self.env.indexentries[docname+'-gen']} + self.env.indexentries = { + docname: self.env.indexentries[docname + '-gen'] + } except KeyError: - self.env.indexentries={} + self.env.indexentries = {} for dname in self.docnames: - self.env.indexentries[dname]=t.get(dname,[]) + self.env.indexentries[dname] = t.get(dname, []) genindex = IndexEntries(self.env).create_index(self) - self.env.indexentries=t + self.env.indexentries = t # EOH (End Of Hack) - if genindex: # No point in creating empty indexes - index_nodes=genindex_nodes(genindex) + if genindex: # No point in creating empty indexes + index_nodes = genindex_nodes(genindex) tree.append(nodes.raw(text='OddPageBreak twoColumn', format='pdf')) tree.append(index_nodes) @@ -239,13 +285,13 @@ def process_tree(docname, tree): if indexname not in indices_config: continue # deprecated config value - if indexname == 'py-modindex' and \ - not self.config.pdf_use_modindex: + if indexname == 'py-modindex' and not self.config.pdf_use_modindex: continue content, collapse = indexcls(domain).generate() if content: self.domain_indices.append( - (indexname, indexcls, content, collapse)) + (indexname, indexcls, content, collapse) + ) # self.domain_indices contains a list of indices to generate, like # this: @@ -259,45 +305,50 @@ def process_tree(docname, tree): for indexname, indexcls, content, collapse in self.domain_indices: indexcontext = dict( - indextitle = indexcls.localname, - content = content, - collapse_index = collapse, + indextitle=indexcls.localname, content=content, collapse_index=collapse, ) # In HTML this is handled with a Jinja template, domainindex.html # We have to generate docutils stuff right here in the same way. self.spinx_logger.info(' ' + indexname) print - output=['DUMMY','=====','', - '.. _modindex:\n\n'] - t=indexcls.localname - t+='\n'+'='*len(t)+'\n' + output = ['DUMMY', '=====', '', '.. _modindex:\n\n'] + t = indexcls.localname + t += '\n' + '=' * len(t) + '\n' output.append(t) for letter, entries in content: - output.append('.. cssclass:: heading4\n\n%s\n\n'%letter) - for (name, grouptype, page, anchor, - extra, qualifier, description) in entries: + output.append('.. cssclass:: heading4\n\n%s\n\n' % letter) + for ( + name, + grouptype, + page, + anchor, + extra, + qualifier, + description, + ) in entries: if qualifier: - q = '[%s]'%qualifier + q = '[%s]' % qualifier else: q = '' if extra: - e = '(%s)'%extra + e = '(%s)' % extra else: e = '' - output.append ('`%s <#%s>`_ %s %s'%(name, anchor, e, q)) - output.append(' %s'%description) + output.append('`%s <#%s>`_ %s %s' % (name, anchor, e, q)) + output.append(' %s' % description) output.append('') dt = docutils.core.publish_doctree('\n'.join(output))[1:] - dt.insert(0,nodes.raw(text='OddPageBreak twoColumn', format='pdf')) + dt.insert(0, nodes.raw(text='OddPageBreak twoColumn', format='pdf')) tree.extend(dt) - if appendices: - tree.append(nodes.raw(text='OddPageBreak %s'%self.page_template, format='pdf')) + tree.append( + nodes.raw(text='OddPageBreak %s' % self.page_template, format='pdf') + ) self.spinx_logger.info() self.spinx_logger.info('adding appendixes...') for docname in appendices: @@ -312,8 +363,13 @@ def process_tree(docname, tree): # This code can be removed when we drop support for Python 2 if sphinx.__version__ > '1.7.9' and sphinx.__version__ < '2.0.0': for i in range(len(self.env.app.registry.post_transforms)): - if self.env.app.registry.post_transforms[i].__name__ == 'HighlightLanguageTransform': - self.env.app.registry.post_transforms[i] = HighlightLanguageTransform + if ( + self.env.app.registry.post_transforms[i].__name__ + == 'HighlightLanguageTransform' + ): + self.env.app.registry.post_transforms[ + i + ] = HighlightLanguageTransform break self.spinx_logger.info("resolving references...") @@ -323,10 +379,15 @@ def process_tree(docname, tree): # This needs work, need to keep track of all targets # so I don't replace and create hanging refs, which # crash - if pendingnode.get('reftarget',None) == 'genindex'\ - and self.config.pdf_use_index: - pendingnode.replace_self(nodes.reference(text=pendingnode.astext(), - refuri=pendingnode['reftarget'])) + if ( + pendingnode.get('reftarget', None) == 'genindex' + and self.config.pdf_use_index + ): + pendingnode.replace_self( + nodes.reference( + text=pendingnode.astext(), refuri=pendingnode['reftarget'], + ) + ) # FIXME: probably need to handle dangling links to domain-specific indexes else: # FIXME: This is from the LaTeX builder and I still don't understand it @@ -346,12 +407,12 @@ def process_tree(docname, tree): else: pass pendingnode.replace_self(newnodes) - #else: - #pass + # else: + # pass return tree def get_target_uri(self, docname, typ=None): - #print 'GTU',docname,typ + # print 'GTU',docname,typ # FIXME: production lists are not supported yet! if typ == 'token': # token references are always inside production lists and must be @@ -361,17 +422,17 @@ def get_target_uri(self, docname, typ=None): # It can be a 'main' document: for doc in self.document_data: - if doc[0]==docname: - return "pdf:"+doc[1]+'.pdf' + if doc[0] == docname: + return "pdf:" + doc[1] + '.pdf' # It can be in some other document's toctree for indexname, toctree in self.env.toctree_includes.items(): if docname in toctree: for doc in self.document_data: - if doc[0]==indexname: - return "pdf:"+doc[1]+'.pdf' + if doc[0] == indexname: + return "pdf:" + doc[1] + '.pdf' # No idea raise NoUri - else: # Local link + else: # Local link return "" def get_relative_uri(self, from_, to, typ=None): @@ -396,34 +457,43 @@ def get_outdated_docs(self): # source doesn't exist anymore pass + def genindex_nodes(genindexentries): indexlabel = _('Index') - indexunder = '='*len(indexlabel) - output=['DUMMY','=====','.. _genindex:\n\n',indexlabel,indexunder,''] + indexunder = '=' * len(indexlabel) + output = [ + 'DUMMY', + '=====', + '.. _genindex:\n\n', + indexlabel, + indexunder, + '', + ] for key, entries in genindexentries: - output.append('.. cssclass:: heading4\n\n%s\n\n'%key) # initial + output.append('.. cssclass:: heading4\n\n%s\n\n' % key) # initial for entryname, entryvalue in entries: links, subitems = entryvalue[0:2] if links: - output.append('`%s <#%s>`_'%(entryname,nodes.make_id(links[0][1]))) - for i,link in enumerate(links[1:]): - output[-1]+=(' `[%s] <#%s>`_ '%(i+1,nodes.make_id(link[1]))) + output.append('`%s <#%s>`_' % (entryname, nodes.make_id(links[0][1]))) + for i, link in enumerate(links[1:]): + output[-1] += ' `[%s] <#%s>`_ ' % (i + 1, nodes.make_id(link[1]),) output.append('') else: output.append(entryname) if subitems: for subentryname, subentrylinks in subitems: if subentrylinks: - output.append(' `%s <%s>`_'%(subentryname,subentrylinks[0])) - for i,link in enumerate(subentrylinks[1:]): - output[-1]+=(' `[%s] <%s>`_ '%(i+1,link)) + output.append( + ' `%s <%s>`_' % (subentryname, subentrylinks[0]) + ) + for i, link in enumerate(subentrylinks[1:]): + output[-1] += ' `[%s] <%s>`_ ' % (i + 1, link) output.append('') else: output.append(subentryname) output.append('') - doctree = docutils.core.publish_doctree('\n'.join(output)) return doctree[1] @@ -434,17 +504,17 @@ class PDFContents(Contents): def build_contents(self, node, level=0): level += 1 - sections=[] + sections = [] # Replaced this with the for below to make it work for Sphinx # trees. - #sections = [sect for sect in node if isinstance(sect, nodes.section)] + # sections = [sect for sect in node if isinstance(sect, nodes.section)] for sect in node: - if isinstance(sect,nodes.compound): + if isinstance(sect, nodes.compound): for sect2 in sect: - if isinstance(sect2,addnodes.start_of_file): + if isinstance(sect2, addnodes.start_of_file): for sect3 in sect2: - if isinstance(sect3,nodes.section): + if isinstance(sect3, nodes.section): sections.append(sect3) elif isinstance(sect, nodes.section): sections.append(sect) @@ -454,15 +524,16 @@ def build_contents(self, node, level=0): depth = self.toc_depth for section in sections: title = section[0] - auto = title.get('auto') # May be set by SectNum. + auto = title.get('auto') # May be set by SectNum. entrytext = self.copy_and_filter(title) - reference = nodes.reference('', '', refid=section['ids'][0], - *entrytext) + reference = nodes.reference('', '', refid=section['ids'][0], *entrytext) ref_id = self.document.set_id(reference) entry = nodes.paragraph('', '', reference) item = nodes.list_item('', entry) - if ( self.backlinks in ('entry', 'top') - and title.next_node(nodes.reference) is None): + if ( + self.backlinks in ('entry', 'top') + and title.next_node(nodes.reference) is None + ): if self.backlinks == 'entry': title['refid'] = ref_id elif self.backlinks == 'top': @@ -481,32 +552,34 @@ def build_contents(self, node, level=0): class PDFWriter(writers.Writer): - def __init__(self, - builder, - stylesheets, - language, - breaklevel = 0, - breakside = 'any', - fontpath = [], - fitmode = 'shrink', - compressed = False, - inline_footnotes = False, - splittables = True, - srcdir = '.', - default_dpi = 300, - page_template = 'cutePage', - invariant = False, - real_footnotes = False, - use_toc = True, - use_coverpage = True, - toc_depth = 9999, - use_numbered_links = False, - fit_background_mode = "scale", - section_header_depth = 2, - baseurl = urlunparse(['file',os.getcwd()+os.sep,'','','','']), - style_path = None, - repeat_table_rows = False, - config = {}): + def __init__( + self, + builder, + stylesheets, + language, + breaklevel=0, + breakside='any', + fontpath=[], + fitmode='shrink', + compressed=False, + inline_footnotes=False, + splittables=True, + srcdir='.', + default_dpi=300, + page_template='cutePage', + invariant=False, + real_footnotes=False, + use_toc=True, + use_coverpage=True, + toc_depth=9999, + use_numbered_links=False, + fit_background_mode="scale", + section_header_depth=2, + baseurl=urlunparse(['file', os.getcwd() + os.sep, '', '', '', '']), + style_path=None, + repeat_table_rows=False, + config={}, + ): writers.Writer.__init__(self) self.builder = builder self.output = '' @@ -524,14 +597,14 @@ def __init__(self, self.config = config self.default_dpi = default_dpi self.page_template = page_template - self.invariant=invariant - self.real_footnotes=real_footnotes - self.use_toc=use_toc - self.use_coverpage=use_coverpage - self.toc_depth=toc_depth - self.use_numbered_links=use_numbered_links - self.fit_background_mode=fit_background_mode - self.section_header_depth=section_header_depth + self.invariant = invariant + self.real_footnotes = real_footnotes + self.use_toc = use_toc + self.use_coverpage = use_coverpage + self.toc_depth = toc_depth + self.use_numbered_links = use_numbered_links + self.fit_background_mode = fit_background_mode + self.section_header_depth = section_header_depth self.repeat_table_rows = repeat_table_rows self.baseurl = baseurl if hasattr(sys, 'frozen'): @@ -543,8 +616,7 @@ def __init__(self, else: self.style_path = [self.srcdir] - - supported = ('pdf') + supported = 'pdf' config_section = 'pdf writer' config_section_dependencies = ('writers',) @@ -557,20 +629,26 @@ def translate(self): # Generate Contents topic manually if self.use_toc: - contents=nodes.topic(classes=['contents']) - contents+=nodes.title('') - contents[0]+=nodes.Text(langmod.labels['contents']) - contents['ids']=['Contents'] - pending=nodes.topic() + contents = nodes.topic(classes=['contents']) + contents += nodes.title('') + contents[0] += nodes.Text(langmod.labels['contents']) + contents['ids'] = ['Contents'] + pending = nodes.topic() contents.append(pending) - pending.details={} - self.document.insert(0,nodes.raw(text='SetPageCounter 1 arabic', format='pdf')) - self.document.insert(0,nodes.raw(text='OddPageBreak %s'%self.page_template, format='pdf')) - self.document.insert(0,contents) - self.document.insert(0,nodes.raw(text='SetPageCounter 1 lowerroman', format='pdf')) - contTrans=PDFContents(self.document) + pending.details = {} + self.document.insert( + 0, nodes.raw(text='SetPageCounter 1 arabic', format='pdf') + ) + self.document.insert( + 0, nodes.raw(text='OddPageBreak %s' % self.page_template, format='pdf'), + ) + self.document.insert(0, contents) + self.document.insert( + 0, nodes.raw(text='SetPageCounter 1 lowerroman', format='pdf') + ) + contTrans = PDFContents(self.document) contTrans.toc_depth = self.toc_depth - contTrans.startnode=pending + contTrans.startnode = pending contTrans.apply() if self.use_coverpage: @@ -582,69 +660,75 @@ def add_template_path(path): return os.path.join(self.srcdir, path) jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader([ - self.srcdir, os.path.expanduser('~/.rst2pdf'), - os.path.join(self.PATH,'templates')] + - list(map(add_template_path, self.config.templates_path))), - autoescape=jinja2.select_autoescape(['html', 'xml']) + loader=jinja2.FileSystemLoader( + [ + self.srcdir, + os.path.expanduser('~/.rst2pdf'), + os.path.join(self.PATH, 'templates'), + ] + + list(map(add_template_path, self.config.templates_path)) + ), + autoescape=jinja2.select_autoescape(['html', 'xml']), ) try: template = jinja_env.get_template(self.config.pdf_cover_template) except jinja2.TemplateNotFound: - log.error("Can't find cover template %s, using default"%self.config.pdf_cover_template) + log.error( + "Can't find cover template %s, using default" + % self.config.pdf_cover_template + ) template = jinja_env.get_template('sphinxcover.tmpl') # This is what's used in the python docs because # Latex does a manual linebreak. This sucks. - authors=self.document.settings.author.split('\\') + authors = self.document.settings.author.split('\\') # Honour the "today" config setting - if self.config.today : + if self.config.today: date = self.config.today else: - date=time.strftime(self.config.today_fmt or _('%B %d, %Y')) + date = time.strftime(self.config.today_fmt or _('%B %d, %Y')) # Feed data to the template, get restructured text. cover_text = template.render( - title=self.document.settings.title or visitor.elements['title'], - subtitle='%s %s'%(_('version'),self.config.version), - authors=authors, - date=date - ) + title=self.document.settings.title or visitor.elements['title'], + subtitle='%s %s' % (_('version'), self.config.version), + authors=authors, + date=date, + ) cover_tree = docutils.core.publish_doctree(cover_text) self.document.insert(0, cover_tree) - sio=BytesIO() + sio = BytesIO() if self.invariant: createpdf.patch_PDFDate() createpdf.patch_digester() - createpdf.RstToPdf(sphinx=True, - stylesheets=self.stylesheets, - language=self.__language, - breaklevel=self.breaklevel, - breakside=self.breakside, - fit_mode=self.fitmode, - font_path=self.fontpath, - inline_footnotes=self.inline_footnotes, - highlightlang=self.highlightlang, - splittables=self.splittables, - style_path=self.style_path, - repeat_table_rows=self.repeat_table_rows, - basedir=self.srcdir, - def_dpi=self.default_dpi, - real_footnotes=self.real_footnotes, - numbered_links=self.use_numbered_links, - background_fit_mode=self.fit_background_mode, - baseurl=self.baseurl, - section_header_depth=self.section_header_depth - ).createPdf(doctree=self.document, - output=sio, - compressed=self.compressed) - self.output=sio.getvalue() + createpdf.RstToPdf( + sphinx=True, + stylesheets=self.stylesheets, + language=self.__language, + breaklevel=self.breaklevel, + breakside=self.breakside, + fit_mode=self.fitmode, + font_path=self.fontpath, + inline_footnotes=self.inline_footnotes, + highlightlang=self.highlightlang, + splittables=self.splittables, + style_path=self.style_path, + repeat_table_rows=self.repeat_table_rows, + basedir=self.srcdir, + def_dpi=self.default_dpi, + real_footnotes=self.real_footnotes, + numbered_links=self.use_numbered_links, + background_fit_mode=self.fit_background_mode, + baseurl=self.baseurl, + section_header_depth=self.section_header_depth, + ).createPdf(doctree=self.document, output=sio, compressed=self.compressed) + self.output = sio.getvalue() def supports(self, format): """This writer supports all format-specific elements.""" @@ -659,9 +743,9 @@ def __init__(self, document, builder): self.curfilestack = [] self.highlightlinenothreshold = 999999 self.top_sectionlevel = 1 - self.footnotecounter=1 - self.curfile=None - self.footnotedict={} + self.footnotecounter = 1 + self.curfile = None + self.footnotedict = {} self.this_is_the_title = True self.in_title = 0 self.elements = { @@ -669,15 +753,15 @@ def __init__(self, document, builder): } self.highlightlang = builder.config.highlight_language - def visit_document(self,node): + def visit_document(self, node): self.curfilestack.append(node.get('docname', '')) self.footnotestack.append('') - def visit_start_of_file(self,node): + def visit_start_of_file(self, node): self.curfilestack.append(node['docname']) self.footnotestack.append(node['docname']) - def depart_start_of_file(self,node): + def depart_start_of_file(self, node): self.footnotestack.pop() self.curfilestack.pop() @@ -692,56 +776,60 @@ def visit_versionmodified(self, node): text += ': ' else: text += '.' - replacement=nodes.paragraph() - replacement+=nodes.Text(text) + replacement = nodes.paragraph() + replacement += nodes.Text(text) replacement.extend(node.children) - node.parent.replace(node,replacement) + node.parent.replace(node, replacement) def depart_versionmodified(self, node): pass def visit_literal_block(self, node): - if 'code' in node['classes']: #Probably a processed code-block + if 'code' in node['classes']: # Probably a processed code-block pass else: - lang=lang_for_block(node.astext(),node.get('language',self.highlightlang)) + lang = lang_for_block( + node.astext(), node.get('language', self.highlightlang) + ) content = node.astext().splitlines() - if len(content) > self.highlightlinenothreshold or\ - node.get('linenos',False): - options = { 'linenos': True } + if len(content) > self.highlightlinenothreshold or node.get( + 'linenos', False + ): + options = {'linenos': True} else: options = {} # FIXME: make tab width configurable - content = [c.replace('\t',' ') for c in content] + content = [c.replace('\t', ' ') for c in content] replacement = nodes.literal_block() - replacement.children = \ - pygments_code_block_directive.code_block_directive( - name = None, - arguments = [lang], - options = options, - content = content, - lineno = False, - content_offset = None, - block_text = None, - state = None, - state_machine = None, - ) - node.parent.replace(node,replacement) + replacement.children = pygments_code_block_directive.code_block_directive( + name=None, + arguments=[lang], + options=options, + content=content, + lineno=False, + content_offset=None, + block_text=None, + state=None, + state_machine=None, + ) + node.parent.replace(node, replacement) def visit_footnote(self, node): - node['backrefs']=[ '%s_%s'%(self.footnotestack[-1],x) for x in node['backrefs']] - node['ids']=[ '%s_%s'%(self.footnotestack[-1],x) for x in node['ids']] - node.children[0][0]=nodes.Text(str(self.footnotecounter)) + node['backrefs'] = [ + '%s_%s' % (self.footnotestack[-1], x) for x in node['backrefs'] + ] + node['ids'] = ['%s_%s' % (self.footnotestack[-1], x) for x in node['ids']] + node.children[0][0] = nodes.Text(str(self.footnotecounter)) for id in node['backrefs']: - fnr=self.footnotedict[id] - fnr.children[0]=nodes.Text(str(self.footnotecounter)) - self.footnotecounter+=1 + fnr = self.footnotedict[id] + fnr.children[0] = nodes.Text(str(self.footnotecounter)) + self.footnotecounter += 1 def visit_footnote_reference(self, node): - node['ids']=[ '%s_%s'%(self.footnotestack[-1],x) for x in node['ids']] - node['refid']='%s_%s'%(self.footnotestack[-1],node['refid']) - self.footnotedict[node['ids'][0]]=node + node['ids'] = ['%s_%s' % (self.footnotestack[-1], x) for x in node['ids']] + node['refid'] = '%s_%s' % (self.footnotestack[-1], node['refid']) + self.footnotedict[node['ids'][0]] = node def visit_desc_annotation(self, node): pass @@ -752,7 +840,7 @@ def depart_desc_annotation(self, node): # This is for graphviz support def visit_graphviz(self, node): # Not neat, but I need to send self to my handlers - node['builder']=self + node['builder'] = self def visit_Aanode(self, node): pass @@ -761,7 +849,7 @@ def depart_Aanode(self, node): pass def visit_productionlist(self, node): - replacement=nodes.literal_block(classes=["code"]) + replacement = nodes.literal_block(classes=["code"]) names = [] for production in node: names.append(production['tokenname']) @@ -769,27 +857,30 @@ def visit_productionlist(self, node): for production in node: if production['tokenname']: lastname = production['tokenname'].ljust(maxlen) - n=nodes.strong() - n+=nodes.Text(lastname) - replacement+=n - replacement+=nodes.Text(' ::= ') + n = nodes.strong() + n += nodes.Text(lastname) + replacement += n + replacement += nodes.Text(' ::= ') else: - replacement+=nodes.Text('%s ' % (' '*len(lastname))) + replacement += nodes.Text('%s ' % (' ' * len(lastname))) production.walkabout(self) replacement.children.extend(production.children) - replacement+=nodes.Text('\n') - node.parent.replace(node,replacement) + replacement += nodes.Text('\n') + node.parent.replace(node, replacement) raise nodes.SkipNode + def depart_productionlist(self, node): pass def visit_production(self, node): pass + def depart_production(self, node): pass def visit_OddEvenNode(self, node): pass + def depart_OddEvenNode(self, node): pass @@ -808,17 +899,20 @@ class HighlightLanguageTransform(SphinxTransform): After processing, this overridden transform DOES NOT REMOVE ``highlightlang`` node from doctree in order to allow pdfbuilder's visit_highlightlang to work as before. """ + default_priority = 400 def apply(self): from sphinx.transforms.post_transforms.code import HighlightLanguageVisitor - visitor = HighlightLanguageVisitor(self.document, - self.config.highlight_language) + + visitor = HighlightLanguageVisitor( + self.document, self.config.highlight_language + ) self.document.walkabout(visitor) # This is copied from sphinx.highlighting -def lang_for_block(source,lang): +def lang_for_block(source, lang): if lang in ('py', 'python'): if source.startswith('>>>'): # interactive session @@ -827,21 +921,22 @@ def lang_for_block(source,lang): # maybe Python -- try parsing it if try_parse(source): return 'python' - else: # Guess - return lang_for_block(source,'guess') + else: # Guess + return lang_for_block(source, 'guess') elif lang in ('python3', 'py3') and source.startswith('>>>'): # for py3, recognize interactive sessions, but do not try parsing... return 'pycon3' elif lang == 'guess': try: - #return 'python' - lexer=guess_lexer(source) + # return 'python' + lexer = guess_lexer(source) return lexer.aliases[0] except Exception: return None else: return lang + def try_parse(src): # Make sure it ends in a newline src += '\n' @@ -853,7 +948,7 @@ def try_parse(src): src = src.replace("...", mark) # lines beginning with "..." are probably placeholders for suite - src = re.sub(r"(?m)^(\s*)" + mark + "(.)", r"\1"+ mark + r"# \2", src) + src = re.sub(r"(?m)^(\s*)" + mark + "(.)", r"\1" + mark + r"# \2", src) # if we're using 2.5, use the with statement if sys.version_info >= (2, 5): @@ -900,23 +995,29 @@ def setup(app): app.add_config_value('pdf_repeat_table_rows', False, None) app.add_config_value('pdf_breakside', 'odd', None) app.add_config_value('pdf_default_dpi', 300, None) - app.add_config_value('pdf_extensions',['vectorpdf'], None) - app.add_config_value('pdf_page_template','cutePage', None) - app.add_config_value('pdf_invariant',False, None) - app.add_config_value('pdf_real_footnotes',False, None) - app.add_config_value('pdf_use_toc',True, None) - app.add_config_value('pdf_toc_depth',9999, None) - app.add_config_value('pdf_use_numbered_links',False, None) - app.add_config_value('pdf_fit_background_mode',"scale", None) - app.add_config_value('section_header_depth',2, None) - app.add_config_value('pdf_baseurl', urlunparse(['file',os.getcwd()+os.sep,'','','','']), None) + app.add_config_value('pdf_extensions', ['vectorpdf'], None) + app.add_config_value('pdf_page_template', 'cutePage', None) + app.add_config_value('pdf_invariant', False, None) + app.add_config_value('pdf_real_footnotes', False, None) + app.add_config_value('pdf_use_toc', True, None) + app.add_config_value('pdf_toc_depth', 9999, None) + app.add_config_value('pdf_use_numbered_links', False, None) + app.add_config_value('pdf_fit_background_mode', "scale", None) + app.add_config_value('section_header_depth', 2, None) + app.add_config_value( + 'pdf_baseurl', urlunparse(['file', os.getcwd() + os.sep, '', '', '', '']), None, + ) project_doc = app.config.project + ' Documentation' - app.config.pdf_documents.append((app.config.master_doc, - app.config.project, - project_doc, - app.config.copyright, - 'manual')) + app.config.pdf_documents.append( + ( + app.config.master_doc, + app.config.project, + project_doc, + app.config.copyright, + 'manual', + ) + ) return { 'version': rst2pdf.version, diff --git a/rst2pdf/pygments2style.py b/rst2pdf/pygments2style.py index dccc26239..74bccf726 100644 --- a/rst2pdf/pygments2style.py +++ b/rst2pdf/pygments2style.py @@ -12,20 +12,21 @@ from pygments import styles as pstyles # First get a list of all possible classes -classnames=set() +classnames = set() for name in list(pstyles.get_all_styles()): - css=os.popen('pygmentize -S %s -f html'%name, 'r').read() + css = os.popen('pygmentize -S %s -f html' % name, 'r').read() for line in css.splitlines(): line = line.strip() sname = "pygments-" + line.split(' ')[0][1:] classnames.add(sname) + def css2rl(css): dstyles = {} # First create a dumb stylesheet for key in STANDARD_TYPES: dstyles["pygments-" + STANDARD_TYPES[key]] = {'parent': 'code'} - seenclassnames=set() + seenclassnames = set() styles = [] for line in css.splitlines(): line = line.strip() @@ -36,8 +37,8 @@ def css2rl(css): for option in options: option = option.strip() option, argument = option.split(':') - option=option.strip() - argument=argument.strip() + option = option.strip() + argument = argument.strip() if option == 'color': style['textColor'] = argument.strip() if option == 'background-color': @@ -45,8 +46,7 @@ def css2rl(css): # These two can come in any order if option == 'font-weight' and argument == 'bold': - if 'fontName' in style and \ - style['fontName'] == 'stdMonoItalic': + if 'fontName' in style and style['fontName'] == 'stdMonoItalic': style['fontName'] = 'stdMonoBoldItalic' else: style['fontName'] = 'stdMonoBold' @@ -56,19 +56,18 @@ def css2rl(css): else: style['fontName'] = 'stdMonoItalic' if style.get('textColor', None) is None: - style['textColor']='black' + style['textColor'] = 'black' styles.append([sname, style]) # Now add default styles for all unseen class names - for sname in classnames-seenclassnames: + for sname in classnames - seenclassnames: style = dstyles.get(sname, {'parent': 'code'}) - style['textColor']='black' + style['textColor'] = 'black' styles.append([sname, style]) return dumpstyle.dumps({'styles': styles}) - for name in list(pstyles.get_all_styles()): - css=os.popen('pygmentize -S %s -f html'%name, 'r').read() - open(name+'.style', 'w').write(css2rl(css)) + css = os.popen('pygmentize -S %s -f html' % name, 'r').read() + open(name + '.style', 'w').write(css2rl(css)) diff --git a/rst2pdf/pygments_code_block_directive.py b/rst2pdf/pygments_code_block_directive.py index 3c18ca7c2..421b8b81a 100755 --- a/rst2pdf/pygments_code_block_directive.py +++ b/rst2pdf/pygments_code_block_directive.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -#$URL$ -#$Date$ -#$Revision$ +# $URL$ +# $Date$ +# $Revision$ # :Author: a Pygments author|contributor; Felix Wiemann; Guenter Milde # :Date: $Date$ @@ -69,6 +69,7 @@ # It does not require anything of docutils and could also become a part of # pygments:: + class DocutilsInterface(object): """Parse `code` string and yield "classified" tokens. @@ -94,14 +95,11 @@ def lex(self): # Get lexer for language (use text as fallback) try: if self.language and unicode(self.language).lower() != 'none': - lexer = get_lexer_by_name(self.language.lower(), - **self.custom_args - ) + lexer = get_lexer_by_name(self.language.lower(), **self.custom_args) else: lexer = get_lexer_by_name('text', **self.custom_args) except ValueError: - log.info("no pygments lexer for %s, using 'text'" \ - % self.language) + log.info("no pygments lexer for %s, using 'text'" % self.language) # what happens if pygment isn't present ? lexer = get_lexer_by_name('text') return pygments.lex(self.code, lexer) @@ -115,9 +113,9 @@ def join(self, tokens): if ttype is lasttype: lastval += value else: - yield(lasttype, lastval) + yield (lasttype, lastval) (lasttype, lastval) = (ttype, value) - yield(lasttype, lastval) + yield (lasttype, lastval) def __iter__(self): """parse code string and yield "clasified" tokens @@ -138,8 +136,18 @@ def __iter__(self): # -------------------- # :: -def code_block_directive(name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): + +def code_block_directive( + name, + arguments, + options, + content, + lineno, + content_offset, + block_text, + state, + state_machine, +): """Parse and classify content of a code_block.""" if 'include' in options: try: @@ -148,7 +156,10 @@ def code_block_directive(name, arguments, options, content, lineno, else: encoding = 'utf-8' content = codecs.open(options['include'], 'r', encoding).read().rstrip() - except (IOError, UnicodeError): # no file or problem finding it or reading it + except ( + IOError, + UnicodeError, + ): # no file or problem finding it or reading it log.error('Error reading file: "%s" L %s' % (options['include'], lineno)) content = u'' line_offset = 0 @@ -167,8 +178,10 @@ def code_block_directive(name, arguments, options, content, lineno, # skip content in include_text before *and NOT incl.* a matching text after_index = content.find(after_text) if after_index < 0: - raise state_machine.reporter.severe('Problem with "start-at" option of "%s" ' - 'code-block directive:\nText not found.' % options['start-at']) + raise state_machine.reporter.severe( + 'Problem with "start-at" option of "%s" ' + 'code-block directive:\nText not found.' % options['start-at'] + ) # patch mmueller start # Move the after_index to the beginning of the line with the # match. @@ -191,8 +204,11 @@ def code_block_directive(name, arguments, options, content, lineno, # skip content in include_text before *and incl.* a matching text after_index = content.find(after_text) if after_index < 0: - raise state_machine.reporter.severe('Problem with "start-after" option of "%s" ' - 'code-block directive:\nText not found.' % options['start-after']) + raise state_machine.reporter.severe( + 'Problem with "start-after" option of "%s" ' + 'code-block directive:\nText not found.' + % options['start-after'] + ) after_index = after_index + len(after_text) # Move the after_index to the start of the line after the match @@ -204,24 +220,27 @@ def code_block_directive(name, arguments, options, content, lineno, line_offset = len(content[:after_index].splitlines()) content = content[after_index:] - # same changes here for the same reason before_text = options.get('end-at', None) if before_text: # skip content in include_text after *and incl.* a matching text before_index = content.find(before_text) if before_index < 0: - raise state_machine.reporter.severe('Problem with "end-at" option of "%s" ' - 'code-block directive:\nText not found.' % options['end-at']) - content = content[:before_index + len(before_text)] + raise state_machine.reporter.severe( + 'Problem with "end-at" option of "%s" ' + 'code-block directive:\nText not found.' % options['end-at'] + ) + content = content[: before_index + len(before_text)] before_text = options.get('end-before', None) if before_text: # skip content in include_text after *and NOT incl.* a matching text before_index = content.find(before_text) if before_index < 0: - raise state_machine.reporter.severe('Problem with "end-before" option of "%s" ' - 'code-block directive:\nText not found.' % options['end-before']) + raise state_machine.reporter.severe( + 'Problem with "end-before" option of "%s" ' + 'code-block directive:\nText not found.' % options['end-before'] + ) content = content[:before_index] else: @@ -233,7 +252,7 @@ def code_block_directive(name, arguments, options, content, lineno, else: tabw = int(options.get('tab-width', 8)) - content = content.replace('\t',' '*tabw) + content = content.replace('\t', ' ' * tabw) hl_lines = options.get('hl_lines', []) withln = "linenos" in options @@ -256,7 +275,9 @@ def code_block_directive(name, arguments, options, content, lineno, linenumber_cls = 'linenumber' if hl_lines and lineno not in hl_lines: linenumber_cls = 'pygments-diml' - code_block += nodes.inline(fstr[1:] % lineno, fstr[1:] % lineno, classes=[linenumber_cls]) + code_block += nodes.inline( + fstr[1:] % lineno, fstr[1:] % lineno, classes=[linenumber_cls] + ) # parse content with pygments and add to code_block element for cls, value in DocutilsInterface(content, language, options): @@ -264,7 +285,9 @@ def code_block_directive(name, arguments, options, content, lineno, cls = "diml" if withln and "\n" in value: linenumber_cls = 'linenumber' - if hl_lines and (lineno+1) not in hl_lines: # use lineno+1 as we're on the previous line when we render the next line number + if ( + hl_lines and (lineno + 1) not in hl_lines + ): # use lineno+1 as we're on the previous line when we render the next line number linenumber_cls = 'pygments-diml' # Split on the "\n"s values = value.split("\n") @@ -274,7 +297,9 @@ def code_block_directive(name, arguments, options, content, lineno, linenos = range(lineno, lineno + len(values)) for chunk, ln in list(zip(values, linenos))[1:]: if ln <= total_lines: - code_block += nodes.inline(fstr % ln, fstr % ln, classes=[linenumber_cls]) + code_block += nodes.inline( + fstr % ln, fstr % ln, classes=[linenumber_cls] + ) code_block += nodes.Text(chunk, chunk) lineno += len(values) - 1 @@ -290,12 +315,14 @@ def code_block_directive(name, arguments, options, content, lineno, return [code_block] + # Custom argument validators # -------------------------- # :: # # Move to separated module?? + def zero_or_positive_int(argument): """ Converts a string into python positive integer including zero. @@ -322,6 +349,7 @@ def string_list(argument): entries = argument.split() return entries + def string_bool(argument): """ Converts True, true, False, False in python boolean values @@ -335,71 +363,71 @@ def string_bool(argument): elif argument.lower() == 'false': return False else: - raise ValueError('"%s" unknown; choose from "True" or "False"' - % argument) + raise ValueError('"%s" unknown; choose from "True" or "False"' % argument) + def csharp_unicodelevel(argument): return directives.choice(argument, ('none', 'basic', 'full')) + def lhs_litstyle(argument): return directives.choice(argument, ('bird', 'latex')) + def raw_compress(argument): return directives.choice(argument, ('gz', 'bz2')) - - # Register Directive # ------------------ # :: code_block_directive.arguments = (0, 1, 1) code_block_directive.content = 1 -code_block_directive.options = {'include': directives.unchanged_required, - 'start-at': directives.unchanged_required, - 'end-at': directives.unchanged_required, - 'start-after': directives.unchanged_required, - 'end-before': directives.unchanged_required, - 'linenos': directives.unchanged, - 'linenos_offset': zero_or_positive_int, - 'tab-width': directives.unchanged, - 'hl_lines': directives.positive_int_list, - # generic - 'stripnl' : string_bool, - 'stripall': string_bool, - 'ensurenl': string_bool, - 'tabsize' : directives.positive_int, - 'encoding': directives.encoding, - # Lua - 'func_name_hightlighting':string_bool, - 'disabled_modules': string_list, - # Python Console - 'python3': string_bool, - # Delphi - 'turbopascal':string_bool, - 'delphi' :string_bool, - 'freepascal': string_bool, - 'units': string_list, - # Modula2 - 'pim' : string_bool, - 'iso' : string_bool, - 'objm2' : string_bool, - 'gm2ext': string_bool, - # CSharp - 'unicodelevel' : csharp_unicodelevel, - # Literate haskell - 'litstyle' : lhs_litstyle, - # Raw - 'compress': raw_compress, - # Rst - 'handlecodeblocks': string_bool, - # Php - 'startinline': string_bool, - 'funcnamehighlighting': string_bool, - 'disabledmodules': string_list, - } - +code_block_directive.options = { + 'include': directives.unchanged_required, + 'start-at': directives.unchanged_required, + 'end-at': directives.unchanged_required, + 'start-after': directives.unchanged_required, + 'end-before': directives.unchanged_required, + 'linenos': directives.unchanged, + 'linenos_offset': zero_or_positive_int, + 'tab-width': directives.unchanged, + 'hl_lines': directives.positive_int_list, + # generic + 'stripnl': string_bool, + 'stripall': string_bool, + 'ensurenl': string_bool, + 'tabsize': directives.positive_int, + 'encoding': directives.encoding, + # Lua + 'func_name_hightlighting': string_bool, + 'disabled_modules': string_list, + # Python Console + 'python3': string_bool, + # Delphi + 'turbopascal': string_bool, + 'delphi': string_bool, + 'freepascal': string_bool, + 'units': string_list, + # Modula2 + 'pim': string_bool, + 'iso': string_bool, + 'objm2': string_bool, + 'gm2ext': string_bool, + # CSharp + 'unicodelevel': csharp_unicodelevel, + # Literate haskell + 'litstyle': lhs_litstyle, + # Raw + 'compress': raw_compress, + # Rst + 'handlecodeblocks': string_bool, + # Php + 'startinline': string_bool, + 'funcnamehighlighting': string_bool, + 'disabledmodules': string_list, +} # .. _doctutils: http://docutils.sf.net/ @@ -417,10 +445,12 @@ def raw_compress(argument): if __name__ == '__main__': from docutils.core import publish_cmdline, default_description from docutils.parsers.rst import directives + directives.register_directive('code-block', code_block_directive) description = "code-block directive test output" + default_description try: import locale + locale.setlocale(locale.LC_ALL, '') except Exception: pass diff --git a/rst2pdf/rson.py b/rst2pdf/rson.py index 5706fa77f..3b29277fa 100644 --- a/rst2pdf/rson.py +++ b/rst2pdf/rson.py @@ -1,4 +1,3 @@ - ################################################################################ #### NOTE: THIS IS STILL IN DEVELOPMENT: #### #### #### @@ -129,13 +128,9 @@ class Tokenizer(list): other = r'[\S](?:[^%s\n]*[^%s\s])*' % (re_delimiterset, re_delimiterset) - pattern = '(%s)' % '|'.join([ - delimiter_pattern, - triple_quoted_string, - quoted_string, - other, - indentation, - ]) + pattern = '(%s)' % '|'.join( + [delimiter_pattern, triple_quoted_string, quoted_string, other, indentation,] + ) splitter = re.compile(pattern).split @@ -199,13 +194,21 @@ def newstring(source, client): continue else: t0 = 'X' - self[index] = (offset, t0, token, whitespace, indentation, linenum, self) + self[index] = ( + offset, + t0, + token, + whitespace, + indentation, + linenum, + self, + ) index += 1 offset -= len(token) + len(whitespace) # Add a sentinel self[index] = (offset, '@', '@', '', '', linenum + 1, self) - self[index+1:] = [] + self[index + 1 :] = [] # Put everything we need in the actual object instantiation self.reverse() @@ -213,6 +216,7 @@ def newstring(source, client): self.next = self.pop self.push = self.append return self + return newstring def peek(self): @@ -241,7 +245,7 @@ def error(cls, s, token): loc = 'at end of string' else: text = token[2] - loc = 'line %s, column %s, text %s' % (lineno, colno, repr(text[:20])) + loc = 'line %s, column %s, text %s' % (lineno, colno, repr(text[:20]),) err = RSONDecodeError('%s: %s' % (s, loc)) err.pos = offset @@ -259,6 +263,7 @@ def make_hashable(what): return tuple(sorted(make_hashable(x) for x in what.items())) return tuple(make_hashable(x) for x in what) + class BaseObjects(object): # These hooks allow compatibility with simplejson @@ -322,16 +327,20 @@ def object_type_factory(self, dict=dict, tuple=tuple): object_pairs_hook = self.object_pairs_hook if object_pairs_hook is not None: + class build_object(list): def get_result(self, token): return object_pairs_hook([tuple(x) for x in self]) + self.disallow_multiple_object_keys = True self.disallow_nonstring_keys = True elif object_hook is not None: mydict = dict + class build_object(list): def get_result(self, token): return object_hook(mydict(self)) + self.disallow_multiple_object_keys = True self.disallow_nonstring_keys = True else: @@ -365,7 +374,7 @@ def loads(s, **kw): # Begin some real ugliness here -- just modify our instance to # have the correct user variables for the initialization functions. # Seems to speed up simplejson testcases a bit. - self.__dict__ = dict((x,y) for (x,y) in key if y is not None) + self.__dict__ = dict((x, y) for (x, y) in key if y is not None) func = parsercache[key] = parser_factory() return func(s) @@ -389,23 +398,27 @@ class QuotedToken(object): quoted_splitter = re.compile(r'(\\u[0-9a-fA-F]{4}|\\.|")').split if six.PY3: - quoted_mapper = {'\\\\': '\\', - r'\"': '"', - r'\/': '/', - r'\b': '\b', - r'\f': '\f', - r'\n': '\n', - r'\r': '\r', - r'\t': '\t'}.get + quoted_mapper = { + '\\\\': '\\', + r'\"': '"', + r'\/': '/', + r'\b': '\b', + r'\f': '\f', + r'\n': '\n', + r'\r': '\r', + r'\t': '\t', + }.get else: - quoted_mapper = {'\\\\': u'\\', - r'\"': u'"', - r'\/': u'/', - r'\b': u'\b', - r'\f': u'\f', - r'\n': u'\n', - r'\r': u'\r', - r'\t': u'\t'}.get + quoted_mapper = { + '\\\\': u'\\', + r'\"': u'"', + r'\/': u'/', + r'\b': u'\b', + r'\f': u'\f', + r'\n': u'\n', + r'\r': u'\r', + r'\t': u'\t', + }.get def quoted_parse_factory(self, int=int, iter=iter, len=len): quoted_splitter = self.quoted_splitter @@ -420,7 +433,9 @@ def quoted_parse_factory(self, int=int, iter=iter, len=len): def badstring(token, special): if token[2] != '"""' or triplequoted is None: - token[-1].error('Invalid character in quoted string: %s' % repr(special), token) + token[-1].error( + 'Invalid character in quoted string: %s' % repr(special), token, + ) result = parse_quoted_str(token, triplequoted(token)) if cachestrings: result = token[-1].stringcache(result, result) @@ -449,8 +464,10 @@ def parse(token, next_): if remap is None: if len(special) == 6: uni = int(special[2:], 16) - if 0xd800 <= uni <= 0xdbff and allow_double: - uni, nonmatch = parse_double_unicode(uni, nonmatch, next_, token) + if 0xD800 <= uni <= 0xDBFF and allow_double: + uni, nonmatch = parse_double_unicode( + uni, nonmatch, next_, token + ) remap = parse_encoded_chr(uni) else: return badstring(token, special) @@ -475,9 +492,11 @@ def parse_double_unicode(uni, nonmatch, next_, token): ok = ok and not nonmatch and uni2.startswith(b'\\u') and len(uni2) == 6 if ok: nonmatch = uni2 - uni = 0x10000 + (((uni - 0xd800) << 10) | (int(uni2[2:], 16) - 0xdc00)) + uni = 0x10000 + (((uni - 0xD800) << 10) | (int(uni2[2:], 16) - 0xDC00)) return uni, nonmatch2 - token[-1].error('Invalid second ch in double sequence: %s' % repr(nonmatch), token) + token[-1].error( + 'Invalid second ch in double sequence: %s' % repr(nonmatch), token, + ) return parse @@ -491,13 +510,13 @@ def triplequoted(token): end = source.find('"""', start) if end < 0: tokens.error('Did not find end for triple-quoted string', token) - if source[end-1] != '\\': + if source[end - 1] != '\\': break - result.append(source[start:end-1]) + result.append(source[start : end - 1]) result.append('"""') start = end + 3 result.append(source[start:end]) - offset = bisect.bisect(tokens, (- end -2, )) + offset = bisect.bisect(tokens, (-end - 2,)) tokens[offset:] = [] return ''.join(result) @@ -521,15 +540,12 @@ class UnquotedToken(object): ''' use_decimal = False - parse_int = staticmethod( - lambda s: int(s.replace('_', ''), 0)) + parse_int = staticmethod(lambda s: int(s.replace('_', ''), 0)) parse_float = float if six.PY2: - parse_unquoted_str = staticmethod( - lambda token: token[2].decode('utf-8')) + parse_unquoted_str = staticmethod(lambda token: token[2].decode('utf-8')) else: - parse_unquoted_str = staticmethod( - lambda token, unicode=str: str(token[2])) + parse_unquoted_str = staticmethod(lambda token, unicode=str: str(token[2])) special_strings = dict(true=True, false=False, null=None) @@ -557,8 +573,7 @@ class UnquotedToken(object): ''' def unquoted_parse_factory(self): - unquoted_match = re.compile(self.unquoted_pattern, - re.VERBOSE).match + unquoted_match = re.compile(self.unquoted_pattern, re.VERBOSE).match parse_unquoted_str = self.parse_unquoted_str parse_float = self.parse_float @@ -567,6 +582,7 @@ def unquoted_parse_factory(self): if self.use_decimal: from decimal import Decimal + parse_float = Decimal def parse(token, next_): @@ -643,14 +659,14 @@ def parse(firsttok, next_): token = next_() while token[5] == linenum: token = next_() - while token[4] > indent: + while token[4] > indent: token = next_() tokens.push(token) # Get rid of \n, and indent one past = indent = indent[1:] + ' ' - bigstring = tokens.source[-firsttok[0] + 1: -token[0]] + bigstring = tokens.source[-firsttok[0] + 1 : -token[0]] if six.PY3: bigstring = bigstring.decode('utf-8') stringlist = bigstring.split('\n') @@ -677,7 +693,14 @@ def post_parse(tokens, value): def client_info(self, parse_locals): pass - def parser_factory(self, len=len, type=type, isinstance=isinstance, list=list, basestring=basestring): + def parser_factory( + self, + len=len, + type=type, + isinstance=isinstance, + list=list, + basestring=basestring, + ): Tokenizer = self.Tokenizer tokenizer = Tokenizer.factory() @@ -689,11 +712,13 @@ def parser_factory(self, len=len, type=type, isinstance=isinstance, list=list, b new_object, new_array = self.object_type_factory() disallow_trailing_commas = self.disallow_trailing_commas disallow_missing_object_keys = self.disallow_missing_object_keys - key_handling = [disallow_missing_object_keys, self.disallow_multiple_object_keys] + key_handling = [ + disallow_missing_object_keys, + self.disallow_multiple_object_keys, + ] disallow_nonstring_keys = self.disallow_nonstring_keys post_parse = self.post_parse - def bad_array_element(token, next): error('Expected array element', token) @@ -722,7 +747,7 @@ def read_json_array(firsttok, next): if result and disallow_trailing_commas: error('Unexpected trailing comma', token) break - append(json_value_dispatch(t0, bad_array_element)(token, next)) + append(json_value_dispatch(t0, bad_array_element)(token, next)) delim = next() t0 = delim[1] if t0 == ',': @@ -740,7 +765,7 @@ def read_json_dict(firsttok, next): while 1: token = next() t0 = token[1] - if t0 == '}': + if t0 == '}': if result and disallow_trailing_commas: error('Unexpected trailing comma', token) break @@ -785,13 +810,20 @@ def read_rson_unquoted(firsttok, next): result[2] = ''.join(s) return read_unquoted(result, next) - json_value_dispatch = {'X':read_unquoted, '[':read_json_array, - '{': read_json_dict, '"':read_quoted}.get - - - rson_value_dispatch = {'X':read_rson_unquoted, '[':read_json_array, - '{': read_json_dict, '"':read_quoted, - '=': parse_equals} + json_value_dispatch = { + 'X': read_unquoted, + '[': read_json_array, + '{': read_json_dict, + '"': read_quoted, + }.get + + rson_value_dispatch = { + 'X': read_rson_unquoted, + '[': read_json_array, + '{': read_json_dict, + '"': read_quoted, + '=': parse_equals, + } if self.disallow_rson_sublists: rson_value_dispatch['['] = read_rson_unquoted @@ -823,9 +855,13 @@ def parse_recurse_array(stack, next, token, result): stack.append(token) lastitem = result[-1] if lastitem == empty_array: - result[-1], token = parse_recurse_array(stack, next, token, lastitem) + result[-1], token = parse_recurse_array( + stack, next, token, lastitem + ) elif lastitem == empty_object: - result[-1], token = parse_recurse_dict(stack, next, token, lastitem) + result[-1], token = parse_recurse_dict( + stack, next, token, lastitem + ) else: result = None if result: @@ -837,8 +873,14 @@ def parse_recurse_array(stack, next, token, result): bad_indent(token, next) if newlinenum <= linenum: if token[1] in '=:': - error('Cannot mix list elements with dict (key/value) elements', token) - error('Array elements must either be on separate lines or enclosed in []', token) + error( + 'Cannot mix list elements with dict (key/value) elements', + token, + ) + error( + 'Array elements must either be on separate lines or enclosed in []', + token, + ) linenum = newlinenum value = rson_value_dispatch(token[1], bad_top_value)(token, next) result.append(value) @@ -873,21 +915,29 @@ def parse_one_dict_entry(stack, next, token, entry, mydict): if type(entry[-1]) is type(value): entry[-1] = value else: - error('Cannot load %s into %s' % (type(value), type(entry[-1])), stack[-1]) + error( + 'Cannot load %s into %s' % (type(value), type(entry[-1])), + stack[-1], + ) elif len(value) == 1 and type(value) is empty_array_type: entry.extend(value) else: entry.append(value) stack.pop() length = len(entry) - if length != 2 and key_handling[length > 2]: + if length != 2 and key_handling[length > 2]: if length < 2: error('Expected ":" or "=", or indented line', token) - error("rson client's object handlers do not support chained objects", token) + error( + "rson client's object handlers do not support chained objects", + token, + ) if disallow_nonstring_keys: for key in entry[:-1]: if not isinstance(key, basestring): - error('Non-string key %s not supported' % repr(key), token) + error( + 'Non-string key %s not supported' % repr(key), token, + ) mydict.append(entry) return token @@ -916,9 +966,11 @@ def parse_recurse(stack, next, tokens=None): # is at the same indentation, or the current value is an empty list token = next() - if (token[5] != firsttok[5] and - (token[4] <= firsttok[4] or - value in empties) and disallow_missing_object_keys): + if ( + token[5] != firsttok[5] + and (token[4] <= firsttok[4] or value in empties) + and disallow_missing_object_keys + ): result = new_array([value], firsttok) if tokens is not None: tokens.top_object = result @@ -931,7 +983,6 @@ def parse_recurse(stack, next, tokens=None): token = parse_one_dict_entry(stack, next, token, [value], result) return parse_recurse_dict(stack, next, token, result) - def parse(source): tokens = tokenizer(source, None) tokens.stringcache = {}.setdefault @@ -943,8 +994,11 @@ def parse(source): # If it's a single item and we don't have a specialized # object builder, just strip the outer list. - if (len(value) == 1 and isinstance(value, list) - and disallow_missing_object_keys): + if ( + len(value) == 1 + and isinstance(value, list) + and disallow_missing_object_keys + ): value = value[0] return post_parse(tokens, value) @@ -953,7 +1007,10 @@ def parse(source): return parse -class RsonSystem(RsonParser, UnquotedToken, QuotedToken, EqualToken, Dispatcher, BaseObjects): +class RsonSystem( + RsonParser, UnquotedToken, QuotedToken, EqualToken, Dispatcher, BaseObjects +): Tokenizer = Tokenizer + loads = RsonSystem.dispatcher_factory() diff --git a/rst2pdf/sectnumlinks.py b/rst2pdf/sectnumlinks.py index fff7656d4..32bcd12da 100644 --- a/rst2pdf/sectnumlinks.py +++ b/rst2pdf/sectnumlinks.py @@ -10,10 +10,10 @@ def visit_generated(self, node): for i in node.parent.parent['ids']: self.sectnums[i] = node.parent.astext().replace(u'\xa0\xa0\xa0', ' ') - def unknown_visit(self, node): pass + class SectRefExpander(docutils.nodes.SparseNodeVisitor): def __init__(self, document, sectnums): docutils.nodes.SparseNodeVisitor.__init__(self, document) @@ -21,7 +21,9 @@ def __init__(self, document, sectnums): def visit_reference(self, node): if node.get('refid', None) in self.sectnums: - node.children = [docutils.nodes.Text('%s ' % self.sectnums[node.get('refid')])] + node.children = [ + docutils.nodes.Text('%s ' % self.sectnums[node.get('refid')]) + ] def unknown_visit(self, node): pass diff --git a/rst2pdf/sinker.py b/rst2pdf/sinker.py index aaf510c72..e5834a099 100644 --- a/rst2pdf/sinker.py +++ b/rst2pdf/sinker.py @@ -11,11 +11,11 @@ class Sinker(Flowable): def __init__(self, content): self.content = content - def wrap (self, aW, aH): + def wrap(self, aW, aH): self.width, self.height = _listWrapOn(self.content, aW, None) return self.width, aH - def draw (self): + def draw(self): canv = self.canv canv.saveState() x = canv._x @@ -23,7 +23,7 @@ def draw (self): y += self.height aW = self.width for c in self.content: - w, h = c.wrapOn(canv, aW, 0xfffffff) + w, h = c.wrapOn(canv, aW, 0xFFFFFFF) if (w < _FUZZ or h < _FUZZ) and not getattr(c, '_ZEROSIZE', None): continue y -= h diff --git a/rst2pdf/sphinxnodes.py b/rst2pdf/sphinxnodes.py index 1ae8412f9..fa0d1da38 100644 --- a/rst2pdf/sphinxnodes.py +++ b/rst2pdf/sphinxnodes.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -#$URL$ -#$Date$ -#$Revision$ +# $URL$ +# $Date$ +# $Revision$ # See LICENSE.txt for licensing terms @@ -21,7 +21,13 @@ from copy import copy from rst2pdf.log import nodeid, log -from rst2pdf.flowables import MySpacer, MyIndenter, Reference, DelayedTable, Table +from rst2pdf.flowables import ( + MySpacer, + MyIndenter, + Reference, + DelayedTable, + Table, +) from rst2pdf.image import MyImage, VectorPdf from rst2pdf.opt_imports import Paragraph, sphinx @@ -33,6 +39,7 @@ ################## NodeHandler subclasses ################### + class SphinxHandler(NodeHandler): sphinxmode = True dispatchdict = {} @@ -55,38 +62,50 @@ def __init__(self): class SphinxFont(SphinxHandler, FontHandler): pass -class HandleSphinxDefaults(SphinxHandler, sphinx.addnodes.glossary, - sphinx.addnodes.start_of_file, - sphinx.addnodes.compact_paragraph, - sphinx.addnodes.pending_xref): + +class HandleSphinxDefaults( + SphinxHandler, + sphinx.addnodes.glossary, + sphinx.addnodes.start_of_file, + sphinx.addnodes.compact_paragraph, + sphinx.addnodes.pending_xref, +): pass + class SphinxListHandler(SphinxHandler): def get_text(self, client, node, replaceEnt): t = client.gather_pdftext(node) while t and t[0] in ', ': - t=t[1:] + t = t[1:] return t -class HandleSphinxDescAddname(SphinxFont, sphinx.addnodes.desc_addname): + +class HandleSphinxDescAddname(SphinxFont, sphinx.addnodes.desc_addname): fontstyle = "descclassname" + class HandleSphinxDescName(SphinxFont, sphinx.addnodes.desc_name): fontstyle = "descname" + class HandleSphinxDescReturn(SphinxFont, sphinx.addnodes.desc_returns): def get_font_prefix(self, client, node, replaceEnt): return ' → ' + client.styleToFont("returns") + class HandleSphinxDescType(SphinxFont, sphinx.addnodes.desc_type): fontstyle = "desctype" + class HandleSphinxDescParamList(SphinxListHandler, sphinx.addnodes.desc_parameterlist): - pre=' (' - post=')' + pre = ' (' + post = ')' + class HandleSphinxDescParam(SphinxFont, sphinx.addnodes.desc_parameter): fontstyle = "descparameter" + def get_pre_post(self, client, node, replaceEnt): pre, post = FontHandler.get_pre_post(self, client, node, replaceEnt) if node.hasattr('noemph'): @@ -96,15 +115,21 @@ def get_pre_post(self, client, node, replaceEnt): post += '' return pre, post + class HandleSphinxDescOpt(SphinxListHandler, SphinxFont, sphinx.addnodes.desc_optional): fontstyle = "optional" + def get_pre_post(self, client, node, replaceEnt): prepost = FontHandler.get_pre_post(self, client, node, replaceEnt) return '%s[%s, ' % prepost, '%s]%s' % prepost -class HandleDescAnnotation(SphinxHandler, HandleEmphasis, sphinx.addnodes.desc_annotation): + +class HandleDescAnnotation( + SphinxHandler, HandleEmphasis, sphinx.addnodes.desc_annotation +): pass + class HandleSphinxIndex(SphinxHandler, sphinx.addnodes.index): def gather_elements(self, client, node, style): try: @@ -112,49 +137,59 @@ def gather_elements(self, client, node, style): client.pending_targets.append(docutils.nodes.make_id(entry[2])) except IndexError: if node['entries']: - log.error("Can't process index entry: %s [%s]", - node['entries'], nodeid(node)) + log.error( + "Can't process index entry: %s [%s]", node['entries'], nodeid(node), + ) return [] + if sphinx.__version__ < '1.0': + class HandleSphinxModule(SphinxHandler, sphinx.addnodes.module): def gather_elements(self, client, node, style): - return [Reference('module-'+node['modname'])] + return [Reference('module-' + node['modname'])] + # custom SPHINX nodes. # FIXME: make sure they are all here, and keep them all together + class HandleSphinxCentered(SphinxHandler, sphinx.addnodes.centered): def gather_elements(self, client, node, style): - return [Paragraph(client.gather_pdftext(node), - client.styles['centered'])] + return [Paragraph(client.gather_pdftext(node), client.styles['centered'])] + class HandleSphinxDesc(SphinxHandler, sphinx.addnodes.desc): def gather_elements(self, client, node, style): - st=client.styles[node['desctype']] - if st==client.styles['normal']: - st=copy(client.styles['desc']) - st.spaceBefore=0 - pre=[MySpacer(0,client.styles['desc'].spaceBefore)] + st = client.styles[node['desctype']] + if st == client.styles['normal']: + st = copy(client.styles['desc']) + st.spaceBefore = 0 + pre = [MySpacer(0, client.styles['desc'].spaceBefore)] return pre + client.gather_elements(node, st) + class HandleSphinxDescSignature(SphinxHandler, sphinx.addnodes.desc_signature): def gather_elements(self, client, node, style): # Need to add ids as targets, found this when using one of the # django docs extensions - targets=[i.replace(' ','') for i in node['ids']] - pre='' + targets = [i.replace(' ', '') for i in node['ids']] + pre = '' for i in targets: if i not in client.targets: - pre+=''% i + pre += '' % i client.targets.append(i) - return [Paragraph(pre+client.gather_pdftext(node),style)] + return [Paragraph(pre + client.gather_pdftext(node), style)] + class HandleSphinxDescContent(SphinxHandler, sphinx.addnodes.desc_content): def gather_elements(self, client, node, style): - return [MyIndenter(left=10)] +\ - client.gather_elements(node, client.styles["definition"]) +\ - [MyIndenter(left=-10)] + return ( + [MyIndenter(left=10)] + + client.gather_elements(node, client.styles["definition"]) + + [MyIndenter(left=-10)] + ) + class HandleHList(SphinxHandler, sphinx.addnodes.hlist): def gather_elements(self, client, node, style): @@ -165,51 +200,60 @@ def gather_elements(self, client, node, style): # Represent it as a N-column, 1-row table, each cell containing # a list. - cells = [[ client.gather_elements(child, style) for child in node.children]] - t_style=TableStyle(client.styles['hlist'].commands) - cw=100./len(node.children) - return [ DelayedTable( cells, - colWidths=["%s%%"%cw,]*len(cells), - style=t_style - )] + cells = [[client.gather_elements(child, style) for child in node.children]] + t_style = TableStyle(client.styles['hlist'].commands) + cw = 100.0 / len(node.children) + return [ + DelayedTable(cells, colWidths=["%s%%" % cw,] * len(cells), style=t_style) + ] + class HandleHighlightLang(SphinxHandler, sphinx.addnodes.highlightlang): pass + graphviz_warn = False try: - x=sphinx.ext.graphviz.graphviz + x = sphinx.ext.graphviz.graphviz + class HandleSphinxGraphviz(SphinxHandler, sphinx.ext.graphviz.graphviz): def gather_elements(self, client, node, style): # Based on the graphviz extension global graphviz_warn try: # Is vectorpdf enabled? - if hasattr(VectorPdf,'load_xobj'): + if hasattr(VectorPdf, 'load_xobj'): # Yes, we have vectorpdf - fname, outfn = sphinx.ext.graphviz.render_dot(node['builder'], node['code'], node['options'], 'pdf') + fname, outfn = sphinx.ext.graphviz.render_dot( + node['builder'], node['code'], node['options'], 'pdf' + ) else: # Use bitmap if not graphviz_warn: - log.warning('Using graphviz with PNG output. You get much better results if you enable the vectorpdf extension.') + log.warning( + 'Using graphviz with PNG output. You get much better results if you enable the vectorpdf extension.' + ) graphviz_warn = True - fname, outfn = sphinx.ext.graphviz.render_dot(node['builder'], node['code'], node['options'], 'png') + fname, outfn = sphinx.ext.graphviz.render_dot( + node['builder'], node['code'], node['options'], 'png' + ) if outfn: client.to_unlink.append(outfn) - client.to_unlink.append(outfn+'.map') + client.to_unlink.append(outfn + '.map') else: # Something went very wrong with graphviz, and # sphinx should have given an error already return [] except sphinx.ext.graphviz.GraphvizError as exc: log.error('dot code %r: ' % node['code'] + str(exc)) - return [Paragraph(node['code'],client.styles['code'])] + return [Paragraph(node['code'], client.styles['code'])] return [MyImage(filename=outfn, client=client)] + + except AttributeError: # Probably the graphviz extension is not enabled pass - sphinxhandlers = SphinxHandler() diff --git a/rst2pdf/styles.py b/rst2pdf/styles.py index 7ee3a5f52..0c591d8ea 100644 --- a/rst2pdf/styles.py +++ b/rst2pdf/styles.py @@ -91,7 +91,7 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): # be loaded first flist = [ join(self.PATH, 'styles', 'styles.style'), - join(self.PATH, 'styles', 'default.style') + join(self.PATH, 'styles', 'default.style'), ] + flist self.def_dpi = def_dpi @@ -103,13 +103,23 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): if style_path is None: style_path = [] style_path += [ - '.', os.path.join(self.PATH, 'styles'), '~/.rst2pdf/styles' + '.', + os.path.join(self.PATH, 'styles'), + '~/.rst2pdf/styles', ] self.StyleSearchPath = list(map(os.path.expanduser, style_path)) # Remove duplicates but preserve order. Not very efficient, but these are short lists - self.FontSearchPath = [x for (i,x) in enumerate(self.FontSearchPath) if self.FontSearchPath.index(x) == i] - self.StyleSearchPath = [x for (i,x) in enumerate(self.StyleSearchPath) if self.StyleSearchPath.index(x) == i] + self.FontSearchPath = [ + x + for (i, x) in enumerate(self.FontSearchPath) + if self.FontSearchPath.index(x) == i + ] + self.StyleSearchPath = [ + x + for (i, x) in enumerate(self.StyleSearchPath) + if self.StyleSearchPath.index(x) == i + ] log.info('FontPath:%s' % self.FontSearchPath) log.info('StylePath:%s' % self.StyleSearchPath) @@ -153,23 +163,27 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): self.ps = list(pagesizes.__dict__[pgs]) self.psname = pgs if 'width' in self.page: - del(self.page['width']) + del self.page['width'] if 'height' in self.page: - del(self.page['height']) + del self.page['height'] elif pgs.endswith('-LANDSCAPE'): self.psname = pgs.split('-')[0] - self.ps = list(pagesizes.landscape(pagesizes.__dict__[self.psname])) + self.ps = list( + pagesizes.landscape(pagesizes.__dict__[self.psname]) + ) if 'width' in self.page: - del(self.page['width']) + del self.page['width'] if 'height' in self.page: - del(self.page['height']) + del self.page['height'] else: - log.critical('Unknown page size %s in stylesheet %s' % - (page['size'], ssname)) + log.critical( + 'Unknown page size %s in stylesheet %s' + % (page['size'], ssname) + ) continue else: # A custom size - if 'size'in self.page: - del(self.page['size']) + if 'size' in self.page: + del self.page['size'] # The sizes are expressed in some unit. # For example, 2cm is 2 centimeters, and we need # to do 2*cm (cm comes from reportlab.lib.units) @@ -251,8 +265,8 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): if not fontList[0].startswith(font): # We need to create font aliases, and use them for fname, aliasname in zip( - fontList, - [font + suffix for suffix in suff]): + fontList, [font + suffix for suffix in suff], + ): self.fontsAlias[aliasname] = fname continue @@ -278,14 +292,18 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): for variant in font: location = self.findFont(variant) pdfmetrics.registerFont( - TTFont(str(variant.split('.')[0]), location)) - log.info('Registering font: %s from %s' % (str( - variant.split('.')[0]), location)) + TTFont(str(variant.split('.')[0]), location) + ) + log.info( + 'Registering font: %s from %s' + % (str(variant.split('.')[0]), location) + ) self.embedded.append(str(variant.split('.')[0])) # And map them all together regular, bold, italic, bolditalic = [ - variant.split('.')[0] for variant in font] + variant.split('.')[0] for variant in font + ] addMapping(regular, 0, 0, regular) addMapping(regular, 0, 1, italic) addMapping(regular, 1, 0, bold) @@ -310,13 +328,15 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): fname = font[0] else: fname = font - log.error("Error processing font %s: %s", - os.path.splitext(fname)[0], str(e)) + log.error( + "Error processing font %s: %s", + os.path.splitext(fname)[0], + str(e), + ) log.error("Registering %s as Helvetica alias", fname) self.fontsAlias[fname] = 'Helvetica' except Exception as e: - log.critical("Error processing font %s: %s", fname, - str(e)) + log.critical("Error processing font %s: %s", fname, str(e)) continue # Go though all styles in all stylesheets and find all fontNames. @@ -333,20 +353,21 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): continue # Standard font, nothing to do if style[key] in ( - "Courier", - "Courier-Bold", - "Courier-BoldOblique", - "Courier-Oblique", - "Helvetica", - "Helvetica-Bold", - "Helvetica-BoldOblique", - "Helvetica-Oblique", - "Symbol", - "Times-Bold", - "Times-BoldItalic", - "Times-Italic", - "Times-Roman", - "ZapfDingbats"): + "Courier", + "Courier-Bold", + "Courier-BoldOblique", + "Courier-Oblique", + "Helvetica", + "Helvetica-Bold", + "Helvetica-BoldOblique", + "Helvetica-Oblique", + "Symbol", + "Times-Bold", + "Times-BoldItalic", + "Times-Italic", + "Times-Roman", + "ZapfDingbats", + ): continue # Now we need to do something # See if we can find the font @@ -374,14 +395,15 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): # We need to create font aliases, and use them basefname = style[key].split('-')[0] for fname, aliasname in zip( - fontList, - [basefname + suffix for suffix in suff]): + fontList, [basefname + suffix for suffix in suff], + ): self.fontsAlias[aliasname] = fname - style[key] = self.fontsAlias[basefname + - suff[pos]] + style[key] = self.fontsAlias[basefname + suff[pos]] else: - log.error("Unknown font: \"%s\"," - "replacing with Helvetica", style[key]) + log.error( + "Unknown font: \"%s\"," "replacing with Helvetica", + style[key], + ) style[key] = "Helvetica" # Get styles from all stylesheets in order @@ -408,17 +430,19 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): # Handle alignment constants elif key == 'alignment': - style[key] = dict(TA_LEFT=0, - LEFT=0, - TA_CENTER=1, - CENTER=1, - TA_CENTRE=1, - CENTRE=1, - TA_RIGHT=2, - RIGHT=2, - TA_JUSTIFY=4, - JUSTIFY=4, - DECIMAL=8, )[style[key].upper()] + style[key] = dict( + TA_LEFT=0, + LEFT=0, + TA_CENTER=1, + CENTER=1, + TA_CENTRE=1, + CENTRE=1, + TA_RIGHT=2, + RIGHT=2, + TA_JUSTIFY=4, + JUSTIFY=4, + DECIMAL=8, + )[style[key].upper()] elif key == 'language': if not style[key] in self.languages: @@ -446,8 +470,9 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): s2 = copy(s) s2['name'] = docutils.nodes.make_id(s['name']) log.warning( - '%s is an invalid docutils class name, adding alias %s' % - (s['name'], s2['name'])) + '%s is an invalid docutils class name, adding alias %s' + % (s['name'], s2['name']) + ) styles2.append(s2) self.styles.extend(styles2) @@ -466,7 +491,7 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): if s['name'] != 'base': s['parent'] = self.StyleSheet['base'] else: - del(s['parent']) + del s['parent'] else: s['parent'] = self.StyleSheet[s['parent']] else: @@ -490,8 +515,9 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): # This means you can set the fontSize to # "2cm" or to "150%" which will be calculated # relative to the parent style - s['fontSize'] = self.adjustUnits(s['fontSize'], - s['parent'].fontSize) + s['fontSize'] = self.adjustUnits( + s['fontSize'], s['parent'].fontSize + ) s['trueFontSize'] = s['fontSize'] else: # If s has no parent, it's base, which has @@ -501,7 +527,7 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): # If the leading is not set, but the size is, set it if 'leading' not in s and hasFS: - s['leading'] = 1.2*s['fontSize'] + s['leading'] = 1.2 * s['fontSize'] # If the bullet font size is not set, set it as fontSize if ('bulletFontSize' not in s) and ('fontSize' in s): @@ -510,20 +536,28 @@ def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): # If the borderPadding is a list and wordaxe <=0.3.2, # convert it to an integer. Workaround for Issue if 'borderPadding' in s and ( - ((HAS_WORDAXE and wordaxe_version <= 'wordaxe 0.3.2') - or reportlab.Version < "2.3") - and isinstance(s['borderPadding'], list)): + ( + (HAS_WORDAXE and wordaxe_version <= 'wordaxe 0.3.2') + or reportlab.Version < "2.3" + ) + and isinstance(s['borderPadding'], list) + ): log.warning( 'Using a borderPadding list in ' 'style %s with wordaxe <= 0.3.2 or Reportlab < 2.3. That is not ' - 'supported, so it will probably look wrong' % s['name']) + 'supported, so it will probably look wrong' % s['name'] + ) s['borderPadding'] = s['borderPadding'][0] if 'spaceBefore' in s: - if isinstance(s['spaceBefore'], str) and s['spaceBefore'].startswith('-'): + if isinstance(s['spaceBefore'], str) and s[ + 'spaceBefore' + ].startswith('-'): log.warning('A negative spaceBefore is the same as 0') s['spaceBefore'] = self.adjustUnits(s['spaceBefore']) if 'spaceAfter' in s: - if isinstance(s['spaceAfter'], str) and s['spaceAfter'].startswith('-'): + if isinstance(s['spaceAfter'], str) and s['spaceAfter'].startswith( + '-' + ): log.warning('A negative spaceAfter is the same as 0') s['spaceAfter'] = self.adjustUnits(s['spaceAfter']) @@ -548,12 +582,14 @@ def __getitem__(self, key): return self.StyleSheet[key] else: if key.startswith('pygments'): - log.info("Using undefined style '%s'" - ", aliased to style 'code'." % key) + log.info( + "Using undefined style '%s'" ", aliased to style 'code'." % key + ) newst = copy(self.StyleSheet['code']) else: - log.warning("Using undefined style '%s'" - ", aliased to style 'normal'." % key) + log.warning( + "Using undefined style '%s'" ", aliased to style 'normal'." % key + ) newst = copy(self.StyleSheet['normal']) newst.name = key self.StyleSheet.add(newst) @@ -623,8 +659,9 @@ def innerFind(path, fn): if os.path.isfile(tfn): return tfn return None + for ext in ['', '.style', '.json']: - result = innerFind(self.StyleSearchPath, fn+ext) + result = innerFind(self.StyleSearchPath, fn + ext) if result: break if result is None: @@ -658,7 +695,7 @@ def styleForNode(self, node): n.figure: 'figure', n.tgroup: 'table', n.table: 'table', - n.Admonition: 'admonition' + n.Admonition: 'admonition', } return self[styles.get(node.__class__, 'bodytext')] @@ -671,40 +708,28 @@ def tstyleHead(self, rows=1): """ # This alignment thing is exactly backwards from # the alignment for paragraphstyles - alignment = { - 0: 'LEFT', 1: 'CENTER', 2: 'RIGHT', - 4: 'JUSTIFY', 8: 'DECIMAL'}[self['table-heading'].alignment] + alignment = {0: 'LEFT', 1: 'CENTER', 2: 'RIGHT', 4: 'JUSTIFY', 8: 'DECIMAL',}[ + self['table-heading'].alignment + ] return [ - ('BACKGROUND', - (0, 0), - (-1, rows - 1), - self['table-heading'].backColor), - ('ALIGN', - (0, 0), - (-1, rows - 1), - alignment), - ('TEXTCOLOR', - (0, 0), - (-1, rows - 1), - self['table-heading'].textColor), - ('FONT', + ('BACKGROUND', (0, 0), (-1, rows - 1), self['table-heading'].backColor,), + ('ALIGN', (0, 0), (-1, rows - 1), alignment), + ('TEXTCOLOR', (0, 0), (-1, rows - 1), self['table-heading'].textColor,), + ( + 'FONT', (0, 0), (-1, rows - 1), self['table-heading'].fontName, self['table-heading'].fontSize, - self['table-heading'].leading), - ('VALIGN', - (0, 0), - (-1, rows - 1), - self['table-heading'].valign)] + self['table-heading'].leading, + ), + ('VALIGN', (0, 0), (-1, rows - 1), self['table-heading'].valign), + ] def adjustUnits(self, v, total=None, default_unit='pt'): if total is None: total = self.tw - return adjustUnits(v, total, - self.def_dpi, - default_unit, - emsize=self.emsize) + return adjustUnits(v, total, self.def_dpi, default_unit, emsize=self.emsize) def combinedStyle(self, styles): '''Given a list of style names, it merges them (the existing ones) @@ -787,15 +812,16 @@ def formatColor(value, numeric=True): while len(c) < 6: c = '0' + c if numeric: - r = int(c[:2], 16)/255. - g = int(c[2:4], 16)/255. - b = int(c[4:6], 16)/255. + r = int(c[:2], 16) / 255.0 + g = int(c[2:4], 16) / 255.0 + b = int(c[4:6], 16) / 255.0 if len(c) >= 8: - alpha = int(c[6:8], 16)/255. + alpha = int(c[6:8], 16) / 255.0 return colors.Color(r, g, b, alpha=alpha) return colors.Color(r, g, b) else: - return str("#"+c) + return str("#" + c) + # The values are: # * Minimum number of arguments @@ -865,11 +891,19 @@ def validateCommands(commands): # See if start and stop are the right types if not isinstance(command[1], (list, tuple)): - log.error('Start cell in table command should be list or tuple, got %s [%s]', type(command[1]), command[1]) + log.error( + 'Start cell in table command should be list or tuple, got %s [%s]', + type(command[1]), + command[1], + ) flag = True if not isinstance(command[2], (list, tuple)): - log.error('Stop cell in table command should be list or tuple, got %s [%s]', type(command[1]), command[1]) + log.error( + 'Stop cell in table command should be list or tuple, got %s [%s]', + type(command[1]), + command[1], + ) flag = True # See if the number of arguments is right @@ -893,7 +927,7 @@ def validateCommands(commands): elif typ == "number": pass elif typ == "string": - command[3+pos] = arg + command[3 + pos] = arg else: log.error("This should never happen: wrong type %s", typ) @@ -909,6 +943,7 @@ class CallableStyleSheet(str): which returns the pre-digested stylesheet data when called. ''' + def __new__(cls, name, value=''): self = str.__new__(cls, name) self.value = value diff --git a/rst2pdf/svgimage.py b/rst2pdf/svgimage.py index 2dbaffee9..93ce4f17c 100644 --- a/rst2pdf/svgimage.py +++ b/rst2pdf/svgimage.py @@ -8,16 +8,18 @@ from svglib.svglib import svg2rlg -class SVGImage(Flowable): - def __init__(self, - filename, - width=None, - height=None, - kind='direct', - mask=None, - lazy=True, - srcinfo=None): +class SVGImage(Flowable): + def __init__( + self, + filename, + width=None, + height=None, + kind='direct', + mask=None, + lazy=True, + srcinfo=None, + ): Flowable.__init__(self) self._kind = kind self._mode = 'svg2rlg' @@ -31,16 +33,16 @@ def __init__(self, self.imageWidth = self._w if not self.imageHeight: self.imageHeight = self._h - self.__ratio = float(self.imageWidth)/self.imageHeight + self.__ratio = float(self.imageWidth) / self.imageHeight if kind in ['direct', 'absolute']: self.drawWidth = width or self.imageWidth self.drawHeight = height or self.imageHeight elif kind in ['bound', 'proportional']: factor = min( - float(width) / self.imageWidth, - float(height) / self.imageHeight) + float(width) / self.imageWidth, float(height) / self.imageHeight, + ) self.drawWidth = self.imageWidth * factor - self.drawHeight = self.imageHeight*factor + self.drawHeight = self.imageHeight * factor def wrap(self, aW, aH): return self.drawWidth, self.drawHeight @@ -49,14 +51,14 @@ def drawOn(self, canv, x, y, _sW=0): if _sW and hasattr(self, 'hAlign'): a = self.hAlign if a in ('CENTER', 'CENTRE', TA_CENTER): - x += 0.5*_sW + x += 0.5 * _sW elif a in ('RIGHT', TA_RIGHT): x += _sW elif a not in ('LEFT', TA_LEFT): raise ValueError("Bad hAlign value " + str(a)) canv.saveState() canv.translate(x, y) - canv.scale(self.drawWidth/self._w, self.drawHeight/self._h) + canv.scale(self.drawWidth / self._w, self.drawHeight / self._h) self.doc._drawOn(canv) canv.restoreState() @@ -65,10 +67,13 @@ def drawOn(self, canv, x, y, _sW=0): import sys from reportlab.platypus import SimpleDocTemplate from reportlab.lib.styles import getSampleStyleSheet + doc = SimpleDocTemplate('svgtest.pdf') styles = getSampleStyleSheet() style = styles['Normal'] - Story = [Paragraph("Before the image", style), - SVGImage(sys.argv[1]), - Paragraph("After the image", style)] + Story = [ + Paragraph("Before the image", style), + SVGImage(sys.argv[1]), + Paragraph("After the image", style), + ] doc.build(Story) diff --git a/rst2pdf/tests/conftest.py b/rst2pdf/tests/conftest.py index f60dac9df..ca8cfc8af 100644 --- a/rst2pdf/tests/conftest.py +++ b/rst2pdf/tests/conftest.py @@ -92,20 +92,19 @@ def fuzzy_string_diff(string_a, string_b): class File(pytest.File): if version.parse(pytest.__version__) < version.parse('5.4.0'): + @classmethod def from_parent(cls, parent, fspath): return cls(parent=parent, fspath=fspath) class TxtFile(File): - def collect(self): name = os.path.splitext(self.fspath.basename)[0] yield TxtItem.from_parent(parent=self, name=name) class SphinxFile(File): - def collect(self): name = os.path.split(self.fspath.dirname)[-1] yield SphinxItem.from_parent(parent=self, name=name) @@ -114,6 +113,7 @@ def collect(self): class Item(pytest.Item): if version.parse(pytest.__version__) < version.parse('5.4.0'): + @classmethod def from_parent(cls, parent, name): return cls(parent=parent, name=name) @@ -123,8 +123,7 @@ def _build(self): def _fail(self, msg, output=None): pytest.fail( - f'{msg}:\n\n{output.decode("utf-8")}' if output else msg, - pytrace=False, + f'{msg}:\n\n{output.decode("utf-8")}' if output else msg, pytrace=False, ) def runtest(self): @@ -157,9 +156,8 @@ def runtest(self): if not os.path.exists(reference_file): self._fail( - 'No reference file at %r to compare against.' % ( - os.path.relpath(output_file, ROOT_DIR), - ), + 'No reference file at %r to compare against.' + % (os.path.relpath(output_file, ROOT_DIR),), ) if os.path.isdir(output_file): @@ -183,8 +181,7 @@ def runtest(self): if len(reference_files) != len(output_files): self._fail( - 'Mismatch between number of files expected and generated', - output, + 'Mismatch between number of files expected and generated', output, ) reference_files.sort() @@ -208,7 +205,6 @@ def reportinfo(self): class TxtItem(Item): - def _build(self): input_ref = self.name + '.txt' output_pdf = os.path.join(OUTPUT_DIR, self.name + '.pdf') @@ -250,16 +246,13 @@ def _build(self): no_pdf = os.path.exists(os.path.join(INPUT_DIR, self.name + '.nopdf')) if not os.path.exists(output_file) and not no_pdf: self._fail( - 'File %r was not generated' % ( - os.path.relpath(output_file, ROOT_DIR), - ), + 'File %r was not generated' % (os.path.relpath(output_file, ROOT_DIR),), output, ) elif os.path.exists(output_file) and no_pdf: self._fail( - 'File %r was erroneously generated' % ( - os.path.relpath(output_file, ROOT_DIR), - ), + 'File %r was erroneously generated' + % (os.path.relpath(output_file, ROOT_DIR),), output, ) @@ -267,7 +260,6 @@ def _build(self): class SphinxItem(Item): - def _build(self): __tracebackhide__ = True diff --git a/rst2pdf/tests/input/sphinx-issue285/conf.py b/rst2pdf/tests/input/sphinx-issue285/conf.py index 153ed2c90..99773b866 100644 --- a/rst2pdf/tests/input/sphinx-issue285/conf.py +++ b/rst2pdf/tests/input/sphinx-issue285/conf.py @@ -27,7 +27,9 @@ # Grouping the document tree into PDF files. List of tuples # (source start file, target name, title, author). -pdf_documents = [('index', 'Issue285', u'Issue 285 Documentation', u'Roberto Alsina')] +pdf_documents = [ + ('index', 'Issue285', u'Issue 285 Documentation', u'Roberto Alsina') +] pdf_break_level = 3 pdf_verbosity = 0 diff --git a/rst2pdf/tests/input/sphinx-issue318/conf.py b/rst2pdf/tests/input/sphinx-issue318/conf.py index d1704b070..ab81c3915 100644 --- a/rst2pdf/tests/input/sphinx-issue318/conf.py +++ b/rst2pdf/tests/input/sphinx-issue318/conf.py @@ -27,7 +27,9 @@ # Grouping the document tree into PDF files. List of tuples # (source start file, target name, title, author). -pdf_documents = [('test', 'Issue318', u'Issue 318 Documentation', u'Roberto Alsina')] +pdf_documents = [ + ('test', 'Issue318', u'Issue 318 Documentation', u'Roberto Alsina') +] pdf_use_index = True pdf_domain_indices = True diff --git a/rst2pdf/tests/input/sphinx-issue364/conf.py b/rst2pdf/tests/input/sphinx-issue364/conf.py index a4f543b34..2490bf2a6 100644 --- a/rst2pdf/tests/input/sphinx-issue364/conf.py +++ b/rst2pdf/tests/input/sphinx-issue364/conf.py @@ -33,7 +33,11 @@ u'index', u'test1', u'C.G.', - {'pdf_use_index': True, 'pdf_use_modindex': True, 'pdf_use_coverpage': True}, + { + 'pdf_use_index': True, + 'pdf_use_modindex': True, + 'pdf_use_coverpage': True, + }, ), ( 'index2', diff --git a/rst2pdf/tests/input/sphinx-issue367/conf.py b/rst2pdf/tests/input/sphinx-issue367/conf.py index f91461dcb..77ec6e320 100644 --- a/rst2pdf/tests/input/sphinx-issue367/conf.py +++ b/rst2pdf/tests/input/sphinx-issue367/conf.py @@ -31,7 +31,9 @@ # Grouping the document tree into PDF files. List of tuples # (source start file, target name, title, author, options). -pdf_documents = [('index', u'twcb-de', u'Das Weinkellerbuch', u'Werner F Bruhin')] +pdf_documents = [ + ('index', u'twcb-de', u'Das Weinkellerbuch', u'Werner F Bruhin') +] # A comma-separated list of custom stylesheets. Example: pdf_stylesheets = ['sphinx', 'sphinx-mine'] diff --git a/rst2pdf/tests/input/sphinx-issue601/conf.py b/rst2pdf/tests/input/sphinx-issue601/conf.py index 5f740bbe3..fe958c112 100644 --- a/rst2pdf/tests/input/sphinx-issue601/conf.py +++ b/rst2pdf/tests/input/sphinx-issue601/conf.py @@ -16,7 +16,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- @@ -31,7 +31,7 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' @@ -51,30 +51,30 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: today = 'February 11, 2020' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. @@ -84,7 +84,7 @@ pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -96,26 +96,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -124,38 +124,38 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'sphinx' @@ -164,42 +164,40 @@ # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'sphinx.tex', u'sphinx Documentation', u'RA', 'manual'), - ('index2', 'sphinx2.tex', u'sphinx2 Documentation', u'RA', 'manual'), + ('index', 'sphinx.tex', u'sphinx Documentation', u'RA', 'manual'), + ('index2', 'sphinx2.tex', u'sphinx2 Documentation', u'RA', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True # -- Options for PDF output -------------------------------------------------- # Grouping the document tree into PDF files. List of tuples # (source start file, target name, title, author). -pdf_documents = [ - ('index', u'MyProject1', u'My Project', u'Author Name') -] +pdf_documents = [('index', u'MyProject1', u'My Project', u'Author Name')] # A comma-separated list of custom stylesheets. Example: pdf_stylesheets = ['sphinx'] @@ -207,13 +205,13 @@ # Create a compressed PDF # Use True/False or 1/0 # Example: compressed=True -#pdf_compressed=False +# pdf_compressed=False # A colon-separated list of folders to search for fonts. Example: # pdf_font_path=['/usr/share/fonts', '/usr/share/texmf-dist/fonts/'] # Language to be used for hyphenation support -pdf_language="en_US" +pdf_language = "en_US" # If false, no index is generated. pdf_use_index = True @@ -227,6 +225,6 @@ pdf_break_level = 1 -pdf_verbosity=0 +pdf_verbosity = 0 pdf_invariant = True pdf_real_footnotes = True diff --git a/rst2pdf/tests/input/test_extensions.py b/rst2pdf/tests/input/test_extensions.py index a321b6f43..6c07bbb68 100644 --- a/rst2pdf/tests/input/test_extensions.py +++ b/rst2pdf/tests/input/test_extensions.py @@ -1,7 +1,9 @@ import sys -print(""" +print( + """ This test should print the message from the sample extension, and then this message, and then generate a PDF. -""") +""" +) diff --git a/rst2pdf/tests/input/test_issue_410.py b/rst2pdf/tests/input/test_issue_410.py index d16c3a670..62a9bfbfe 100644 --- a/rst2pdf/tests/input/test_issue_410.py +++ b/rst2pdf/tests/input/test_issue_410.py @@ -1,4 +1,3 @@ def f(): a = 1 b = 2 - diff --git a/rst2pdf/utils.py b/rst2pdf/utils.py index 8107d6961..aa66e9fce 100644 --- a/rst2pdf/utils.py +++ b/rst2pdf/utils.py @@ -12,6 +12,7 @@ PageCounter = None + def parseRaw(data, node): """Parse and process a simple DSL to handle creation of flowables. @@ -30,8 +31,8 @@ def parseRaw(data, node): global PageCounter if PageCounter is None: from rst2pdf.createpdf import PageCounter as pc - PageCounter = pc + PageCounter = pc elements = [] lines = data.splitlines() @@ -64,7 +65,8 @@ def parseRaw(data, node): elements.append(CondPageBreak(float(tokens[1]))) elif command == 'Spacer': elements.append( - flowables.MySpacer(adjustUnits(tokens[1]), adjustUnits(tokens[2]))) + flowables.MySpacer(adjustUnits(tokens[1]), adjustUnits(tokens[2])) + ) elif command == 'Transition': elements.append(flowables.Transition(*tokens[1:])) elif command == 'SetPageCounter': @@ -72,7 +74,9 @@ def parseRaw(data, node): elif command == 'TextAnnotation': elements.append(flowables.TextAnnotation(*tokens[1:])) else: - log.error('Unknown command %s in raw pdf directive [%s]' % (command, nodeid(node))) + log.error( + 'Unknown command %s in raw pdf directive [%s]' % (command, nodeid(node)) + ) return elements @@ -124,17 +128,23 @@ def pisaPreLoop2(node, context, collect=False): media = [x.strip() for x in attr.media.lower().split(",") if x.strip()] # print repr(media) - if (attr.get("type", "").lower() in ("", "text/css") - and (not media or "all" in media or "print" in media - or "pdf" in media)): + if attr.get("type", "").lower() in ("", "text/css") and ( + not media or "all" in media or "print" in media or "pdf" in media + ): if name == "style": for node in node.childNodes: data += pisaPreLoop2(node, context, collect=True) return u"" - if name == "link" and attr.href and attr.rel.lower() == "stylesheet": - context.addCSS('\n@import "%s" %s;' % (attr.href, ",".join(media))) + if ( + name == "link" + and attr.href + and attr.rel.lower() == "stylesheet" + ): + context.addCSS( + '\n@import "%s" %s;' % (attr.href, ",".join(media)) + ) for node in node.childNodes: result = pisaPreLoop2(node, context, collect=collect) @@ -367,11 +377,22 @@ def parseHTML(data, node): context.pathCallback = link_callback # Build story - context = pisaStory(data, path, link_callback, debug, default_css, xhtml, - encoding, context=context, xml_output=xml_output) + context = pisaStory( + data, + path, + link_callback, + debug, + default_css, + xhtml, + encoding, + context=context, + xml_output=xml_output, + ) return context.story + else: # no xhtml2pdf + def parseHTML(data, none): log.error("You need xhtml2pdf installed to use the raw HTML directive.") return [] diff --git a/rst2pdf/writer.py b/rst2pdf/writer.py index 5c84a689e..4707e9225 100644 --- a/rst2pdf/writer.py +++ b/rst2pdf/writer.py @@ -8,29 +8,30 @@ from rst2pdf import createpdf import six - + if six.PY3: unicode = str basestring = str -class PdfWriter(writers.Writer): +class PdfWriter(writers.Writer): def __init__(self, builder): writers.Writer.__init__(self) self.builder = builder self.output = u'' - supported = ('pdf') + supported = 'pdf' """Formats this writer supports.""" config_section = 'pdf writer' - config_section_dependencies = ('writers') + config_section_dependencies = 'writers' """Final translated form of `document`.""" def translate(self): sio = StringIO('') createpdf.RstToPdf(sphinx=True).createPdf( - doctree=self.document, output=sio, compressed=False) + doctree=self.document, output=sio, compressed=False + ) self.output = unicode(sio.getvalue(), 'utf-8', 'ignore') def supports(self, format): diff --git a/setup.cfg b/setup.cfg index 4d987bac6..70874b216 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,6 @@ [egg_info] tag_build = .dev + [flake8] +max-line-length = 88 ignore = E501,W503