Separate the general from the WL-specific: PDF universal
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 18 Jan 2013 12:28:30 +0000 (13:28 +0100)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Fri, 18 Jan 2013 12:28:30 +0000 (13:28 +0100)
24 files changed:
librarian/__init__.py
librarian/cover.py
librarian/epub.py
librarian/fb2.py
librarian/functions.py
librarian/html.py
librarian/mobi.py
librarian/parser.py
librarian/pdf.py
librarian/pdf/cover_image.sty [new file with mode: 0755]
librarian/pdf/default.sty [new file with mode: 0755]
librarian/pdf/wl.cls [changed mode: 0644->0755]
librarian/pdf/wl2tex.xslt [changed mode: 0644->0755]
librarian/res/styles/wolnelektury/pdf/wolnelektury.sty [new file with mode: 0755]
librarian/styles/__init__.py [new file with mode: 0755]
librarian/styles/wolnelektury/__init__.py [new file with mode: 0644]
librarian/styles/wolnelektury/cover.py [new file with mode: 0644]
librarian/styles/wolnelektury/partners/__init__.py [new file with mode: 0755]
librarian/styles/wolnelektury/partners/cover.py [new file with mode: 0755]
librarian/styles/wolnelektury/pdf.py [new file with mode: 0644]
librarian/text.py
scripts/book2cover
tests/test_html.py
tests/test_iofile.py [new file with mode: 0644]

index c46d5d1..3b811d3 100644 (file)
@@ -205,32 +205,35 @@ def get_resource(path):
     return os.path.join(os.path.dirname(__file__), path)
 
 
-class OutputFile(object):
-    """Represents a file returned by one of the converters."""
-
+class IOFile(object):
+    """ Represents a file fed as input or returned as a result. """
     _string = None
     _filename = None
+    _filename_tmp = False
+
+    def __init__(self, attachments=None):
+        self.attachments = attachments or {}
 
     def __del__(self):
-        if self._filename:
+        if self._filename_tmp:
             os.unlink(self._filename)
 
     def __nonzero__(self):
         return self._string is not None or self._filename is not None
 
     @classmethod
-    def from_string(cls, string):
+    def from_string(cls, string, *args, **kwargs):
         """Converter returns contents of a file as a string."""
 
-        instance = cls()
+        instance = cls(*args, **kwargs)
         instance._string = string
         return instance
 
     @classmethod
-    def from_filename(cls, filename):
+    def from_filename(cls, filename, *args, **kwargs):
         """Converter returns contents of a file as a named file."""
 
-        instance = cls()
+        instance = cls(*args, **kwargs)
         instance._filename = filename
         return instance
 
@@ -263,6 +266,7 @@ class OutputFile(object):
             temp.write(self._string)
             temp.close()
             self._filename = temp.name
+            self._filename_tmp = True
             return self._filename
         else:
             return None
@@ -275,6 +279,23 @@ class OutputFile(object):
             os.makedirs(dirname)
         shutil.copy(self.get_filename(), path)
 
+    def dump_to(self, path, directory=None):
+        """ Path should be name for main file. """
+        self.save_as(path)
+        dirname = os.path.dirname(os.path.abspath(path))
+        for filename, attachment in self.attachments.items():
+            attachment.save_as(os.path.join(dirname, filename))
+
+
+class Format(object):
+    """ Generic format class. """
+    def __init__(self, wldoc, **kwargs):
+        self.wldoc = wldoc
+        self.customization = kwargs
+
+    def build(self):
+        raise NotImplementedError
+
 
 class URLOpener(urllib.FancyURLopener):
     version = 'FNP Librarian (http://github.com/fnp/librarian)'
index be34e26..b53de30 100644 (file)
@@ -6,7 +6,7 @@
 import re
 import Image, ImageFont, ImageDraw, ImageFilter
 from StringIO import StringIO
-from librarian import get_resource, OutputFile, URLOpener
+from librarian import get_resource, IOFile
 
 
 class TextBox(object):
@@ -186,200 +186,9 @@ class Cover(object):
     def output_file(self, *args, **kwargs):
         imgstr = StringIO()
         self.save(imgstr, *args, **kwargs)
-        return OutputFile.from_string(imgstr.getvalue())
+        return IOFile.from_string(imgstr.getvalue())
 
-
-class WLCover(Cover):
-    """Default Wolne Lektury cover generator."""
-    width = 600
-    height = 833
-    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'
-    default_background = get_resource('res/cover.png')
-    format = 'JPEG'
-
-    epoch_colors = {
-        u'Starożytność': '#9e3610',
-        u'Średniowiecze': '#564c09',
-        u'Renesans': '#8ca629',
-        u'Barok': '#a6820a',
-        u'Oświecenie': '#f2802e',
-        u'Romantyzm': '#db4b16',
-        u'Pozytywizm': '#961060',
-        u'Modernizm': '#7784e0',
-        u'Dwudziestolecie międzywojenne': '#3044cf',
-        u'Współczesność': '#06393d',
-    }
-
-    def __init__(self, book_info, format=None, image_cache=None):
-        super(WLCover, self).__init__(book_info, format=format)
-        self.kind = book_info.kind
-        self.epoch = book_info.epoch
-        if book_info.cover_url:
-            url = book_info.cover_url
-            bg_src = None
-            if image_cache:
-                from urllib import quote
-                try:
-                    bg_src = URLOpener().open(image_cache + quote(url, safe=""))
-                except:
-                    pass
-            if bg_src is None:
-                bg_src = URLOpener().open(url)
-            self.background_img = StringIO(bg_src.read())
-            bg_src.close()
-        else:
-            self.background_img = self.default_background
-
-    def pretty_author(self):
-        return self.author.upper()
-
-    def image(self):
-        img = Image.new('RGB', (self.width, self.height), self.background_color)
-        draw = ImageDraw.Draw(img)
-
-        if self.epoch in self.epoch_colors:
-            epoch_color = self.epoch_colors[self.epoch]
-        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
-    height = 730
-    author_top = 73
-    title_top = 73
-    logo_bottom = 25
-    logo_width = 250
-
-
-class PrestigioCover(Cover):
-    width = 580
-    height = 783
-    background_img = get_resource('res/cover-prestigio.png')
-
-    author_top = 446
-    author_margin_left = 118
-    author_margin_right = 62
-    author_lineskip = 60
-    author_color = '#fff'
-    author_shadow = '#000'
-    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Italic.ttf'), 50)
-
-    title_top = 0
-    title_margin_left = 118
-    title_margin_right = 62
-    title_lineskip = 60
-    title_color = '#fff'
-    title_shadow = '#000'
-    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Italic.ttf'), 50)
-
-    def pretty_title(self):
-        return u"„%s”" % self.title
-
-
-class BookotekaCover(Cover):
-    width = 2140
-    height = 2733
-    background_img = get_resource('res/cover-bookoteka.png')
-
-    author_top = 480
-    author_margin_left = 307
-    author_margin_right = 233
-    author_lineskip = 156
-    author_color = '#d9d919'
-    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 130)
-
-    title_top = 400
-    title_margin_left = 307
-    title_margin_right = 233
-    title_lineskip = 168
-    title_color = '#d9d919'
-    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 140)
-
-    format = 'PNG'
-
-
-class GandalfCover(Cover):
-    width = 600
-    height = 730
-    background_img = get_resource('res/cover-gandalf.png')
-    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 30)
-    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 40)
-    logo_bottom = 25
-    logo_width = 250
-    format = 'PNG'
+    def for_pdf(self):
+        return IOFile.from_filename(get_resource('pdf/cover_image.sty'), {
+            'cover.png': self.output_file(),
+        })
index 10922d4..0c7a16c 100644 (file)
@@ -16,7 +16,7 @@ import zipfile
 from tempfile import mkdtemp, NamedTemporaryFile
 from shutil import rmtree
 
-from librarian import RDFNS, WLNS, NCXNS, OPFNS, XHTMLNS, OutputFile
+from librarian import RDFNS, WLNS, NCXNS, OPFNS, XHTMLNS, IOFile
 from librarian.cover import WLCover
 
 from librarian import functions, get_resource
@@ -560,4 +560,4 @@ def transform(wldoc, verbose=False,
     zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True))
     zip.close()
 
-    return OutputFile.from_filename(output_file.name)
+    return IOFile.from_filename(output_file.name)
index d979566..1e110f5 100644 (file)
@@ -7,7 +7,7 @@ import os.path
 from copy import deepcopy
 from lxml import etree
 
-from librarian import functions, OutputFile
+from librarian import functions, IOFile
 from .epub import replace_by_verse
 
 
@@ -58,6 +58,6 @@ def transform(wldoc, verbose=False,
 
     result = document.transform(style)
 
-    return OutputFile.from_string(unicode(result).encode('utf-8'))
+    return IOFile.from_string(unicode(result).encode('utf-8'))
 
 # vim:et
index 523b3d5..9490cbb 100644 (file)
@@ -104,3 +104,17 @@ def reg_texcommand():
     _register_function(texcommand)
 
 
+def reg_get(format_):
+    def get(context, *args):
+        obj = format_
+        for arg in args:
+            if hasattr(obj, arg):
+                obj = getattr(obj, arg)
+            else:
+                try:
+                    obj = obj[arg]
+                except (TypeError, KeyError), e:
+                    # Just raise proper AttributeError.
+                    getattr(obj, arg)
+        return obj
+    _register_function(get)
index c1a5e5b..985970a 100644 (file)
@@ -8,7 +8,7 @@ import cStringIO
 import copy
 
 from lxml import etree
-from librarian import XHTMLNS, ParseError, OutputFile
+from librarian import XHTMLNS, ParseError, IOFile
 from librarian import functions
 
 from lxml.etree import XMLSyntaxError, XSLTApplyError
@@ -59,7 +59,7 @@ def transform(wldoc, stylesheet='legacy', options=None, flags=None):
             add_anchors(result.getroot())
             add_table_of_contents(result.getroot())
 
-            return OutputFile.from_string(etree.tostring(result, method='html',
+            return IOFile.from_string(etree.tostring(result, method='html',
                 xml_declaration=False, pretty_print=True, encoding='utf-8'))
         else:
             return None
index d98b838..9558452 100644 (file)
@@ -8,7 +8,7 @@ import os
 import subprocess
 from tempfile import NamedTemporaryFile
 
-from librarian import OutputFile
+from librarian import IOFile
 from librarian.cover import WLCover
 from librarian import get_resource
 
@@ -57,4 +57,4 @@ def transform(wldoc, verbose=False,
     subprocess.check_call(['ebook-convert', epub.get_filename(), output_file.name,
             '--no-inline-toc', '--cover=%s' % cover_file.name], **kwargs)
     os.unlink(cover_file.name)
-    return OutputFile.from_filename(output_file.name)
\ No newline at end of file
+    return IOFile.from_filename(output_file.name)
\ No newline at end of file
index a9e8c65..d330a72 100644 (file)
@@ -4,8 +4,8 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 from librarian import ValidationError, NoDublinCore,  ParseError, NoProvider
-from librarian import RDFNS
-from librarian.cover import WLCover
+from librarian import RDFNS, IOFile
+from librarian.styles.wolnelektury.cover import WLCover
 from librarian import dcparser
 
 from xml.parsers.expat import ExpatError
@@ -20,58 +20,68 @@ class WLDocument(object):
     LINE_SWAP_EXPR = re.compile(r'/\s', re.MULTILINE | re.UNICODE)
     provider = None
 
-    def __init__(self, edoc, parse_dublincore=True, provider=None, 
-                    strict=False, meta_fallbacks=None):
-        self.edoc = edoc
-        self.provider = provider
-
-        root_elem = edoc.getroot()
-
-        dc_path = './/' + RDFNS('RDF')
+    _edoc = None
+    @property
+    def edoc(self):
+        if self._edoc is None:
+            data = self.source.get_string()
+            if not isinstance(data, unicode):
+                data = data.decode('utf-8')
+            data = data.replace(u'\ufeff', '')
+            try:
+                parser = etree.XMLParser(remove_blank_text=False)
+                self._edoc = etree.parse(StringIO(data.encode('utf-8')), parser)
+            except (ExpatError, XMLSyntaxError, XSLTApplyError), e:
+                raise ParseError(e)
+        return self._edoc
+
+    _rdf_elem = None
+    @property
+    def rdf_elem(self):
+        if self._rdf_elem is None:
+            dc_path = './/' + RDFNS('RDF')
+            self._rdf_elem = self.edoc.getroot().find(dc_path)
+            if self._rdf_elem is None:
+                raise NoDublinCore('Document has no DublinCore - which is required.')
+        return self._rdf_elem
 
-        if root_elem.tag != 'utwor':
+    _book_info = None
+    @property
+    def book_info(self):
+        if not self.parse_dublincore:
+            return None
+        if self._book_info is None:
+            self._book_info = dcparser.BookInfo.from_element(
+                    self.rdf_elem, fallbacks=self.meta_fallbacks, strict=self.strict)
+        return self._book_info
+
+    def __init__(self, iofile, provider=None, 
+            parse_dublincore=True, # shouldn't it be in a subclass?
+            strict=False, # ?
+            meta_fallbacks=None # ?
+            ):
+        self.source = iofile
+        self.provider = provider
+        self.parse_dublincore = parse_dublincore
+        self.strict = strict
+        self.meta_fallbacks = meta_fallbacks
+        if self.edoc.getroot().tag != 'utwor':
             raise ValidationError("Invalid root element. Found '%s', should be 'utwor'" % root_elem.tag)
-
         if parse_dublincore:
-            self.rdf_elem = root_elem.find(dc_path)
-
-            if self.rdf_elem is None:
-                raise NoDublinCore('Document has no DublinCore - which is required.')
-
-            self.book_info = dcparser.BookInfo.from_element(
-                    self.rdf_elem, fallbacks=meta_fallbacks, strict=strict)
-        else:
-            self.book_info = None
+            self.book_info
 
     @classmethod
     def from_string(cls, xml, *args, **kwargs):
-        return cls.from_file(StringIO(xml), *args, **kwargs)
+        return cls(IOFile.from_string(xml), *args, **kwargs)
 
     @classmethod
     def from_file(cls, xmlfile, *args, **kwargs):
-
-        # first, prepare for parsing
         if isinstance(xmlfile, basestring):
-            file = open(xmlfile, 'rb')
-            try:
-                data = file.read()
-            finally:
-                file.close()
+            iofile = IOFile.from_filename(xmlfile)
         else:
-            data = xmlfile.read()
-
-        if not isinstance(data, unicode):
-            data = data.decode('utf-8')
+            iofile = IOFile.from_file(xmlfile)
+        return cls(iofile, *args, **kwargs)
 
-        data = data.replace(u'\ufeff', '')
-
-        try:
-            parser = etree.XMLParser(remove_blank_text=False)
-            tree = etree.parse(StringIO(data.encode('utf-8')), parser)
-
-            return cls(tree, *args, **kwargs)
-        except (ExpatError, XMLSyntaxError, XSLTApplyError), e:
-            raise ParseError(e)
 
     def swap_endlines(self):
         """Converts line breaks in stanzas into <br/> tags."""
@@ -95,10 +105,10 @@ class WLDocument(object):
                 elem.text = chunks.pop(0)
 
     def parts(self):
-        if self.provider is None:
-            raise NoProvider('No document provider supplied.')
         if self.book_info is None:
             raise NoDublinCore('No Dublin Core in document.')
+        if self.book_info.parts and self.provider is None:
+            raise NoProvider('No document provider supplied.')
         for part_uri in self.book_info.parts:
             yield self.from_file(self.provider.by_uri(part_uri),
                     provider=self.provider)
index 9fb92b1..9308704 100644 (file)
@@ -25,9 +25,8 @@ from lxml.etree import XMLSyntaxError, XSLTApplyError
 
 from librarian.dcparser import Person
 from librarian.parser import WLDocument
-from librarian import ParseError, DCNS, get_resource, OutputFile
+from librarian import ParseError, DCNS, get_resource, IOFile, Format
 from librarian import functions
-from librarian.cover import WLCover
 
 
 functions.reg_substitute_entities()
@@ -40,15 +39,6 @@ STYLESHEETS = {
     'wl2tex': 'pdf/wl2tex.xslt',
 }
 
-#CUSTOMIZATIONS = [
-#    'nofootnotes',
-#    'nothemes',
-#    'defaultleading',
-#    'onehalfleading',
-#    'doubleleading',
-#    'nowlfont',
-#    ]
-
 def insert_tags(doc, split_re, tagname, exclude=None):
     """ inserts <tagname> for every occurence of `split_re' in text nodes in the `doc' tree
 
@@ -83,7 +73,7 @@ def substitute_hyphens(doc):
     insert_tags(doc,
                 re.compile("(?<=[^-\s])-(?=[^-\s])"),
                 "dywiz",
-                exclude=[DCNS("identifier.url"), DCNS("rights.license")]
+                exclude=[DCNS("identifier.url"), DCNS("rights.license"), 'www']
                 )
 
 
@@ -183,52 +173,65 @@ def package_available(package, args='', verbose=False):
     return p == 0
 
 
-def transform(wldoc, verbose=False, save_tex=None, morefloats=None,
-              cover=None, flags=None, customizations=None):
-    """ produces a PDF file with XeLaTeX
+def load_including_children(wldoc=None, provider=None, uri=None):
+    """ Makes one big xml file with children inserted at end.
+    
+    Either wldoc or provider and URI must be provided.
+    """
+
+    if uri and provider:
+        f = provider.by_uri(uri)
+        text = f.read().decode('utf-8')
+        f.close()
+    elif wldoc is not None:
+        text = etree.tostring(wldoc.edoc, encoding=unicode)
+        provider = wldoc.provider
+    else:
+        raise ValueError('Neither a WLDocument, nor provider and URI were provided.')
+
+    text = re.sub(ur"([\u0400-\u04ff]+)", ur"<alien>\1</alien>", text)
+
+    document = WLDocument.from_string(text,
+                parse_dublincore=True, provider=provider)
+    document.swap_endlines()
+
+    for child_uri in document.book_info.parts:
+        child = load_including_children(provider=provider, uri=child_uri)
+        document.edoc.getroot().append(child.edoc.getroot())
+    return document
+
+
+class PDFFormat(Format):
+    """ Base PDF format.
+    
+    Available customization:
+        nofootnotes: Doesn't do footnotes.
+        nothemes: Doesn't do themes.
+        defaultleading: Default leading.
+        onehalfleading: Bigger leading.
+        doubleleading: Big leading.
+        nowlfont: Uses standard TeX font instead of JUnicodeWL.
 
-    wldoc: a WLDocument
-    verbose: prints all output from LaTeX
-    save_tex: path to save the intermediary LaTeX file to
-    morefloats (old/new/none): force specific morefloats
-    cover: a cover.Cover factory or True for default
-    flags: less-advertising,
-    customizations: user requested customizations regarding various formatting parameters (passed to wl LaTeX class)
     """
 
-    # Parse XSLT
-    try:
-        book_info = wldoc.book_info
-        document = load_including_children(wldoc)
-        root = document.edoc.getroot()
+    cover_class = None
+    tex_passes = 1
+    style = get_resource('pdf/default.sty')
+    cover = None
+
+    @property
+    def has_cover(self):
+        """ For use in XSLT. """
+        return self.cover is not None
 
-        if cover:
-            if cover is True:
-                cover = WLCover
-            bound_cover = cover(book_info)
-            root.set('data-cover-width', str(bound_cover.width))
-            root.set('data-cover-height', str(bound_cover.height))
-            if bound_cover.uses_dc_cover:
-                if book_info.cover_by:
-                    root.set('data-cover-by', book_info.cover_by)
-                if book_info.cover_source:
-                    root.set('data-cover-source',
-                            book_info.cover_source)
-        if flags:
-            for flag in flags:
-                root.set('flag-' + flag, 'yes')
-
-        # check for LaTeX packages
-        if morefloats:
-            root.set('morefloats', morefloats.lower())
-        elif package_available('morefloats', 'maxfloats=19'):
-            root.set('morefloats', 'new')
-
-        # add customizations
-        if customizations is not None:
-            root.set('customizations', u','.join(customizations))
-
-        # add editors info
+    @property
+    def customization_str(self):
+        """ For use in XSLT. """
+        return u','.join(k for k, v in self.customization.items() if v)
+
+    def get_document(self):
+        document = load_including_children(self.wldoc)
+        root = document.edoc.getroot()
         root.set('editors', u', '.join(sorted(
             editor.readable() for editor in document.editors())))
 
@@ -238,45 +241,51 @@ def transform(wldoc, verbose=False, save_tex=None, morefloats=None,
         parse_creator(document.edoc)
         substitute_hyphens(document.edoc)
         fix_hanging(document.edoc)
+        return document
 
-        # wl -> TeXML
+    def get_texml(self):
         style_filename = get_stylesheet("wl2tex")
-        style = etree.parse(style_filename)
-
-        texml = document.transform(style)
-
-        # TeXML -> LaTeX
+        functions.reg_get(self)
+        try:
+            style = etree.parse(style_filename)
+            texml = self.get_document().transform(style)
+            return texml
+        except (XMLSyntaxError, XSLTApplyError), e:
+            raise ParseError(e)
+
+    def get_tex_dir(self):
+        texml = self.get_texml()
         temp = mkdtemp('-wl2pdf')
-
-        if cover:
-            with open(os.path.join(temp, 'cover.png'), 'w') as f:
-                bound_cover.save(f)
-
-        del document # no longer needed large object :)
-
+        # Save TeX file
         tex_path = os.path.join(temp, 'doc.tex')
-        fout = open(tex_path, 'w')
-        process(StringIO(texml), fout, 'utf-8')
-        fout.close()
-        del texml
-
-        if save_tex:
-            shutil.copy(tex_path, save_tex)
-
-        # LaTeX -> PDF
+        with open(tex_path, 'w') as fout:
+            process(StringIO(texml), fout, 'utf-8')
+        if self.save_tex:
+            shutil.copy(tex_path, self.save_tex)
+        # Copy style
         shutil.copy(get_resource('pdf/wl.cls'), temp)
-        shutil.copy(get_resource('res/wl-logo.png'), temp)
-
+        shutil.copy(self.style, os.path.join(temp, 'style.sty'))
+        # Save attachments
+        if self.cover:
+            self.cover.for_pdf().dump_to(os.path.join(temp, 'makecover.sty'))
+        return temp
+
+    def get_pdf(self):
+        temp = self.get_tex_dir()
+        tex_path = os.path.join(temp, 'doc.tex')
         try:
             cwd = os.getcwd()
         except OSError:
             cwd = None
         os.chdir(temp)
 
-        if verbose:
-            p = call(['xelatex', tex_path])
+        if self.verbose:
+            for i in range(self.tex_passes):
+                p = call(['xelatex', tex_path])
         else:
-            p = call(['xelatex', '-interaction=batchmode', tex_path], stdout=PIPE, stderr=PIPE)
+            for i in range(self.tex_passes):
+                p = call(['xelatex', '-interaction=batchmode', tex_path],
+                            stdout=PIPE, stderr=PIPE)
         if p:
             raise ParseError("Error parsing .tex file")
 
@@ -287,35 +296,20 @@ def transform(wldoc, verbose=False, save_tex=None, morefloats=None,
         pdf_path = os.path.join(temp, 'doc.pdf')
         shutil.move(pdf_path, output_file.name)
         shutil.rmtree(temp)
-        return OutputFile.from_filename(output_file.name)
-
-    except (XMLSyntaxError, XSLTApplyError), e:
-        raise ParseError(e)
-
-
-def load_including_children(wldoc=None, provider=None, uri=None):
-    """ Makes one big xml file with children inserted at end.
-    
-    Either wldoc or provider and URI must be provided.
-    """
-
-    if uri and provider:
-        f = provider.by_uri(uri)
-        text = f.read().decode('utf-8')
-        f.close()
-    elif wldoc is not None:
-        text = etree.tostring(wldoc.edoc, encoding=unicode)
-        provider = wldoc.provider
-    else:
-        raise ValueError('Neither a WLDocument, nor provider and URI were provided.')
-
-    text = re.sub(ur"([\u0400-\u04ff]+)", ur"<alien>\1</alien>", text)
-
-    document = WLDocument.from_string(text,
-                parse_dublincore=True, provider=provider)
-    document.swap_endlines()
-
-    for child_uri in document.book_info.parts:
-        child = load_including_children(provider=provider, uri=child_uri)
-        document.edoc.getroot().append(child.edoc.getroot())
-    return document
+        return IOFile.from_filename(output_file.name)
+
+    def build(self, verbose=False, save_tex=None, morefloats=None):
+        """ morefloats: new/old/none
+        """
+        self.verbose = verbose
+        self.save_tex = save_tex
+        
+        if morefloats is None and package_available('morefloats', 'maxfloats=19'):
+            morefloats = 'new'
+        self.morefloats = morefloats
+
+        book_info = self.wldoc.book_info
+        if self.cover_class:
+            self.cover = self.cover_class(book_info)
+
+        return self.get_pdf()
diff --git a/librarian/pdf/cover_image.sty b/librarian/pdf/cover_image.sty
new file mode 100755 (executable)
index 0000000..4ed4eed
--- /dev/null
@@ -0,0 +1,27 @@
+\newcommand{\makecover}[2]{
+        \pdfpagewidth=#1
+        \pdfpageheight=#2
+
+        \thispagestyle{empty}
+        \newlength{\PictHOffset}
+        \newlength{\PictVOffset}
+        \setlength{\PictHOffset}{1in}
+        \addtolength{\PictHOffset}{\hoffset}
+        \addtolength{\PictHOffset}{\oddsidemargin}
+
+        \setlength{\PictVOffset}{1in}
+        \addtolength{\PictVOffset}{\voffset}
+        \addtolength{\PictVOffset}{\topmargin}
+        \addtolength{\PictVOffset}{\headheight}
+        \addtolength{\PictVOffset}{\headsep}
+        \addtolength{\PictVOffset}{\topskip}
+        \addtolength{\PictVOffset}{-\pdfpageheight}
+
+        \noindent\hspace*{-\PictHOffset}%
+        \raisebox{\PictVOffset}[0pt][0pt]{\makebox[0pt][l]{%
+            \includegraphics[height=\pdfpageheight,width=\pdfpagewidth]{cover.png}}}
+        \clearpage
+
+        \setlength{\pdfpagewidth}{210mm}
+        \setlength{\pdfpageheight}{297mm}
+}
diff --git a/librarian/pdf/default.sty b/librarian/pdf/default.sty
new file mode 100755 (executable)
index 0000000..40b867d
--- /dev/null
@@ -0,0 +1,33 @@
+\usepackage[MeX]{polski}
+
+\newcommand{\rightsinfostr}[2][] {
+    \ifx&#1&%
+        #2
+    \else
+        Ten utwór jest udostępniony na licencji
+        \href{#1}{#2}.
+    \fi
+}
+
+
+\renewcommand{\maketitle}{}
+
+\newcommand{\editorialsection}{
+  \begin{figure}[b!]
+  {
+    \footnotesize
+    \color{theme}
+    \noindent \rule{\linewidth}{0.4pt}
+
+    \rightsinfo
+    \vspace{.6em}
+
+    \editors
+
+    \vspace{.6em}
+    \coverby
+
+    \color{black}
+  }
+  \end{figure}
+}
old mode 100644 (file)
new mode 100755 (executable)
index c387b03..53da8d5
@@ -66,8 +66,6 @@
 
 \usepackage{trace}
 
-\usepackage[MeX]{polski}
-
 \usepackage[xetex]{graphicx}
 \usepackage{fontspec}
 \usepackage{xunicode}
@@ -75,6 +73,7 @@
 
 \usepackage[overload]{textcase}
 \usepackage{scalefnt}
+% TODO: link color is a style thing
 \usepackage[colorlinks=true,linkcolor=black,setpagesize=false,urlcolor=black,xetex]{hyperref}
 
 \ifenablewlfont
@@ -168,91 +167,6 @@ Letters={SmallCaps,UppercaseSmallCaps}
 \usebox{\ximagebox}%
 \raisebox{0pt}[0pt][0pt]{\makebox[0pt][r]{\usebox{\xglyphbox}}}}
 
-\newcommand{\makecover}[2]{
-        \pdfpagewidth=#1
-        \pdfpageheight=#2
-
-        \thispagestyle{empty}
-        \newlength{\PictHOffset}
-        \newlength{\PictVOffset}
-        \setlength{\PictHOffset}{1in}
-        \addtolength{\PictHOffset}{\hoffset}
-        \addtolength{\PictHOffset}{\oddsidemargin}
-
-        \setlength{\PictVOffset}{1in}
-        \addtolength{\PictVOffset}{\voffset}
-        \addtolength{\PictVOffset}{\topmargin}
-        \addtolength{\PictVOffset}{\headheight}
-        \addtolength{\PictVOffset}{\headsep}
-        \addtolength{\PictVOffset}{\topskip}
-        \addtolength{\PictVOffset}{-\pdfpageheight}
-
-        \noindent\hspace*{-\PictHOffset}%
-        \raisebox{\PictVOffset}[0pt][0pt]{\makebox[0pt][l]{%
-            \includegraphics[height=\pdfpageheight,width=\pdfpagewidth]{cover.png}}}
-        \clearpage
-
-        \setlength{\pdfpagewidth}{210mm}
-        \setlength{\pdfpageheight}{297mm}
-}
-
-
-\renewcommand{\maketitle}{
-    {
-    \thispagestyle{empty}
-    \footnotesize
-    \color{theme}
-
-    \noindent \begin{minipage}[t]{.35\textwidth}\vspace{0pt}
-        \href{http://www.wolnelektury.pl}{\xbox{\includegraphics[width=\textwidth]{wl-logo.png}}}
-    \end{minipage}
-    \begin{minipage}[t]{.65\textwidth}\vspace{0pt}
-
-    \ifflaglessadvertising
-    \else
-        \href{\bookurl}{Ta lektura}, podobnie jak tysiące innych, jest dostępna on-line na stronie
-        \href{http://www.wolnelektury.pl/}{wolnelektury.pl}.
-        \vspace{.5em}
-    \fi
-
-    Utwór opracowany został w ramach projektu \href{http://www.wolnelektury.pl/}{Wolne Lektury}
-    przez \href{http://nowoczesnapolska.org.pl}{fundację Nowoczesna Polska}.
-
-    \end{minipage}
-    \noindent \rule{\linewidth}{0.4pt}
-
-    \vspace{.6em}
-    \color{black}
-    }
-}
-
-\newcommand{\editorialsection}{
-  \begin{figure}[b!]
-  {
-    \footnotesize
-    \color{theme}
-    \noindent \rule{\linewidth}{0.4pt}
-
-    \rightsinfo
-    \vspace{.6em}
-
-    Źródło: \href{\bookurl}{\bookurl}
-
-    \vspace{.6em}
-    \sourceinfo
-
-    \description
-    \vspace{.6em}
-
-    \editors
-
-    \vspace{.6em}
-    \coverby
-
-    \color{black}
-  }
-  \end{figure}
-}
 
 
 \newcommand{\typosubsubsection}[1]{%
@@ -303,6 +217,7 @@ Letters={Uppercase}
 }
 
 \newcommand{\translator}[1]{%
+% TODO: l10n is a style thing
 \subsection*{\typosubsubsection{tłum. #1}}%
 }
 
@@ -430,7 +345,7 @@ Letters={Uppercase}
 }
 
 \ifshowfootnotes
-  \newcommand{\pa}[1]{\NoCaseChange{\footnote{#1 [przypis autorski]}}}
+  \newcommand{\pa}[1]{\NoCaseChange{\footnote{#1}}}
   \newcommand{\pe}[1]{\NoCaseChange{\footnote{#1}}}
   \newcommand{\pr}[1]{\NoCaseChange{\footnote{#1}}}
   \newcommand{\pt}[1]{\NoCaseChange{\footnote{#1}}}
old mode 100644 (file)
new mode 100755 (executable)
index 909cf4b..f36172d
 <xsl:template match="utwor">
     <TeXML xmlns="http://getfo.sourceforge.net/texml/ns1">
         <TeXML escape="0">
-        \documentclass[<xsl:value-of select="@customizations"/>]{wl}
-
-        <!-- flags and values set on root -->
-
-        \newif\ifflaglessadvertising
-        <xsl:for-each select="@*[starts-with(name(), 'flag-')]">
-            <cmd>
-                <xsl:attribute name="name"><xsl:value-of select="wl:texcommand(name())" />true</xsl:attribute>
-            </cmd>
-        </xsl:for-each>
-
-        <xsl:for-each select="@*[starts-with(name(), 'data-')]">
-            <TeXML escape="0">
-                \def\<xsl:value-of select="wl:texcommand(name())" />{<TeXML escape="1"><xsl:value-of select="."/></TeXML>}
-            </TeXML>
-        </xsl:for-each>
-        </TeXML>
+        \documentclass[<xsl:value-of select="wl:get('customization_str')"/>]{wl}
+        \usepackage{style}
+        <xsl:if test="wl:get('has_cover')">
+            \usepackage{makecover}
+        </xsl:if>
 
         <xsl:choose>
-            <xsl:when test="@morefloats = 'new'">
+            <xsl:when test="wl:get('morefloats') = 'new'">
                 <TeXML escape="0">
                     \usepackage[maxfloats=64]{morefloats}
                 </TeXML>
             </xsl:when>
-            <xsl:when test="@morefloats = 'old'">
+            <xsl:when test="wl:get('morefloats') = 'old'">
                 <TeXML escape="0">
                     \usepackage{morefloats}
                 </TeXML>
             </xsl:when>
-            <xsl:when test="@morefloats = 'none'" />
+            <xsl:when test="wl:get('morefloats') = 'none'" />
             <xsl:otherwise>
                 <TeXML escape="0">
                     \IfFileExists{morefloats.sty}{
@@ -58,9 +46,9 @@
         <xsl:apply-templates select="powiesc|opowiadanie|liryka_l|liryka_lp|dramat_wierszowany_l|dramat_wierszowany_lp|dramat_wspolczesny" mode='titlepage' />
 
         <env name="document">
-            <xsl:if test="@data-cover-width">
+            <xsl:if test="wl:get('has_cover')">
                 <cmd name="makecover">
-                    <parm><xsl:value-of select="210 * @data-cover-width div @data-cover-height" />mm</parm>
+                    <parm><xsl:value-of select="210 * wl:get('cover', 'width') div wl:get('cover', 'height')" />mm</parm>
                     <parm>210mm</parm>
                 </cmd>
             </xsl:if>
 
             <TeXML escape="0">
                 \def\coverby{
-                <xsl:if test="@data-cover-by">Okładka na podstawie: 
+                <TeXML escape="1">
+                <xsl:if test="wl:get('has_cover') and wl:get('wldoc', 'book_info', 'cover_by')">
+                    <!-- FIXME: should be stylable -->
+                    Okładka na podstawie: 
                     <xsl:choose>
-                    <xsl:when test="@data-cover-source">
-                        \href{\datacoversource}{\datacoverby}
+                    <xsl:when test="wl:get('wldoc', 'book_info', 'cover_source')">
+                        <cmd name="href"><parm>
+                            <xsl:value-of select="wl:get('wldoc', 'book_info', 'cover_source')" />
+                        </parm><parm>
+                            <xsl:value-of select="wl:get('wldoc', 'book_info', 'cover_by')" />
+                        </parm></cmd>
                     </xsl:when>
                     <xsl:otherwise>
-                        \datacoverby{}
+                        <xsl:value-of select="wl:get('wldoc', 'book_info', 'cover_by')" />
                     </xsl:otherwise>
                     </xsl:choose>
                 </xsl:if>
+                </TeXML>
                 }
-                \def\editors{<xsl:call-template name="editors" />}
+                \def\editors{<TeXML escape="1"><xsl:call-template name="editors" /></TeXML>}
             </TeXML>
 
             <cmd name="editorialsection" />
 
         </env>
+        </TeXML>
     </TeXML>
 </xsl:template>
 
 
 <xsl:template match="rdf:RDF" mode="titlepage">
     <TeXML escape="0">
-        \def\authors{<xsl:call-template name="authors" />}
+        \def\authors{<TeXML escape="1"><xsl:call-template name="authors" /></TeXML>}
         \author{\authors}
-        \title{<xsl:apply-templates select=".//dc:title" mode="inline" />}
-        \def\translatorsline{<xsl:call-template name="translators" />}
-
-        \def\bookurl{<xsl:value-of select=".//dc:identifier.url" />}
-
-        \def\rightsinfo{Ten utwór nie jest chroniony prawem autorskim i~znajduje się w~domenie
-            publicznej, co oznacza że możesz go swobodnie wykorzystywać, publikować
-            i~rozpowszechniać. Jeśli utwór opatrzony jest dodatkowymi materiałami
-            (przypisy, motywy literackie etc.), które podlegają prawu autorskiemu, to
-            te dodatkowe materiały udostępnione są na licencji
-            \href{http://creativecommons.org/licenses/by-sa/3.0/}{Creative Commons
-            Uznanie Autorstwa – Na Tych Samych Warunkach 3.0 PL}.}
-        <xsl:if test=".//dc:rights.license">
-            \def\rightsinfo{Ten utwór jest udostepniony na licencji
-            \href{<xsl:value-of select=".//dc:rights.license" />}{<xsl:value-of select=".//dc:rights" />}.}
-        </xsl:if>
+        \title{<TeXML escape="1"><xsl:apply-templates select=".//dc:title" mode="inline" /></TeXML>}
+        \def\translatorsline{<TeXML escape="1"><xsl:call-template name="translators" /></TeXML>}
+
+        \def\bookurl{<TeXML escape="1"><xsl:value-of select=".//dc:identifier.url" /></TeXML>}
 
-        \def\sourceinfo{
+        \def\rightsinfo{<TeXML excape="1">
+            <cmd name="rightsinfostr">
+                <xsl:if test=".//dc:rights.license">
+                    <opt><xsl:value-of select=".//dc:rights.license" /></opt>
+                </xsl:if>
+                <parm><xsl:value-of select=".//dc:rights" /></parm>
+            </cmd>
+        </TeXML>}
+
+        <!-- FIXME: should be stylable -->
+        \def\sourceinfo{<TeXML excape="1">
             <xsl:if test=".//dc:source">
                 Tekst opracowany na podstawie: <xsl:apply-templates select=".//dc:source" mode="inline" />
                 \vspace{.6em}
-            </xsl:if>}
-        \def\description{<xsl:apply-templates select=".//dc:description" mode="inline" />}
+            </xsl:if>
+        </TeXML>}
+        \def\description{<TeXML excape="1">
+            <xsl:apply-templates select=".//dc:description" mode="inline" /></TeXML>}
     </TeXML>
 </xsl:template>
 
diff --git a/librarian/res/styles/wolnelektury/pdf/wolnelektury.sty b/librarian/res/styles/wolnelektury/pdf/wolnelektury.sty
new file mode 100755 (executable)
index 0000000..827d551
--- /dev/null
@@ -0,0 +1,80 @@
+\usepackage[MeX]{polski}
+
+\newcommand{\rightsinfostr}[2][] {
+    \ifx&#1&%
+        Ten utwór nie jest chroniony prawem autorskim i~znajduje się w~domenie
+        publicznej, co oznacza że możesz go swobodnie wykorzystywać, publikować
+        i~rozpowszechniać. Jeśli utwór opatrzony jest dodatkowymi materiałami
+        (przypisy, motywy literackie etc.), które podlegają prawu autorskiemu, to
+        te dodatkowe materiały udostępnione są na licencji
+        \href{http://creativecommons.org/licenses/by-sa/3.0/}{Creative Commons
+        Uznanie Autorstwa – Na Tych Samych Warunkach 3.0 PL}.
+    \else
+        Ten utwór jest udostępniony na licencji
+        \href{#1}{#2}.
+    \fi
+}
+
+\renewcommand{\maketitle}{
+    {
+    \thispagestyle{empty}
+    \footnotesize
+    \color{theme}
+
+    \noindent \begin{minipage}[t]{.35\textwidth}\vspace{0pt}
+        \href{http://www.wolnelektury.pl}{\xbox{\includegraphics[width=\textwidth]{wl-logo.png}}}
+    \end{minipage}
+    \begin{minipage}[t]{.65\textwidth}\vspace{0pt}
+
+    %\ifflaglessadvertising
+    %\else
+        \href{\bookurl}{Ta lektura}, podobnie jak tysiące innych, jest dostępna on-line na stronie
+        \href{http://www.wolnelektury.pl/}{wolnelektury.pl}.
+        \vspace{.5em}
+    %\fi
+
+    Utwór opracowany został w ramach projektu \href{http://www.wolnelektury.pl/}{Wolne Lektury}
+    przez \href{http://nowoczesnapolska.org.pl}{fundację Nowoczesna Polska}.
+
+    \end{minipage}
+    \noindent \rule{\linewidth}{0.4pt}
+
+    \vspace{.6em}
+    \color{black}
+    }
+}
+
+\newcommand{\editorialsection}{
+  \begin{figure}[b!]
+  {
+    \footnotesize
+    \color{theme}
+    \noindent \rule{\linewidth}{0.4pt}
+
+    \rightsinfo
+    \vspace{.6em}
+
+    Źródło: \href{\bookurl}{\bookurl}
+
+    \vspace{.6em}
+    \sourceinfo
+
+    \description
+    \vspace{.6em}
+
+    \editors
+
+    \vspace{.6em}
+    \coverby
+
+    \color{black}
+  }
+  \end{figure}
+}
+
+
+% Label <pa> as such.
+\let\paorig\pa
+\ifshowfootnotes
+  \renewcommand{\pa}[1]{\paorig{#1 [przypis autorski]}}
+\fi
\ No newline at end of file
diff --git a/librarian/styles/__init__.py b/librarian/styles/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/librarian/styles/wolnelektury/__init__.py b/librarian/styles/wolnelektury/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/librarian/styles/wolnelektury/cover.py b/librarian/styles/wolnelektury/cover.py
new file mode 100644 (file)
index 0000000..b417216
--- /dev/null
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import Image, ImageFont, ImageDraw
+from StringIO import StringIO
+from librarian import get_resource, URLOpener
+from librarian.cover import Cover, TextBox
+
+
+class WLCover(Cover):
+    """Default Wolne Lektury cover generator."""
+    width = 600
+    height = 833
+    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'
+    default_background = get_resource('res/cover.png')
+    format = 'JPEG'
+
+    epoch_colors = {
+        u'Starożytność': '#9e3610',
+        u'Średniowiecze': '#564c09',
+        u'Renesans': '#8ca629',
+        u'Barok': '#a6820a',
+        u'Oświecenie': '#f2802e',
+        u'Romantyzm': '#db4b16',
+        u'Pozytywizm': '#961060',
+        u'Modernizm': '#7784e0',
+        u'Dwudziestolecie międzywojenne': '#3044cf',
+        u'Współczesność': '#06393d',
+    }
+
+    def __init__(self, book_info, format=None, image_cache=None):
+        super(WLCover, self).__init__(book_info, format=format)
+        self.kind = book_info.kind
+        self.epoch = book_info.epoch
+        if book_info.cover_url:
+            url = book_info.cover_url
+            bg_src = None
+            if image_cache:
+                from urllib import quote
+                try:
+                    bg_src = URLOpener().open(image_cache + quote(url, safe=""))
+                except:
+                    pass
+            if bg_src is None:
+                bg_src = URLOpener().open(url)
+            self.background_img = StringIO(bg_src.read())
+            bg_src.close()
+        else:
+            self.background_img = self.default_background
+
+    def pretty_author(self):
+        return self.author.upper()
+
+    def image(self):
+        img = Image.new('RGB', (self.width, self.height), self.background_color)
+        draw = ImageDraw.Draw(img)
+
+        if self.epoch in self.epoch_colors:
+            epoch_color = self.epoch_colors[self.epoch]
+        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
diff --git a/librarian/styles/wolnelektury/partners/__init__.py b/librarian/styles/wolnelektury/partners/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/librarian/styles/wolnelektury/partners/cover.py b/librarian/styles/wolnelektury/partners/cover.py
new file mode 100755 (executable)
index 0000000..28f3573
--- /dev/null
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import ImageFont
+from librarian import get_resource
+from librarian.cover import Cover
+
+
+class VirtualoCover(Cover):
+    width = 600
+    height = 730
+    author_top = 73
+    title_top = 73
+    logo_bottom = 25
+    logo_width = 250
+
+
+class PrestigioCover(Cover):
+    width = 580
+    height = 783
+    background_img = get_resource('res/cover-prestigio.png')
+
+    author_top = 446
+    author_margin_left = 118
+    author_margin_right = 62
+    author_lineskip = 60
+    author_color = '#fff'
+    author_shadow = '#000'
+    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Italic.ttf'), 50)
+
+    title_top = 0
+    title_margin_left = 118
+    title_margin_right = 62
+    title_lineskip = 60
+    title_color = '#fff'
+    title_shadow = '#000'
+    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Italic.ttf'), 50)
+
+    def pretty_title(self):
+        return u"„%s”" % self.title
+
+
+class BookotekaCover(Cover):
+    width = 2140
+    height = 2733
+    background_img = get_resource('res/cover-bookoteka.png')
+
+    author_top = 480
+    author_margin_left = 307
+    author_margin_right = 233
+    author_lineskip = 156
+    author_color = '#d9d919'
+    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 130)
+
+    title_top = 400
+    title_margin_left = 307
+    title_margin_right = 233
+    title_lineskip = 168
+    title_color = '#d9d919'
+    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 140)
+
+    format = 'PNG'
+
+
+class GandalfCover(Cover):
+    width = 600
+    height = 730
+    background_img = get_resource('res/cover-gandalf.png')
+    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 30)
+    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Regular.ttf'), 40)
+    logo_bottom = 25
+    logo_width = 250
+    format = 'PNG'
diff --git a/librarian/styles/wolnelektury/pdf.py b/librarian/styles/wolnelektury/pdf.py
new file mode 100644 (file)
index 0000000..6a43b0e
--- /dev/null
@@ -0,0 +1,13 @@
+import shutil
+from librarian import get_resource
+from librarian.pdf import PDFFormat
+from librarian.styles.wolnelektury.cover import WLCover
+
+class WLPDFFormat(PDFFormat):
+    cover_class = WLCover
+    style = get_resource('res/styles/wolnelektury/pdf/wolnelektury.sty')
+
+    def get_tex_dir(self):
+        temp = super(WLPDFFormat, self).get_tex_dir()
+        shutil.copy(get_resource('res/wl-logo.png'), temp)
+        return temp
index d99e7cf..70f5f01 100644 (file)
@@ -4,7 +4,7 @@
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 import copy
-from librarian import functions, OutputFile
+from librarian import functions, IOFile
 from lxml import etree
 import os
 
@@ -78,7 +78,7 @@ def transform(wldoc, flags=None, **options):
             license_description = ""
             source = ""
             contributors = ""
-        return OutputFile.from_string((TEMPLATE % {
+        return IOFile.from_string((TEMPLATE % {
             'description': description,
             'url': url,
             'license_description': license_description,
@@ -87,5 +87,5 @@ def transform(wldoc, flags=None, **options):
             'contributors': contributors,
         }).encode('utf-8'))
     else:
-        return OutputFile.from_string(unicode(result).encode('utf-8'))
+        return IOFile.from_string(unicode(result).encode('utf-8'))
 
index 3cc0ed7..a8b4871 100755 (executable)
@@ -4,9 +4,7 @@
 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from StringIO import StringIO
-from librarian import OutputFile
-from librarian.book2anything import Book2Anything, Option
+from librarian.book2anything import Book2Anything
 
 
 class Book2Cover(Book2Anything):
index 51d6acd..21adfb1 100644 (file)
@@ -37,4 +37,4 @@ def test_empty():
     assert not WLDocument.from_string(
             '<utwor />',
             parse_dublincore=False,
-        ).as_html()
+        ).as_html().get_string()
diff --git a/tests/test_iofile.py b/tests/test_iofile.py
new file mode 100644 (file)
index 0000000..097a65a
--- /dev/null
@@ -0,0 +1,21 @@
+import os
+from StringIO import StringIO
+from tempfile import NamedTemporaryFile
+from nose.tools import *
+from librarian import IOFile
+
+def test_iofile_from_string_reusable():
+    some_file = IOFile.from_string("test")
+    some_file.get_file().read()
+    assert_equal(some_file.get_file().read(), "test")
+
+def test_iofile_from_filename_reusable():
+    temp = NamedTemporaryFile(delete=False)
+    try:
+        temp.write('test')
+        temp.close()
+        some_file = IOFile.from_filename(temp.name)
+        some_file.get_file().read()
+        assert_equal(some_file.get_file().read(), "test")
+    finally:
+        os.unlink(temp.name)