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
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
#
from __future__ import with_statement
+from copy import deepcopy
import os
import os.path
import subprocess
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
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 """
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)
#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;
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;
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"> </div>
+ </div><div class='stanza-spacer' xmlns="http://www.w3.org/1999/xhtml"> </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>
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
class GandalfPdfPackager(PdfPackager):
cover = cover.GandalfCover
+class ArtaTechEpubPackager(EpubPackager):
+ cover = cover.ArtaTechCover
+
+class ArtaTechPdfPackager(PdfPackager):
+ cover = cover.ArtaTechCover
+
class BookotekaEpubPackager(EpubPackager):
cover = cover.BookotekaCover
""" 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,
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,