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

@@@ -7,7 -7,79 +7,110 @@@ 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'
              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
  
@@@ -272,22 -290,15 +291,15 @@@ def transform(wldoc, verbose=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
  
      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')
  
          '<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))
Simple merge
          <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" />
Simple merge
Simple merge
@@@ -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__':
                        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,