Skip to main content

Ralsina.Me — Roberto Alsina's website

Extending rst2pdf: easy and powerful

I do al­most all my busi­ness writ­ing (and my book) us­ing re­struc­tured tex­t. And when I want to pro­duce print­-qual­i­ty out­put, I tend to use my own tool, rst2pdf.

It's pop­u­lar, sure­ly my most pop­u­lar pro­gram, but very few know it's al­so ex­treme­ly easy to ex­tend (hat tip to Patrick Maupin, who wrote this part!). And not on­ly that, but you can make it do some amaz­ing stuff with a lit­tle ef­fort.

To show that, let's cre­ate the most daz­zling sec­tion head­ings known to man ( Let's see you do what this ba­by can do in La­TeX ;-).

First: de­fine the prob­lem.

The ti­tles rst2pdf can pro­duce are bor­ing. If you pull ev­ery lever and push ev­ery but­ton, you may end with the ti­tle tex­t, in a Com­ic San­s, right-aligned, in pink let­ter­ing, with av­o­cado-­green back­ground and a red bor­der.

And that's as far as the cus­tomiza­tion ca­pa­bil­i­ties go us­ing stylesheet­s. That's usu­al­ly enough, be­cause rst2pdf is not meant for brochures or some­thing like that (but I have done it).

The re­al prob­lem is that when you get all graph­ic-de­sign­er on rst2pdf, you lose doc­u­ment struc­ture, be­cause you are not be­ing se­man­tic.

Sec­ond: de­fine the goal.

So, imag­ine you want to make a head­ing that looks like this:

fancytitles1

The im­age is tak­en from the li­brary of con­gress with some light (and bad) gimp­ing by me to leave that emp­ty space at the left, and the ti­tle was added us­ing Inkscape.

Can you do that with rst2pdf? Hell no you can't. Not with­out cod­ing. So let's code an ex­ten­sion that lets you cre­ate any head­ing you like with­in the lim­its of Inkscape!

First, we cre­ate a SVG tem­plate for the head­ings (it's a bit big be­cause it has the bit­map em­bed­ded).

Three: the im­age-­head­ing flow­able

Suppose you have an image of the heading just like the one above. How would you draw that in a PDF? In reportlab, you do that using flowables which are elements that compose the story that is your document. These flowables are arranged in pages, and that's your PDF.

If you are do­ing a head­ing, there's a bit more, in that you need to add a book­mark, so it ap­pears on the PDF ta­ble of con­tents.

So, here's a flowable that does just that. It's cobbled from pieces inside rst2pdf, and is basically an unholy mix of Heading and MyImage:

class FancyHeading(MyImage):
  '''This is a cross between the Heading flowable, that adds outline
  entries so you have a PDF TOC, and MyImage, that draws images'''

  def __init__(self, *args, **kwargs):
      # The inicialization is taken from rst2pdf.flowables.Heading
      self.stext = kwargs.pop('text')
      # Cleanup title text
      self.stext = re.sub(r'<[^>]*?>', '', unescape(self.stext))
      self.stext = self.stext.strip()

      # Stuff needed for the outline entry
      self.snum = kwargs.pop('snum')
      self.level = kwargs.pop('level')
      self.parent_id= kwargs.pop('parent_id')


      MyImage.__init__(self, *args, **kwargs)

  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]

      # Add outline entry. This is copied from rst2pdf.flowables.heading
      canv.bookmarkHorizontal(self.parent_id,0,0+self.image.height)

      if canv.firstSect:
          canv.sectName = self.stext
          canv.firstSect=False
          if self.snum is not None:
              canv.sectNum = self.snum
          else:
              canv.sectNum = ""

      canv.addOutlineEntry(self.stext.encode('utf-8','replace'),
                                self.parent_id.encode('utf-8','replace'),
                                int(self.level), False)

      # And let MyImage do all the drawing
      MyImage.drawOn(self,canv,x,y,_sW)

And how do we tell rst2df to use that instead of a regular Heading? by overriding the TitleHandler class. Here's where the extension magic kicks in.

If you de­fine, in an ex­ten­sion, a class like this:

class FancyTitleHandler(genelements.HandleParagraph, docutils.nodes.title):

Then that class will handle all docutils nodes of class docutils.nodes.title. Here, I just took rst2pdf.genelements.HandleTitle and changed how it works for level-1 headings, making it generate a FancyHeading instead of a Heading... and that's all there is to it.

class FancyTitleHandler(genelements.HandleParagraph, docutils.nodes.title):
  '''
  This class will handle title nodes.

  It takes a "titletemplate.svg", replaces TITLEGOESHERE with
  the actual title text, and draws that using the FancyHeading flowable
  (see below).

  Since this class is defined in an extension, it
  effectively replaces rst2pdf.genelements.HandleTitle.
  '''

  def gather_elements(self, client, node, style):
      # This method is copied from the HandleTitle class
      # in rst2pdf.genelements.

      # Special cases: (Not sure this is right ;-)
      if isinstance(node.parent, docutils.nodes.document):
          #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'])]
      elif isinstance(node.parent, docutils.nodes.Admonition):
          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'])]
      elif isinstance(node.parent, docutils.nodes.sidebar):
          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']:
              snum = fch.astext()
          else:
              snum = None
          key = node.get('refid')
          maxdepth=4
          if reportlab.Version > '2.1':
              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'-'+unicode(id(node))
          if 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 ;-)
              # Hack the title template SVG
              tfile = open('titletemplate.svg')
              tdata = tfile.read()
              tfile.close()
              tfile = tempfile.NamedTemporaryFile(dir='.', delete=False, suffix='.svg')
              tfname = tfile.name
              tfile.write(tdata.replace('TITLEGOESHERE', text))
              tfile.close()

              # Now tfname contains a SVG with the right title.
              # Make rst2pdf delete it later.
              client.to_unlink.append(tfname)

              e = FancyHeading(tfname, width=700, height=100,
                  client=client, snum=snum, parent_id=parent_id,
                  text=text, level=client.depth-1)

              node.elements = [e]

          if client.depth <= client.breaklevel:
              node.elements.insert(0, MyPageBreak(breakTo=client.breakside))
      return node.elements

The full ex­ten­sion is in SVN and you can try it this way:

[fancytitles]$ rst2pdf -e fancytitles -e inkscape demo.txt -b1

You need to en­able the Inkscape ex­ten­sion so the SVG will look nice. And here's the out­put:

fancytitles2

You can over­ride how any el­e­ment is han­dled. That's be­ing ex­ten­si­ble :-)

Njharman / 2010-10-05 22:27:

Great post

Alquimista / 2011-02-16 16:51:

como puedo cambiar el estilo (por ejemplo el color) de los títulos, subtitulos, se que es con el archivo .style, pero no se que parametros puedo usar, actualmente estoy usándolo para crear una presentación.

Si pudiera cambiar también los Bullets

Roberto Alsina / 2011-02-16 18:42:

Fijate en los estilos, los que queres son heading* title y subtitle.

Para saber que modificar, fijate el stylesheet que viene por default, ahi tenes ejemplos de todo.

phone number lookup / 2011-12-03 22:27:

this is really interesting viewpoint on the subject i might add

employment background check / 2011-12-27 23:28:

Man ... Beautiful . Amazing ... I will bookmark your website and use the your RSS feed also


Contents © 2000-2023 Roberto Alsina