1 from datetime import date
 
   4 from ebooklib import epub
 
   7 from librarian import functions, OutputFile, get_resource, XHTMLNS
 
   8 from librarian.cover import make_cover
 
   9 from librarian.embeds.mathml import MathML
 
  11 from librarian.fonts import strip_font
 
  18         self.element = etree.XML('''<html xmlns="http://www.w3.org/1999/xhtml"><head><link rel="stylesheet" href="style.css" type="text/css"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>WolneLektury.pl</title></head><body/></html>''')
 
  22         return self.element.find('.//' + XHTMLNS('title'))
 
  26         return self.element.find('.//' + XHTMLNS('body'))
 
  32     def __init__(self, base_url=None, fundraising=None):
 
  33         self._base_url = base_url or 'file:///home/rczajka/for/fnp/librarian/temp~/maly/img/'
 
  34         self.fundraising = fundraising
 
  35         self.footnotes = etree.Element('div', id='footnotes')
 
  39 #            'header': self.header,
 
  40             'footnotes': self.footnotes,
 
  42         self.current_cursors = []
 
  48         return self.current_cursors[-1]
 
  50     def enter_fragment(self, fragment):
 
  51         self.current_cursors.append(self.cursors[fragment])
 
  53     def exit_fragment(self):
 
  54         self.current_cursors.pop()
 
  56     def create_fragment(self, name, element):
 
  57         assert name not in self.cursors
 
  58         self.cursors[name] = element
 
  60     def forget_fragment(self, name):
 
  61         del self.cursors[name]
 
  67         if self._base_url is not None:
 
  70             return 'https://wolnelektury.pl/media/book/pictures/{}/'.format(self.document.meta.url.slug)
 
  73     # Base URL should be on Document level, not builder.
 
  74     def build(self, document, **kwargs):
 
  75         """Should return an OutputFile with the output."""
 
  76         raise NotImplementedError()
 
  79 class EpubBuilder(Builder):
 
  80     file_extension = 'epub'
 
  82     def __init__(self, *args, **kwargs):
 
  85         super().__init__(*args, **kwargs)
 
  87     def build(self, document, **kwargs):
 
  88         # replace_characters -- nie, robimy to na poziomie elementów
 
  90         # hyphenator (\00ad w odp. miejscach) -- jeśli już, to też powinno to się dziać na poziomie elementów
 
  91         # spójniki (\u00a0 po)-- jeśli już, to na poziomie elementów
 
  92         # trick na dywizy: ­⁠-
 
  95         #   początek z KAŻDEGO PLIKU xml
 
  97         # zliczamy zbiór użytych znaków
 
 100         # mieliśmy taką flagę less-advertising, używaną tylko dla Prestigio; już nie używamy.
 
 102         # @editors = document.editors() (jako str)
 
 103         # @funders = join(meta.funders)
 
 104         # @thanks = meta.thanks
 
 107         self.output = output = epub.EpubBook()
 
 108         self.document = document
 
 115         self.add_title_page()
 
 124             'Początek utworu', # i18n
 
 127         self.output.guide.append({
 
 130             "href": "part1.xhtml"
 
 134         self.build_document(self.document)
 
 139         self.add_annotations()
 
 140         self.add_support_page()
 
 144             e = len(self.output.spine) - 3 - 3
 
 145             nfunds = len(self.fundraising)
 
 149             # COUNTING CHARACTERS?
 
 150             for f in range(nfunds):
 
 151                 spine_index = int(4 + (f / nfunds * e) + f)
 
 155                     etree.XML('<div id="book-text"><div class="fundraising">' + self.fundraising[f % len(self.fundraising)] + '</div></div>')
 
 157                 self.add_html(h.element, file_name='fund%d.xhtml' % f, spine=spine_index)
 
 161         output_file = tempfile.NamedTemporaryFile(
 
 162             prefix='librarian', suffix='.epub',
 
 165         epub.write_epub(output_file.name, output, {'epub3_landmark': False})
 
 166         return OutputFile.from_filename(output_file.name)
 
 168     def build_document(self, document):
 
 169         self.toc_precedences = []
 
 174         document.tree.getroot().epub_build(self)
 
 175         if document.meta.parts:
 
 178             self.start_element('div', {'class': 'title-page'})
 
 179             self.start_element('h1', {'class': 'title'})
 
 180             self.push_text(document.meta.title)
 
 194             for child in document.children:
 
 196                 self.add_toc_entry(None, child.meta.title, 0)
 
 197                 self.build_document(child)
 
 199         self.shift_toc_base()
 
 202     def add_title_page(self):
 
 204         html.title.text = "Strona tytułowa"
 
 205         bt = etree.SubElement(html.body, 'div', **{'id': 'book-text'})
 
 206         tp = etree.SubElement(bt, 'div', **{'class': 'title-page'})
 
 208         # Tak jak jest teraz – czy może być jednocześnie
 
 210         # i „dzieło nadrzędne”
 
 211         # wcześniej mogło być dzieło nadrzędne,
 
 213         e = self.document.tree.find('//autor_utworu')
 
 215             etree.SubElement(tp, 'h2', **{'class': 'author'}).text = e.raw_printable_text()
 
 216         e = self.document.tree.find('//nazwa_utworu')
 
 218             etree.SubElement(tp, 'h1', **{'class': 'title'}).text = e.raw_printable_text()
 
 221             for author in self.document.meta.authors:
 
 222                 etree.SubElement(tp, 'h2', **{'class': 'author'}).text = author.readable()
 
 223             etree.SubElement(tp, 'h1', **{'class': 'title'}).text = self.document.meta.title
 
 225 #                <xsl:apply-templates select="//nazwa_utworu | //podtytul | //dzielo_nadrzedne" mode="poczatek"/>
 
 227 #                            <xsl:apply-templates select="//dc:creator" mode="poczatek"/>
 
 228 #                <xsl:apply-templates select="//dc:title | //podtytul | //dzielo_nadrzedne" mode="poczatek"/>
 
 230         etree.SubElement(tp, 'p', **{"class": "info"}).text = '\u00a0'
 
 232         if self.document.meta.translators:
 
 233             p = etree.SubElement(tp, 'p', **{'class': 'info'})
 
 234             p.text = 'tłum. ' + ', '.join(t.readable() for t in self.document.meta.translators)
 
 236         #<p class="info">[Kopia robocza]</p>
 
 238         p = etree.XML("""<p class="info">
 
 239               <a>Ta lektura</a>, podobnie jak tysiące innych, jest dostępna on-line na stronie
 
 240               <a href="http://www.wolnelektury.pl/">wolnelektury.pl</a>.
 
 242         p[0].attrib['href'] = str(self.document.meta.url)
 
 245         if self.document.meta.thanks:
 
 246             etree.SubElement(tp, 'p', **{'class': 'info'}).text = self.document.meta.thanks
 
 248         tp.append(etree.XML("""
 
 250             Utwór opracowany został w ramach projektu<a href="http://www.wolnelektury.pl/"> Wolne Lektury</a> przez<a href="http://www.nowoczesnapolska.org.pl/"> fundację Nowoczesna Polska</a>.
 
 254         if self.document.meta.isbn_epub:
 
 255             etree.SubElement(tp, 'p', **{"class": "info"}).text = self.document.meta.isbn_epub
 
 257         tp.append(etree.XML("""<p class="footer info">
 
 258             <a href="http://www.wolnelektury.pl/"><img src="logo_wolnelektury.png" alt="WolneLektury.pl" /></a>
 
 263             file_name='title.xhtml',
 
 265             toc='Strona tytułowa' # TODO: i18n
 
 269             get_resource('res/wl-logo-small.png'),
 
 270             file_name='logo_wolnelektury.png',
 
 271             media_type='image/png'
 
 274     def set_metadata(self):
 
 275         self.output.set_identifier(
 
 276             str(self.document.meta.url))
 
 277         self.output.set_language(
 
 278             functions.lang_code_3to2(self.document.meta.language)
 
 280         self.output.set_title(self.document.meta.title)
 
 282         for i, author in enumerate(self.document.meta.authors):
 
 283             self.output.add_author(
 
 285                 file_as=six.text_type(author),
 
 286                 uid='creator{}'.format(i)
 
 288         for translator in self.document.meta.translators:
 
 289             self.output.add_author(
 
 290                 translator.readable(),
 
 291                 file_as=six.text_type(translator),
 
 293                 uid='translator{}'.format(i)
 
 295         for publisher in self.document.meta.publisher:
 
 296             self.output.add_metadata("DC", "publisher", publisher)
 
 298         self.output.add_metadata("DC", "date", self.document.meta.created_at)
 
 304         item = epub.EpubNav()
 
 305         self.output.add_item(item)
 
 306         self.output.spine.append(item)
 
 307         self.output.add_item(epub.EpubNcx())
 
 309         self.output.toc.append(
 
 319     def add_support_page(self):
 
 321             get_resource('epub/support.xhtml'),
 
 323             toc='Wesprzyj Wolne Lektury'
 
 327             get_resource('res/jedenprocent.png'),
 
 328             media_type='image/png'
 
 331             get_resource('epub/style.css'),
 
 332             media_type='text/css'
 
 336     def add_file(self, path=None, content=None,
 
 337                  media_type='application/xhtml+xml',
 
 338                  file_name=None, uid=None,
 
 339                  spine=False, toc=None):
 
 342         # jakieś tam ścieśnianie białych znaków?
 
 345             with open(path, 'rb') as f:
 
 347             if file_name is None:
 
 348                 file_name = path.rsplit('/', 1)[-1]
 
 351             uid = file_name.split('.', 1)[0]
 
 353         item = epub.EpubItem(
 
 356             media_type=media_type,
 
 360         self.output.add_item(item)
 
 363                 self.output.spine.append(item)
 
 365                 self.output.spine.insert(spine, item)
 
 368             self.output.toc.append(
 
 376     def add_html(self, html_tree, **kwargs):
 
 377         html = etree.tostring(
 
 378             html_tree, pretty_print=True, xml_declaration=True,
 
 380             doctype='<!DOCTYPE html>'
 
 383         html = librarian.epub.squeeze_whitespace(html)
 
 392         for fname in ('DejaVuSerif.ttf', 'DejaVuSerif-Bold.ttf',
 
 393                       'DejaVuSerif-Italic.ttf', 'DejaVuSerif-BoldItalic.ttf'):
 
 396                     get_resource('fonts/' + fname),
 
 400                 media_type='font/ttf'
 
 403     def start_chunk(self):
 
 404         if getattr(self, 'current_chunk', None) is not None:
 
 405             if not len(self.current_chunk):
 
 408         self.current_chunk = etree.Element(
 
 412         self.cursors[None] = self.current_chunk
 
 413         self.current_cursors.append(self.current_chunk)
 
 415         self.section_number = 0
 
 418     def close_chunk(self):
 
 419         assert self.cursor is self.current_chunk
 
 420         ###### -- what if we're inside?
 
 427         self.chunk_counter = chunk_no + 1
 
 430         html.body.append(self.current_chunk)
 
 433             ## html container from template.
 
 436             file_name='part%d.xhtml' % chunk_no,
 
 440         self.current_chunk = None
 
 441         self.current_cursors.pop()
 
 443     def start_element(self, tag, attr):
 
 444         self.current_cursors.append(
 
 445             etree.SubElement(self.cursor, tag, **attr)
 
 448     def end_element(self):
 
 449         self.current_cursors.pop()
 
 451     def push_text(self, text):
 
 452         self.chars.update(text)
 
 454             self.cursor[-1].tail = (self.cursor[-1].tail or '') + text
 
 456             self.cursor.text = (self.cursor.text or '') + text
 
 459     def assign_image_number(self):
 
 460         image_number = getattr(self, 'image_number', 0)
 
 461         self.image_number = image_number + 1
 
 464     def assign_footnote_number(self):
 
 465         number = getattr(self, 'footnote_number', 1)
 
 466         self.footnote_number = number + 1
 
 469     def assign_section_number(self):
 
 470         number = getattr(self, 'section_number', 1)
 
 471         self.section_number = number + 1
 
 474     def assign_mathml_number(self):
 
 475         number = getattr(self, 'mathml_number', 0)
 
 476         self.mathml_number = number + 1
 
 480     def add_toc_entry(self, fragment, name, precedence):
 
 482             while self.toc_precedences and self.toc_precedences[-1] >= precedence:
 
 483                 self.toc_precedences.pop()
 
 485             self.toc_precedences = []
 
 487         real_level = self.toc_base + len(self.toc_precedences)
 
 489             self.toc_precedences.append(precedence)
 
 493         part_number = getattr(
 
 498         filename = 'part%d.xhtml' % part_number
 
 499         uid = filename.split('.')[0]
 
 501             filename += '#' + fragment
 
 502             uid += '-' + fragment
 
 504         toc = self.output.toc
 
 505         for l in range(1, real_level):
 
 506             if isinstance(toc[-1], epub.Link):
 
 507                 toc[-1] = [toc[-1], []]
 
 518     def shift_toc_base(self):
 
 522     def add_last_page(self):
 
 524         m = self.document.meta
 
 526         html.title.text = 'Strona redakcyjna'
 
 527         d = etree.SubElement(html.body, 'div', id='book-text')
 
 529         newp = lambda: etree.SubElement(d, 'p', {'class': 'info'})
 
 534                       Ten utwór jest udostępniony na licencji
 
 536             etree.SubElement(p, 'a', href=m.license).text = m.license_description
 
 539                     Ten utwór nie jest objęty majątkowym prawem autorskim i znajduje się w domenie
 
 540                     publicznej, co oznacza że możesz go swobodnie wykorzystywać, publikować
 
 541                     i rozpowszechniać. Jeśli utwór opatrzony jest dodatkowymi materiałami
 
 542                     (przypisy, motywy literackie etc.), które podlegają prawu autorskiemu, to
 
 543                     te dodatkowe materiały udostępnione są na licencji
 
 545             a = etree.SubElement(p, "a", href="http://creativecommons.org/licenses/by-sa/3.0/")
 
 546             a.text = """Creative Commons
 
 547                     Uznanie Autorstwa – Na Tych Samych Warunkach 3.0 PL"""
 
 554             p, 'a', href=str(m.url),
 
 556                 ', '.join(p.readable() for p in m.authors),
 
 561         newp().text = 'Tekst opracowany na podstawie: ' + m.source_name
 
 565               """ + ", ".join(p for p in m.publisher)
 
 568             newp().text = m.description
 
 572             newp().text = 'Opracowanie redakcyjne i przypisy: %s.' % (
 
 573                 ', '.join(e.readable() for e in sorted(self.document.editors())))
 
 576             etree.SubElement(d, 'p', {'class': 'minor-info'}).text = '''Publikację wsparli i wsparły:
 
 577             %s.''' % (', '.join(m.funders))
 
 581             p.text = 'Okładka na podstawie: '
 
 592             newp().text = m.isbn_epub
 
 594         newp().text = '\u00a0'
 
 597         p.attrib['class'] = 'minor-info'
 
 599               Plik wygenerowany dnia '''
 
 600         span = etree.SubElement(p, 'span', id='file_date')
 
 601         span.text = str(date.today())
 
 607             file_name='last.xhtml',
 
 608             toc='Strona redakcyjna',
 
 613     def add_annotations(self):
 
 614         if not len(self.footnotes):
 
 618         html.title.text = 'Przypisy'
 
 619         d = etree.SubElement(
 
 634         d.extend(self.footnotes)
 
 638             file_name='annotations.xhtml',
 
 644         # TODO: allow other covers
 
 646         cover_maker = make_cover
 
 648         cover_file = six.BytesIO()
 
 649         cover = cover_maker(self.document.meta)
 
 650         cover.save(cover_file)
 
 651         cover_name = 'cover.%s' % cover.ext()
 
 653         self.output.set_cover(
 
 654             file_name=cover_name,
 
 655             content=cover_file.getvalue(),
 
 658         ci = ('''<?xml version="1.0" encoding="UTF-8"?>
 
 660 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="en" xml:lang="en">
 
 662   <title>Okładka</title>
 
 664     body { margin: 0em; padding: 0em; }
 
 665     img { width: 100%%; }
 
 669    <img src="cover.%s" alt="Okładka" />
 
 671 </html>''' % cover.ext()).encode('utf-8')
 
 672         self.add_file(file_name='cover.xhtml', content=ci)
 
 674         self.output.spine.append(('cover', 'no'))
 
 675         self.output.guide.append({
 
 677             'href': 'cover.xhtml',
 
 681     def mathml(self, element):
 
 682         name = "math%d.png" % self.assign_mathml_number()
 
 684             content=MathML(element).to_latex().to_png().data,
 
 685             media_type='image/png',