Merge branch 'pretty' into commerce
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 19 Jan 2012 15:35:59 +0000 (16:35 +0100)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 19 Jan 2012 15:35:59 +0000 (16:35 +0100)
Conflicts:
librarian/cover.py
librarian/epub.py
librarian/epub/xsltContent.xsl
librarian/epub/xsltScheme.xsl
scripts/book2epub

1  2 
librarian/cover.py
librarian/epub.py
librarian/epub/style.css
librarian/epub/xsltContent.xsl
librarian/epub/xsltScheme.xsl
librarian/packagers.py
scripts/book2epub

diff --combined librarian/cover.py
@@@ -7,13 -7,84 +7,116 @@@ import Image, ImageFont, ImageDraw, Ima
  from librarian import get_resource
  
  
 -             shadow_color=None):
+ class TextBox(object):
+     """Creates an Image with a series of centered strings."""
+     SHADOW_X = 3
+     SHADOW_Y = 3
+     SHADOW_BLUR = 3
+     def __init__(self, max_width, max_height, padding_x=None, padding_y=None):
+         if padding_x is None:
+             padding_x = self.SHADOW_X + self.SHADOW_BLUR
+         if padding_y is None:
+             padding_y = self.SHADOW_Y + self.SHADOW_BLUR
+         self.max_width = max_width
+         self.max_text_width = max_width - 2 * padding_x
+         self.padding_y = padding_y
+         self.height = padding_y
+         self.img = Image.new('RGBA', (max_width, max_height))
+         self.draw = ImageDraw.Draw(self.img)
+         self.shadow_img = None
+         self.shadow_draw = None
+     def skip(self, height):
+         """Skips some vertical space."""
+         self.height += height
+     def text(self, text, color='#000', font=None, line_height=20, 
 -            line = text
 -            line_width = self.draw.textsize(line, font=font)[0]
 -            while line_width > self.max_text_width:
 -                parts = line.rsplit(' ', 1)
 -                if len(parts) == 1:
 -                    line_width = self.max_text_width
 -                    break
 -                line = parts[0]
++             shadow_color=None, shortener=None):
+         """Writes some centered text."""
+         if shadow_color:
+             if not self.shadow_img:
+                 self.shadow_img = Image.new('RGBA', self.img.size)
+                 self.shadow_draw = ImageDraw.Draw(self.shadow_img)
+         while text:
++            if shortener:
++                for line in shortener(text):
++                    if text_draw.textsize(line, font=font)[0] <= self.max_text_width:
++                        break
++                text = ''
++            else:
++                line = text
+                 line_width = self.draw.textsize(line, font=font)[0]
++                while line_width > self.max_text_width:
++                    parts = line.rsplit(' ', 1)
++                    if len(parts) == 1:
++                        line_width = self.max_text_width
++                        break
++                    line = parts[0]
++                    line_width = self.draw.textsize(line, font=font)[0]
++
+             line = line.strip() + ' '
+             pos_x = (self.max_width - line_width) / 2
+             if shadow_color:
+                 self.shadow_draw.text(
+                         (pos_x + self.SHADOW_X, self.height + self.SHADOW_Y),
+                         line, font=font, fill=shadow_color
+                 )
+             self.draw.text((pos_x, self.height), line, font=font, fill=color)
+             self.height += line_height
+             # go to next line
+             text = text[len(line):]
++    @staticmethod
++    def person_shortener(text):
++        yield text
++        chunks = text.split()
++        n_chunks = len(chunks)
++        # make initials from given names, starting from last
++        for i in range(n_chunks - 2, -1, -1):
++            chunks[i] = chunks[i][0] + '.'
++            yield " ".join(chunks)
++        # remove given names initials, starting from last
++        while len(chunks) > 2:
++            del chunks[1]
++            yield " ".join(chunks)
++
++    @staticmethod
++    def title_shortener(text):
++        yield text
++        chunks = text.split()
++        n_chunks = len(chunks)
++        # remove words, starting from last one
++        while len(chunks) > 1:
++            del chunks[-1]
++            yield " ".join(chunks) + u'…'
++
+     def image(self):
+         """Creates the actual Image object."""
+         image = Image.new('RGBA', (self.max_width,
+                                    self.height + self.padding_y))
+         if self.shadow_img:
+             shadow = self.shadow_img.filter(ImageFilter.BLUR)
+             image.paste(shadow, (0, 0), shadow)
+             image.paste(self.img, (0, 0), self.img)
+         else:
+             image.paste(self.img, (0, 0))
+         return image
  class Cover(object):
+     """Abstract base class for cover images generator."""
      width = 600
      height = 800
      background_color = '#fff'
      background_img = None
  
 +    author_align = 'c'
      author_top = 100
      author_margin_left = 20
      author_margin_right = 20
      author_color = '#000'
      author_shadow = None
      author_font = None
 +    author_wrap = True
  
 +    title_align = 'c'
      title_top = 100
      title_margin_left = 20
      title_margin_right = 20
      title_color = '#000'
      title_shadow = None
      title_font = None
 +    title_wrap = True
  
      logo_bottom = None
      logo_width = None
+     uses_dc_cover = False
  
      format = 'JPEG'
  
      exts = {
          'JPEG': 'jpg',
          'PNG': 'png',
          'PNG': 'image/png',
          }
  
-     @staticmethod
-     def person_shortener(text):
-         yield text
-         chunks = text.split()
-         n_chunks = len(chunks)
-         # make initials from given names, starting from last
-         for i in range(n_chunks - 2, -1, -1):
-             chunks[i] = chunks[i][0] + '.'
-             yield " ".join(chunks)
-         # remove given names initials, starting from last
-         while len(chunks) > 2:
-             del chunks[1]
-             yield " ".join(chunks)
-     @staticmethod
-     def title_shortener(text):
-         yield text
-         chunks = text.split()
-         n_chunks = len(chunks)
-         # remove words, starting from last one
-         while len(chunks) > 1:
-             del chunks[-1]
-             yield " ".join(chunks) + u'…'
-     @staticmethod
-     def draw_text(text, img, font, align, shortener, margin_left, width, pos_y, lineskip, color, shadow_color):
-         if shadow_color:
-             shadow_img = Image.new('RGBA', img.size)
-             shadow_draw = ImageDraw.Draw(shadow_img)
-         text_img = Image.new('RGBA', img.size)
-         text_draw = ImageDraw.Draw(text_img)
-         while text:
-             if shortener:
-                 for line in shortener(text):
-                     if text_draw.textsize(line, font=font)[0] <= width:
-                         break
-                 text = ''
-             else:
-                 line = text
-                 while text_draw.textsize(line, font=font)[0] > width:
-                     try:
-                         line, ext = line.rsplit(' ', 1)
-                     except:
-                         break
-                 text = text[len(line)+1:]
-             pos_x = margin_left
-             if align == 'c':
-                 pos_x += (width - text_draw.textsize(line, font=font)[0]) / 2
-             elif align == 'r':
-                 pos_x += (width - text_draw.textsize(line, font=font)[0])
-             if shadow_color:
-                 shadow_draw.text((pos_x + 3, pos_y + 3), line, font=font, fill=shadow_color)
-             text_draw.text((pos_x, pos_y), line, font=font, fill=color)
-             pos_y += lineskip
-         if shadow_color:
-             shadow_img = shadow_img.filter(ImageFilter.BLUR)
-             img.paste(shadow_img, None, shadow_img)
-         img.paste(text_img, None, text_img)
-         return pos_y
-     def __init__(self, author='', title=''):
-         self.author = author
-         self.title = title
+     def __init__(self, book_info):
+         self.author = ", ".join(auth.readable() for auth in book_info.authors)
+         self.title = book_info.title
  
      def pretty_author(self):
+         """Allows for decorating author's name."""
          return self.author
  
      def pretty_title(self):
+         """Allows for decorating title."""
          return self.title
  
      def image(self):
  
          if self.background_img:
              background = Image.open(self.background_img)
 -            img.paste(background, None, background)
 +            try:
 +                img.paste(background, None, background)
 +            except ValueError, e:
 +                img.paste(background)
              del background
  
          # WL logo
              logo = logo.resize((self.logo_width, logo.size[1] * self.logo_width / logo.size[0]))
              img.paste(logo, ((self.width - self.logo_width) / 2, img.size[1] - logo.size[1] - self.logo_bottom))
  
-         author_font = self.author_font or ImageFont.truetype(get_resource('fonts/DejaVuSerif.ttf'), 30)
-         author_shortener = None if self.author_wrap else self.person_shortener 
-         title_y = self.draw_text(self.pretty_author(), img, author_font, self.author_align, author_shortener,
-                     self.author_margin_left, self.width - self.author_margin_left - self.author_margin_right, self.author_top,
-                     self.author_lineskip, self.author_color, self.author_shadow) + self.title_top
-         title_shortener = None if self.title_wrap else self.title_shortener 
-         title_font = self.title_font or ImageFont.truetype(get_resource('fonts/DejaVuSerif.ttf'), 40)
-         self.draw_text(self.pretty_title(), img, title_font, self.title_align, title_shortener,
-                     self.title_margin_left, self.width - self.title_margin_left - self.title_margin_right, title_y,
-                     self.title_lineskip, self.title_color, self.title_shadow)
+         top = self.author_top
+         tbox = TextBox(
+             self.width - self.author_margin_left - self.author_margin_right,
+             self.height - top,
+             )
+         author_font = self.author_font or ImageFont.truetype(
+             get_resource('fonts/DejaVuSerif.ttf'), 30)
++        author_shortener = None if self.author_wrap else TextBox.person_shortener 
+         tbox.text(self.pretty_author(), self.author_color, author_font,
 -            self.author_lineskip, self.author_shadow)
++            self.author_lineskip, self.author_shadow, author_shortener)
+         text_img = tbox.image()
+         img.paste(text_img, (self.author_margin_left, top), text_img)
+         
+         top += text_img.size[1] + self.title_top
+         tbox = TextBox(
+             self.width - self.title_margin_left - self.title_margin_right,
+             self.height - top,
+             )
+         title_font = self.author_font or ImageFont.truetype(
+             get_resource('fonts/DejaVuSerif.ttf'), 40)
++        title_shortener = None if self.title_wrap else TextBox.title_shortener 
+         tbox.text(self.pretty_title(), self.title_color, title_font,
 -            self.title_lineskip, self.title_shadow)
++            self.title_lineskip, self.title_shadow, title_shortener)
+         text_img = tbox.image()
+         img.paste(text_img, (self.title_margin_left, top), text_img)
  
          return img
  
          return self.image().save(format=self.format, *args, **kwargs)
  
  
+ class WLCover(Cover):
+     """Default Wolne Lektury cover generator."""
+     uses_dc_cover = True
+     author_font = ImageFont.truetype(
+         get_resource('fonts/JunicodeWL-Regular.ttf'), 20)
+     author_lineskip = 30
+     title_font = ImageFont.truetype(
+         get_resource('fonts/DejaVuSerif-Bold.ttf'), 30)
+     title_lineskip = 40
+     title_box_width = 350
+     bar_width = 35
+     background_color = '#444'
+     author_color = '#444'
+     epochs = {
+         u'Starożytność': 0,
+         u'Średniowiecze': 30,
+         u'Renesans': 60,
+         u'Barok': 90,
+         u'Oświecenie': 120,
+         u'Romantyzm': 150,
+         u'Pozytywizm': 180,
+         u'Modernizm': 210,
+         u'Dwudziestolecie międzywojenne': 240,
+         u'Współczesność': 270,
+     }
+     def __init__(self, book_info):
+         super(WLCover, self).__init__(book_info)
+         self.kind = book_info.kind
+         self.epoch = book_info.epoch
+         if book_info.cover_url:
+             from urllib2 import urlopen
+             from StringIO import StringIO
+             bg_src = urlopen(book_info.cover_url)
+             self.background_img = StringIO(bg_src.read())
+             bg_src.close()
+     def pretty_author(self):
+         return self.author.upper()
+     def image(self):
+         from colorsys import hsv_to_rgb
+         img = Image.new('RGB', (self.width, self.height), self.background_color)
+         draw = ImageDraw.Draw(img)
+         if self.epoch in self.epochs:
+             epoch_color = tuple(int(255 * c) for c in hsv_to_rgb(
+                     float(self.epochs[self.epoch]) / 360, .7, .7))
+         else:
+             epoch_color = '#000'
+         draw.rectangle((0, 0, self.bar_width, self.height), fill=epoch_color)
+         if self.background_img:
+             src = Image.open(self.background_img)
+             trg_size = (self.width - self.bar_width, self.height)
+             if src.size[0] * trg_size[1] < src.size[1] * trg_size[0]:
+                 resized = (
+                     trg_size[0],
+                     src.size[1] * trg_size[0] / src.size[0]
+                 )
+                 cut = (resized[1] - trg_size[1]) / 2
+                 src = src.resize(resized)
+                 src = src.crop((0, cut, src.size[0], src.size[1] - cut))
+             else:
+                 resized = (
+                     src.size[0] * trg_size[1] / src.size[1],
+                     trg_size[1],
+                 )
+                 cut = (resized[0] - trg_size[0]) / 2
+                 src = src.resize(resized)
+                 src = src.crop((cut, 0, src.size[0] - cut, src.size[1]))
+             
+             img.paste(src, (self.bar_width, 0))
+             del src
+         box = TextBox(self.title_box_width, self.height, padding_y=20)
+         box.text(self.pretty_author(), 
+                  font=self.author_font,
+                  line_height=self.author_lineskip,
+                  color=self.author_color,
+                  shadow_color=self.author_shadow,
+                 )
+         box.skip(10)
+         box.draw.line((75, box.height, 275, box.height), 
+                 fill=self.author_color, width=2)
+         box.skip(15)
+         box.text(self.pretty_title(),
+                  line_height=self.title_lineskip,
+                  font=self.title_font,
+                  color=epoch_color,
+                  shadow_color=self.title_shadow,
+                 )
+         box_img = box.image()
+         if self.kind == 'Liryka':
+             # top
+             box_top = 100
+         elif self.kind == 'Epika':
+             # bottom
+             box_top = self.height - 100 - box_img.size[1]
+         else:
+             # center
+             box_top = (self.height - box_img.size[1]) / 2
+         box_left = self.bar_width + (self.width - self.bar_width - 
+                         box_img.size[0]) / 2
+         draw.rectangle((box_left, box_top, 
+             box_left + box_img.size[0], box_top + box_img.size[1]),
+             fill='#fff')
+         img.paste(box_img, (box_left, box_top), box_img)
+         return img
  
  class VirtualoCover(Cover):
      width = 600
@@@ -226,42 -364,3 +404,42 @@@ class GandalfCover(Cover)
      logo_bottom = 25
      logo_width = 250
      format = 'PNG'
 +
 +
 +class ArtaTechCover(Cover):
 +    width = 600
 +    height = 800
 +    background_img = get_resource('res/cover-arta-tech.jpg')
 +    author_top = 132
 +    author_margin_left = 235
 +    author_margin_right = 23
 +    author_align = 'r'
 +    author_font = ImageFont.truetype(get_resource('fonts/DroidSans.ttf'), 32)
 +    author_color = '#555555'
 +    author_wrap = False
 +    title_top = 17
 +    title_margin_right = 21
 +    title_margin_left = 60
 +    title_align = 'r'
 +    title_font = ImageFont.truetype(get_resource('fonts/EBGaramond-Regular.ttf'), 42)
 +    title_color = '#222222'
 +    title_wrap = False
 +    format = 'JPEG'
 +
 +    def pretty_author(self):
 +        return self.author.upper()
 +
 +
 +def ImageCover(img):
 +    """ a class factory for simple image covers """
 +    img = Image.open(img)
 +
 +    class ImgCover(Cover):
 +        def image(self):
 +            return img
 +
 +        @property
 +        def format(self):
 +            return self.image().format
 +
 +    return ImgCover
diff --combined librarian/epub.py
@@@ -5,7 -5,6 +5,7 @@@
  #
  from __future__ import with_statement
  
 +from copy import deepcopy
  import os
  import os.path
  import subprocess
@@@ -13,14 -12,11 +13,11 @@@ from StringIO import StringI
  from copy import deepcopy
  from lxml import etree
  import zipfile
- from tempfile import mkdtemp
+ from tempfile import mkdtemp, NamedTemporaryFile
  from shutil import rmtree
  
- import sys
- from librarian import XMLNamespace, RDFNS, DCNS, WLNS, NCXNS, OPFNS, XHTMLNS, NoDublinCore
- from librarian.dcparser import BookInfo
- from librarian.cover import ImageCover
+ from librarian import RDFNS, WLNS, NCXNS, OPFNS, XHTMLNS, OutputFile
+ from librarian.cover import WLCover
  
  from librarian import functions, get_resource
  
@@@ -160,19 -156,23 +157,23 @@@ def add_to_spine(spine, partno)
  
  
  class TOC(object):
-     def __init__(self, name=None, part_number=None):
+     def __init__(self, name=None, part_href=None):
          self.children = []
          self.name = name
-         self.part_number = part_number
+         self.part_href = part_href
          self.sub_number = None
  
-     def add(self, name, part_number, level=0, is_part=True):
+     def add(self, name, part_href, level=0, is_part=True, index=None):
+         assert level == 0 or index is None
          if level > 0 and self.children:
-             return self.children[-1].add(name, part_number, level-1, is_part)
+             return self.children[-1].add(name, part_href, level-1, is_part)
          else:
              t = TOC(name)
-             t.part_number = part_number
-             self.children.append(t)
+             t.part_href = part_href
+             if index is not None:
+                 self.children.insert(index, t)
+             else:
+                 self.children.append(t)
              if not is_part:
                  t.sub_number = len(self.children) + 1
                  return t.sub_number
          else:
              return 0
  
-     def write_to_xml(self, nav_map, counter):
+     def href(self):
+         src = self.part_href
+         if self.sub_number is not None:
+             src += '#sub%d' % self.sub_number
+         return src
+     def write_to_xml(self, nav_map, counter=1):
          for child in self.children:
              nav_point = nav_map.makeelement(NCXNS('navPoint'))
              nav_point.set('id', 'NavPoint-%d' % counter)
              nav_point.append(nav_label)
  
              content = nav_map.makeelement(NCXNS('content'))
-             src = 'part%d.html' % child.part_number
-             if child.sub_number is not None:
-                 src += '#sub%d' % child.sub_number
-             content.set('src', src)
+             content.set('src', child.href())
              nav_point.append(content)
              nav_map.append(nav_point)
              counter = child.write_to_xml(nav_point, counter + 1)
          return counter
  
+     def html_part(self, depth=0):
+         texts = []
+         for child in self.children:
+             texts.append(
+                 "<div style='margin-left:%dem;'><a href='%s'>%s</a></div>" %
+                 (depth, child.href(), child.name))
+             texts.append(child.html_part(depth+1))
+         return "\n".join(texts)
+     def html(self):
+         with open(get_resource('epub/toc.html')) as f:
+             t = unicode(f.read(), 'utf-8')
+         return t % self.html_part()
  
  def used_chars(element):
      """ Lists characters used in an ETree Element """
@@@ -250,9 -267,9 +268,9 @@@ def transform_chunk(chunk_xml, chunk_no
      toc = TOC()
      for element in chunk_xml[0]:
          if element.tag in ("naglowek_czesc", "naglowek_rozdzial", "naglowek_akt", "srodtytul"):
-             toc.add(node_name(element), chunk_no)
+             toc.add(node_name(element), "part%d.html" % chunk_no)
          elif element.tag in ('naglowek_podrozdzial', 'naglowek_scena'):
-             subnumber = toc.add(node_name(element), chunk_no, level=1, is_part=False)
+             subnumber = toc.add(node_name(element), "part%d.html" % chunk_no, level=1, is_part=False)
              element.set('sub', str(subnumber))
      if empty:
          if not _empty_html_static:
      return output_html, toc, chars
  
  
- def transform(provider, slug=None, file_path=None, output_file=None, output_dir=None, make_dir=False, verbose=False,
+ def transform(wldoc, verbose=False,
+               style=None, html_toc=False,
                sample=None, cover=None, flags=None):
      """ produces a EPUB file
  
-     provider: a DocProvider
-     slug: slug of file to process, available by provider
-     output_file: file-like object or path to output file
-     output_dir: path to directory to save output file to; either this or output_file must be present
-     make_dir: writes output to <output_dir>/<author>/<slug>.epub instead of <output_dir>/<slug>.epub
      sample=n: generate sample e-book (with at least n paragraphs)
-     cover: a cover.Cover object
-     flags: less-advertising, images, not-wl
+     cover: a cover.Cover object or True for default
 -    flags: less-advertising, without-fonts
++    flags: less-advertising, without-fonts, images, not-wl
      """
  
-     def transform_file(input_xml, chunk_counter=1, first=True, sample=None):
+     def transform_file(wldoc, chunk_counter=1, first=True, sample=None):
          """ processes one input file and proceeds to its children """
  
-         replace_characters(input_xml.getroot())
-         children = [child.text for child in input_xml.findall('.//'+DCNS('relation.hasPart'))]
+         replace_characters(wldoc.edoc.getroot())
  
          # every input file will have a TOC entry,
          # pointing to starting chunk
-         toc = TOC(node_name(input_xml.find('.//'+DCNS('title'))), chunk_counter)
+         toc = TOC(wldoc.book_info.title, "part%d.html" % chunk_counter)
          chars = set()
          if first:
              # write book title page
-             html_tree = xslt(input_xml, get_resource('epub/xsltTitle.xsl'))
+             html_tree = xslt(wldoc.edoc, get_resource('epub/xsltTitle.xsl'))
              chars = used_chars(html_tree.getroot())
              zip.writestr('OPS/title.html',
                   etree.tostring(html_tree, method="html", pretty_print=True))
-         elif children:
+             # add a title page TOC entry
+             toc.add(u"Strona tytułowa", "title.html")
+         elif wldoc.book_info.parts:
              # write title page for every parent
              if sample is not None and sample <= 0:
                  chars = set()
                  html_string = open(get_resource('epub/emptyChunk.html')).read()
              else:
-                 html_tree = xslt(input_xml, get_resource('epub/xsltChunkTitle.xsl'))
+                 html_tree = xslt(wldoc.edoc, get_resource('epub/xsltChunkTitle.xsl'))
                  chars = used_chars(html_tree.getroot())
                  html_string = etree.tostring(html_tree, method="html", pretty_print=True)
              zip.writestr('OPS/part%d.html' % chunk_counter, html_string)
              add_to_spine(spine, chunk_counter)
              chunk_counter += 1
  
-         if len(input_xml.getroot()) > 1:
+         if len(wldoc.edoc.getroot()) > 1:
              # rdf before style master
-             main_text = input_xml.getroot()[1]
+             main_text = wldoc.edoc.getroot()[1]
          else:
              # rdf in style master
-             main_text = input_xml.getroot()[0]
+             main_text = wldoc.edoc.getroot()[0]
              if main_text.tag == RDFNS('RDF'):
                  main_text = None
  
                  add_to_spine(spine, chunk_counter)
                  chunk_counter += 1
  
-         if children:
-             for child in children:
-                 child_xml = etree.parse(provider.by_uri(child))
-                 child_toc, chunk_counter, chunk_chars, sample = transform_file(child_xml, chunk_counter, first=False, sample=sample)
-                 toc.append(child_toc)
-                 chars = chars.union(chunk_chars)
+         for child in wldoc.parts():
+             child_toc, chunk_counter, chunk_chars, sample = transform_file(
+                 child, chunk_counter, first=False, sample=sample)
+             toc.append(child_toc)
+             chars = chars.union(chunk_chars)
  
          return toc, chunk_counter, chars, sample
  
-     # read metadata from the first file
-     if file_path:
-         if slug:
-             raise ValueError('slug or file_path should be specified, not both')
-         f = open(file_path, 'r')
-         input_xml = etree.parse(f)
-         f.close()
-     else:
-         if not slug:
-             raise ValueError('either slug or file_path should be specified')
-         input_xml = etree.parse(provider[slug])
+     document = deepcopy(wldoc)
+     del wldoc
  
      if flags:
          for flag in flags:
-             input_xml.getroot().set(flag, 'yes')
-     metadata = input_xml.find('.//'+RDFNS('Description'))
-     if metadata is None:
-         raise NoDublinCore('Document has no DublinCore - which is required.')
-     book_info = BookInfo.from_element(input_xml)
-     metadata = etree.ElementTree(metadata)
-     # if output to dir, create the file
-     if output_dir is not None:
-         if make_dir:
-             author = unicode(book_info.author)
-             output_dir = os.path.join(output_dir, author)
-             try:
-                 os.makedirs(output_dir)
-             except OSError:
-                 pass
-         if slug:
-             output_file = open(os.path.join(output_dir, '%s.epub' % slug), 'w')
-         else:
-             output_file = open(os.path.join(output_dir, os.path.splitext(os.path.basename(file_path))[0] + '.epub'), 'w')
+             document.edoc.getroot().set(flag, 'yes')
  
-     opf = xslt(metadata, get_resource('epub/xsltContent.xsl'))
+     opf = xslt(document.book_info.to_etree(), get_resource('epub/xsltContent.xsl'))
      manifest = opf.find('.//' + OPFNS('manifest'))
+     guide = opf.find('.//' + OPFNS('guide'))
      spine = opf.find('.//' + OPFNS('spine'))
  
+     output_file = NamedTemporaryFile(prefix='librarian', suffix='.epub', delete=False)
++
      zip = zipfile.ZipFile(output_file, 'w', zipfile.ZIP_DEFLATED)
  
      # write static elements
                         '<rootfiles><rootfile full-path="OPS/content.opf" ' \
                         'media-type="application/oebps-package+xml" />' \
                         '</rootfiles></container>')
-     zip.write(get_resource('epub/style.css'), os.path.join('OPS', 'style.css'))
 -    zip.write(get_resource('res/wl-logo-small.png'), os.path.join('OPS', 'logo_wolnelektury.png'))
 -    zip.write(get_resource('res/jedenprocent.png'), os.path.join('OPS', 'jedenprocent.png'))
 +    if not flags or 'not-wl' not in flags:
 +        manifest.append(etree.fromstring(
 +            '<item id="logo_wolnelektury" href="logo_wolnelektury.png" media-type="image/png" />'))
++        manifest.append(etree.fromstring(
++            '<item id="jedenprocent" href="jedenprocent.png" media-type="image/png" />'))
 +        zip.write(get_resource('res/wl-logo-small.png'), os.path.join('OPS', 'logo_wolnelektury.png'))
++        zip.write(get_resource('res/jedenprocent.png'), os.path.join('OPS', 'jedenprocent.png'))
++
+     if not style:
+         style = get_resource('epub/style.css')
+     zip.write(style, os.path.join('OPS', 'style.css'))
  
      if cover:
+         if cover is True:
+             cover = WLCover
+         if cover.uses_dc_cover:
+             if document.book_info.cover_by:
+                 document.edoc.getroot().set('data-cover-by', document.book_info.cover_by)
+             if document.book_info.cover_source:
+                 document.edoc.getroot().set('data-cover-source', document.book_info.cover_source)
          cover_file = StringIO()
-         c = cover(book_info.author.readable(), book_info.title)
+         c = cover(document.book_info)
          c.save(cover_file)
          c_name = 'cover.%s' % c.ext()
          zip.writestr(os.path.join('OPS', c_name), cover_file.getvalue())
              '<item id="cover" href="cover.html" media-type="application/xhtml+xml" />'))
          manifest.append(etree.fromstring(
              '<item id="cover-image" href="%s" media-type="%s" />' % (c_name, c.mime_type())))
-         spine.insert(0, etree.fromstring('<itemref idref="cover" />'))
+         spine.insert(0, etree.fromstring('<itemref idref="cover" linear="no" />'))
          opf.getroot()[0].append(etree.fromstring('<meta name="cover" content="cover-image"/>'))
-         opf.getroot().append(etree.fromstring('<guide><reference href="cover.html" type="cover" title="Okładka"/></guide>'))
+         guide.append(etree.fromstring('<reference href="cover.html" type="cover" title="Okładka"/>'))
  
-         for ilustr in input_xml.findall('//ilustr'):
 +    if flags and 'images' in flags:
-         for ilustr in input_xml.findall('//ilustr'):
++        for ilustr in document.edoc.findall('//ilustr'):
 +            src = ilustr.get('src')
 +            mime = ImageCover(src)().mime_type()
 +            zip.write(src, os.path.join('OPS', src))
 +            manifest.append(etree.fromstring(
 +                '<item id="%s" href="%s" media-type="%s" />' % (src, src, mime)))
 +            # get it up to master
 +            after = ilustr
 +            while after.getparent().tag not in ['powiesc', 'opowiadanie', 'liryka_l', 'liryka_lp', 'dramat_wierszowany_l', 'dramat_wierszowany_lp', 'dramat_wspolczesny']:
 +                after = after.getparent()
 +            if not(after is ilustr):
 +                moved = deepcopy(ilustr)
 +                ilustr.tag = 'extra'
 +                ilustr.text = None
 +                moved.tail = None
 +                after.addnext(moved)
 +    else:
++        for ilustr in document.edoc.findall('//ilustr'):
 +            ilustr.tag = 'extra'
  
      annotations = etree.Element('annotations')
  
                                 '"-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">' \
                                 '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" xml:lang="pl" ' \
                                 'version="2005-1"><head></head><docTitle></docTitle><navMap>' \
-                                '<navPoint id="NavPoint-1" playOrder="1"><navLabel>' \
-                                '<text>Strona tytułowa</text></navLabel><content src="title.html" />' \
-                                '</navPoint></navMap></ncx>')
+                                '</navMap></ncx>')
      nav_map = toc_file[-1]
  
-     toc, chunk_counter, chars, sample = transform_file(input_xml, sample=sample)
+     if html_toc:
+         manifest.append(etree.fromstring(
+             '<item id="html_toc" href="toc.html" media-type="application/xhtml+xml" />'))
+         spine.append(etree.fromstring(
+             '<itemref idref="html_toc" />'))
+         guide.append(etree.fromstring('<reference href="toc.html" type="toc" title="Spis treści"/>'))
+     toc, chunk_counter, chars, sample = transform_file(document, sample=sample)
  
-     if not toc.children:
-         toc.add(u"Początek utworu", 1)
-     toc_counter = toc.write_to_xml(nav_map, 2)
+     if len(toc.children) < 2:
+         toc.add(u"Początek utworu", "part1.html")
  
      # Last modifications in container files and EPUB creation
      if len(annotations) > 0:
-         nav_map.append(etree.fromstring(
-             '<navPoint id="NavPoint-%(i)d" playOrder="%(i)d" ><navLabel><text>Przypisy</text>'\
-             '</navLabel><content src="annotations.html" /></navPoint>' % {'i': toc_counter}))
-         toc_counter += 1
+         toc.add("Przypisy", "annotations.html")
          manifest.append(etree.fromstring(
              '<item id="annotations" href="annotations.html" media-type="application/xhtml+xml" />'))
          spine.append(etree.fromstring(
          zip.writestr('OPS/annotations.html', etree.tostring(
                              html_tree, method="html", pretty_print=True))
  
-     nav_map.append(etree.fromstring(
-         '<navPoint id="NavPoint-%(i)d" playOrder="%(i)d" ><navLabel><text>Strona redakcyjna</text>'\
-         '</navLabel><content src="last.html" /></navPoint>' % {'i': toc_counter}))
+     toc.add("Strona redakcyjna", "last.html")
      manifest.append(etree.fromstring(
          '<item id="last" href="last.html" media-type="application/xhtml+xml" />'))
      spine.append(etree.fromstring(
          '<itemref idref="last" />'))
-     stopka = input_xml.find('//stopka')
 -    html_tree = xslt(document.edoc, get_resource('epub/xsltLast.xsl'))
++    stopka = document.edoc.find('//stopka')
 +    if stopka is not None:
 +        stopka.tag = 'stopka_'
 +        replace_by_verse(stopka)
 +        html_tree = xslt(stopka, get_resource('epub/xsltScheme.xsl'))
 +    else:
-         html_tree = xslt(input_xml, get_resource('epub/xsltLast.xsl'))
++        html_tree = xslt(document.edoc, get_resource('epub/xsltLast.xsl'))
      chars.update(used_chars(html_tree.getroot()))
      zip.writestr('OPS/last.html', etree.tostring(
                          html_tree, method="html", pretty_print=True))
  
-     # strip fonts
-     tmpdir = mkdtemp('-librarian-epub')
-     cwd = os.getcwd()
-     os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'font-optimizer'))
-     for fname in 'DejaVuSerif.ttf', 'DejaVuSerif-Bold.ttf', 'DejaVuSerif-Italic.ttf', 'DejaVuSerif-BoldItalic.ttf':
-         optimizer_call = ['perl', 'subset.pl', '--chars', ''.join(chars).encode('utf-8'),
-                           get_resource('fonts/' + fname), os.path.join(tmpdir, fname)]
-         if verbose:
-             print "Running font-optimizer"
-             subprocess.check_call(optimizer_call)
-         else:
-             subprocess.check_call(optimizer_call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-         zip.write(os.path.join(tmpdir, fname), os.path.join('OPS', fname))
-     rmtree(tmpdir)
-     os.chdir(cwd)
+     if not flags or not 'without-fonts' in flags:
+         # strip fonts
+         tmpdir = mkdtemp('-librarian-epub')
+         cwd = os.getcwd()
+         os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'font-optimizer'))
+         for fname in 'DejaVuSerif.ttf', 'DejaVuSerif-Bold.ttf', 'DejaVuSerif-Italic.ttf', 'DejaVuSerif-BoldItalic.ttf':
+             optimizer_call = ['perl', 'subset.pl', '--chars', ''.join(chars).encode('utf-8'),
+                               get_resource('fonts/' + fname), os.path.join(tmpdir, fname)]
+             if verbose:
+                 print "Running font-optimizer"
+                 subprocess.check_call(optimizer_call)
+             else:
+                 subprocess.check_call(optimizer_call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+             zip.write(os.path.join(tmpdir, fname), os.path.join('OPS', fname))
+             manifest.append(etree.fromstring(
+                 '<item id="%s" href="%s" media-type="font/ttf" />' % (fname, fname)))
+         rmtree(tmpdir)
+         os.chdir(cwd)
  
      zip.writestr('OPS/content.opf', etree.tostring(opf, pretty_print=True))
-     contents = []
-     title = node_name(etree.ETXPath('.//'+DCNS('title'))(input_xml)[0])
+     title = document.book_info.title
      attributes = "dtb:uid", "dtb:depth", "dtb:totalPageCount", "dtb:maxPageNumber"
      for st in attributes:
          meta = toc_file.makeelement(NCXNS('meta'))
      toc_file[0][0].set('content', ''.join((title, 'WolneLektury.pl')))
      toc_file[0][1].set('content', str(toc.depth()))
      set_inner_xml(toc_file[1], ''.join(('<text>', title, '</text>')))
+     # write TOC
+     if html_toc:
+         toc.add(u"Spis treści", "toc.html", index=1)
+         zip.writestr('OPS/toc.html', toc.html().encode('utf-8'))
+     toc.write_to_xml(nav_map)
      zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True))
      zip.close()
+     return OutputFile.from_filename(output_file.name)
diff --combined librarian/epub/style.css
@@@ -46,8 -46,7 +46,8 @@@ a img 
  
  #book-text
  {
 -      margin: 2em;
 +      /*margin: 2em;*/
 +    margin: 5px;
        /*margin-right: 9em;*/
  }
  
        text-align: left;
  }
  
- .annotation
+ .annotation-anchor
  {
        font-style: normal;
        font-weight: normal;
        font-size: 0.875em;
- }
- #footnotes .annotation
- {
        display: block;
        float: left;
        width: 2.5em;
        clear: both;
  }
  
- #footnotes div
+ .annotation
  {
      margin: 0;
      margin-top: 1.5em;
  }
  
- #footnotes p
+ .annotation-body
  {
        margin-left: 2.5em;
        font-size: 0.875em;
@@@ -352,13 -347,17 +348,17 @@@ em.author-emphasi
        text-transform: uppercase;
  }
  
p.info
+ .info
  {
        text-align: center;
        margin-bottom: 1em;
  }
+ .info div
+ {
+     text-align: center;
+ }
  
p.info img
+ .info img
  {
        margin: 0;
        margin-left: 2em;
@@@ -371,12 -370,3 +371,12 @@@ p.minor 
  p.footer {
      margin-top: 2em;
  }
 +
 +.ilustr {
 +    margin-top: 1em;
 +    margin-bottom: 1em;
 +}
 +
 +.ilustr img {
 +    max-width: 100%;
 +}
          <item id="toc" href="toc.ncx" media-type="application/x-dtbncx+xml" />
          <item id="style" href="style.css" media-type="text/css" />
          <item id="titlePage" href="title.html" media-type="application/xhtml+xml" />
-         <item id="DejaVuSerif.ttf" href="DejaVuSerif.ttf" media-type="font/ttf" />
-         <item id="DejaVuSerif-Bold.ttf" href="DejaVuSerif-Bold.ttf" media-type="font/ttf" />
-         <item id="DejaVuSerif-BoldItalic.ttf" href="DejaVuSerif-BoldItalic.ttf" media-type="font/ttf" />
-         <item id="DejaVuSerif-Italic.ttf" href="DejaVuSerif-Italic.ttf" media-type="font/ttf" />
 -        <item id="logo_wolnelektury" href="logo_wolnelektury.png" media-type="image/png" />
 -        <item id="jedenprocent" href="jedenprocent.png" media-type="image/png" />
        </manifest>
        <spine toc="toc">
          <itemref idref="titlePage" />
        </spine>
+       <guide>
+         <reference type="text" title="Początek" href="part1.html" />
+       </guide>
      </package>
    </xsl:template>
  
    <xsl:template match="strofa">
      <div class="stanza" xmlns="http://www.w3.org/1999/xhtml">
        <xsl:apply-templates />
-     </div><div xmlns="http://www.w3.org/1999/xhtml">&#160;</div>
+     </div><div class='stanza-spacer' xmlns="http://www.w3.org/1999/xhtml">&#160;</div>
    </xsl:template>
  
    <xsl:template match="wers_normalny">
      <hr class="spacer-line" xmlns="http://www.w3.org/1999/xhtml"></hr>
    </xsl:template>
  
 +  <xsl:template match="ilustr">
 +    <div xmlns="http://www.w3.org/1999/xhtml" class="ilustr">
 +      <img xmlns="http://www.w3.org/1999/xhtml" alt="ilustracja">
 +        <xsl:attribute name="src">
 +          <xsl:value-of select="@src" />
 +        </xsl:attribute>
 +      </img>
 +    </div>
 +  </xsl:template>
 +
 +  <xsl:template match="stopka" />
 +
    <!--===========================================================-->
    <!-- Tagi SPECJALNE -->
    <!--===========================================================-->
    <!--===========================================================-->
  
    <xsl:template match="text()"  >
-     <xsl:value-of select="."/>
+     <xsl:value-of select="." />
    </xsl:template>
  
    <xsl:template match="text()" >
-     <xsl:value-of select="."/>
+     <xsl:value-of select="." />
    </xsl:template>
  
  </xsl:stylesheet>
diff --combined librarian/packagers.py
@@@ -6,8 -6,8 +6,8 @@@
  import os
  from copy import deepcopy
  from lxml import etree
- from librarian import epub, pdf, DirDocProvider, ParseError, cover
- from librarian.dcparser import BookInfo
+ from librarian import pdf, epub, DirDocProvider, ParseError, cover
+ from librarian.parser import WLDocument
  
  
  class Packager(object):
              except:
                  pass
          outfile = os.path.join(output_dir, slug + '.' + cls.ext)
-         cls.converter.transform(provider, file_path=main_input, output_file=outfile,
+         doc = WLDocument.from_file(main_input, provider=provider)
+         output_file = cls.converter.transform(doc,
                  cover=cls.cover, flags=cls.flags)
+         doc.save_output_file(output_file, output_path=outfile)
  
  
      @classmethod
@@@ -60,12 -63,6 +63,12 @@@ class GandalfEpubPackager(EpubPackager)
  class GandalfPdfPackager(PdfPackager):
      cover = cover.GandalfCover
  
 +class ArtaTechEpubPackager(EpubPackager):
 +    cover = cover.ArtaTechCover
 +
 +class ArtaTechPdfPackager(PdfPackager):
 +    cover = cover.ArtaTechCover
 +
  class BookotekaEpubPackager(EpubPackager):
      cover = cover.BookotekaCover
  
@@@ -84,7 -81,6 +87,6 @@@ class VirtualoEpubPackager(Packager)
          """ truncates text to at most `limit' bytes in utf-8 """
          if text is None:
              return text
-         orig_text = text
          if len(text.encode('utf-8')) > limit:
              newlimit = limit - 3
              while len(text.encode('utf-8')) > newlimit:
                  outfile_dir = os.path.join(output_dir, slug)
                  os.makedirs(os.path.join(output_dir, slug))
  
-                 info = BookInfo.from_file(main_input)
+                 doc = WLDocument.from_file(main_input, provider=provider)
+                 info = doc.book_info
  
                  product_elem = deepcopy(product)
                  product_elem[0].text = cls.utf_trunc(slug, 100)
                  product_elem[4][0][1].text = cls.utf_trunc(info.author.last_name, 100)
                  xml.append(product_elem)
  
-                 cover.VirtualoCover(
-                     u' '.join(info.author.first_names + (info.author.last_name,)),
-                     info.title
-                     ).save(os.path.join(outfile_dir, slug+'.jpg'))
+                 cover.VirtualoCover(info).save(os.path.join(outfile_dir, slug+'.jpg'))
                  outfile = os.path.join(outfile_dir, '1.epub')
                  outfile_sample = os.path.join(outfile_dir, '1.sample.epub')
-                 epub.transform(provider, file_path=main_input, output_file=outfile)
-                 epub.transform(provider, file_path=main_input, output_file=outfile_sample, sample=25)
+                 doc.save_output_file(epub.transform(doc),
+                         output_path=outfile)
+                 doc.save_output_file(epub.transform(doc, sample=25), 
+                         output_path=outfile_sample)
          except ParseError, e:
              print '%(file)s:%(name)s:%(message)s' % {
                  'file': main_input,
diff --combined scripts/book2epub
@@@ -7,8 -7,8 +7,9 @@@
  import os.path
  import optparse
  
- from librarian import epub, DirDocProvider, ParseError
+ from librarian import DirDocProvider, ParseError
 +from librarian.cover import ImageCover
+ from librarian.parser import WLDocument
  
  
  if __name__ == '__main__':
  
      parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
          help='print status messages to stdout')
+     parser.add_option('-c', '--with-cover', action='store_true', dest='with_cover', default=False,
+                       help='create default cover')
      parser.add_option('-d', '--make-dir', action='store_true', dest='make_dir', default=False,
                        help='create a directory for author and put the PDF in it')
      parser.add_option('-o', '--output-file', dest='output_file', metavar='FILE',
                        help='specifies the output file')
      parser.add_option('-O', '--output-dir', dest='output_dir', metavar='DIR',
                        help='specifies the directory for output')
-     parser.add_option('-c', '--cover', dest='cover', metavar='FILE',
 +    parser.add_option('-i', '--with-images', action='store_true', dest='images', default=False,
 +                      help='add images with <ilustr src="..."/>')
 +    parser.add_option('-A', '--less-advertising', action='store_true', dest='less_advertising', default=False,
 +                      help='less advertising, for commercial purposes')
 +    parser.add_option('-W', '--not-wl', action='store_true', dest='not_wl', default=False,
 +                      help='not a WolneLektury book')
++    parser.add_option('--cover', dest='cover', metavar='FILE',
 +                      help='specifies the cover file')
  
      options, input_filenames = parser.parse_args()
  
          for main_input in input_filenames:
              if options.verbose:
                  print main_input
              path, fname = os.path.realpath(main_input).rsplit('/', 1)
              provider = DirDocProvider(path)
-             output_dir = output_file = None
-             if options.output_dir:
-                 output_dir = options.output_dir
-             elif options.output_file:
-                 output_file = options.output_file
+             if not (options.output_file or options.output_dir):
+                 output_file = os.path.splitext(main_input)[0] + '.epub'
              else:
-                 output_dir = path
+                 output_file = None
+             doc = WLDocument.from_file(main_input, provider=provider)
 -            epub = doc.as_epub(cover=options.with_cover)
 +
-             cover = None
 +            if options.cover:
 +                cover = ImageCover(options.cover)
++            else:
++                cover = options.with_cover
 +
 +            flags = []
 +            if options.images:
 +                flags.append('images')
 +            if options.less_advertising:
 +                flags.append('less-advertising')
 +            if options.not_wl:
 +                flags.append('not-wl')
 +
-             epub.transform(provider, file_path=main_input, output_dir=output_dir, output_file=output_file, make_dir=options.make_dir,
-                 cover=cover, flags=flags)
++            epub = doc.as_epub(cover=cover, flags=flags)
+             doc.save_output_file(epub,
+                 output_file, options.output_dir, options.make_dir, 'epub')
      except ParseError, e:
          print '%(file)s:%(name)s:%(message)s' % {
              'file': main_input,