Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Extensiones para rst2pdf: Fácil y poderoso

Es po­pu­la­r, por le­jos mi pro­gra­ma más usa­do, pe­ro po­cos sa­ben que es tam­bién fá­cil de ex­ten­der (¡s­alu­dos a Pa­tri­ck Mau­pi­n, que es­cri­bió esa par­te!). Y no só­lo eso, sino que se pue­de ha­cer que ha­ga co­sas bas­tan­te asom­bro­sas con un po­co de es­fuer­zo.

Pa­ra de­mos­trar­lo, cree­mos los tí­tu­los de sec­ción más im­pre­sio­nan­tes del mun­do y sus al­re­de­do­res (a ver co­mo ha­cen es­to con La­TeX ;-)

Pri­me­ro: de­fi­na­mos el pro­ble­ma

Los tí­tu­los que pue­de pro­du­cir rs­t2­pdf son abu­rri­do­s. Si apre­tás to­dos los bo­to­nes y ti­rás de to­das las pa­lan­ca­s, po­dés lle­gar a pro­du­cir un tí­tu­lo en Co­mic San­s, ali­nea­do de­re­cha en le­tras ro­sas con fon­do pal­ta y bor­de ro­jo.

Y has­ta ahí lle­ga la per­so­na­li­za­ción que po­dés ha­cer usan­do sty­les­hee­ts. Nor­mal­men­te eso es su­fi­cien­te por­que rs­t2­pdf no es­tá pen­sa­do pa­ra fo­lle­te­ría (aun­que al­gu­na se ha he­cho).

El pro­ble­ma real es que si te po­nés en di­se­ña­dor grá­fi­co con rs­t2­pdf, per­dés la es­truc­tu­ra del do­cu­men­to por­que no es­tás sien­do se­mánti­co.

Se­gun­do: de­fi­nir la me­ta

Uie­ro ha­cer en­ca­be­za­dos co­mo es­te:

fancytitles1

La ima­gen es­tá saca­da de la bi­blio­te­ca del con­gre­so de EEUU con un po­co de (ma­l) tra­ba­jo de gimp pa­ra de­jar el es­pa­cio li­bre a la iz­quier­da, y el tí­tu­lo se agre­gó con Inks­ca­pe.

¿Se pue­de ha­cer eso con rs­t2­pdf? No, ni cer­ca. No sin pro­gra­ma­r. ¡A­sí que pro­gra­me­mos una ex­ten­sión que te per­mi­ta crear cual­quier en­ca­be­za­do que vos quie­ras den­tro de los lí­mi­tes de Inks­ca­pe!

Pri­me­ro crea­mos un tem­pla­te SVG pa­ra los en­ca­be­za­dos (Es un po­co gran­de por­que tie­ne la ima­gen aden­tro­).

Ter­ce­ro: el flo­wa­ble en­ca­be­za­do­-i­ma­gen

Suponete que tenés una imagen del encabezado exactamente como esa de arriba. ¿Como lo dibujás en un PDF? En reportlab se hace usando flowables que son elementos que componen la historia que es tu documento. Esos flowables se acomodan en páginas, y eso es tu PDF.

Si es­tás ha­cien­do un tí­tu­lo, hay una co­sa má­s, ne­ce­si­tás crear un book­ma­rk pa­ra que apa­rez­ca en la ta­bla de con­te­ni­dos del PDF.

Este es un flowable que hace eso. Está hecho pegando pedazos de cosas de rst2pdf y es una cruza maligna entre Heading y 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)

¿Y cómo le decimos a rst2pdf que use eso en vez de un Heading común? Pisando la clase TitleHandler. Acá es donde entra la magia de las extensiones.

Si se de­fi­ne, en una ex­ten­sió­n, una cla­se co­mo es­ta:

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

Entonces esa clase va a ser responsable de todos los nodos de clase docutils.nodes.title. En este caso, tan solo copié rst2pdf.genelements.HandleTitle y cambié el resultado de los encabezados nivel 1 para que use un FancyHeading en vez de un Heading... y eso es todo.

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

La ex­ten­sión es­tá en SVN y se pue­de pro­bar así:

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

Hay que ha­bi­li­tar la ex­ten­sión Inks­ca­pe pa­ra que los SVG se vean lo me­jor po­si­ble. Y es­ta es la sali­da:

fancytitles2

Se pue­de cam­biar cual­quier ele­men­to de la sali­da. Eso es ser 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