From: Radek Czajka Date: Thu, 19 Jan 2012 15:35:59 +0000 (+0100) Subject: Merge branch 'pretty' into commerce X-Git-Url: https://git.mdrn.pl/librarian.git/commitdiff_plain/65916b0958b55a24073cb592e31b6bb7ac0585b9?ds=inline;hp=-c Merge branch 'pretty' into commerce Conflicts: librarian/cover.py librarian/epub.py librarian/epub/xsltContent.xsl librarian/epub/xsltScheme.xsl scripts/book2epub --- 65916b0958b55a24073cb592e31b6bb7ac0585b9 diff --combined librarian/cover.py index 8e61fe3,63e4aa0..e7a8e1b --- a/librarian/cover.py +++ b/librarian/cover.py @@@ -7,13 -7,84 +7,116 @@@ import Image, ImageFont, ImageDraw, Ima from librarian import get_resource + 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, - shadow_color=None): ++ 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: - 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] ++ 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 @@@ -21,9 -92,7 +124,9 @@@ 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 @@@ -31,14 -100,13 +134,14 @@@ 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', @@@ -49,75 -117,16 +152,16 @@@ '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): @@@ -125,10 -134,7 +169,10 @@@ 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 @@@ -137,16 -143,29 +181,31 @@@ 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 @@@ -160,6 -179,125 +219,125 @@@ return self.image().save(format=self.format, *args, **kwargs) + class WLCover(Cover): + """Default Wolne Lektury cover generator.""" + uses_dc_cover = True + author_font = ImageFont.truetype( + get_resource('fonts/JunicodeWL-Regular.ttf'), 20) + author_lineskip = 30 + title_font = ImageFont.truetype( + get_resource('fonts/DejaVuSerif-Bold.ttf'), 30) + title_lineskip = 40 + title_box_width = 350 + bar_width = 35 + background_color = '#444' + author_color = '#444' + + epochs = { + u'Starożytność': 0, + u'Średniowiecze': 30, + u'Renesans': 60, + u'Barok': 90, + u'Oświecenie': 120, + u'Romantyzm': 150, + u'Pozytywizm': 180, + u'Modernizm': 210, + u'Dwudziestolecie międzywojenne': 240, + u'Współczesność': 270, + } + + def __init__(self, book_info): + super(WLCover, self).__init__(book_info) + self.kind = book_info.kind + self.epoch = book_info.epoch + if book_info.cover_url: + from urllib2 import urlopen + from StringIO import StringIO + + bg_src = urlopen(book_info.cover_url) + self.background_img = StringIO(bg_src.read()) + bg_src.close() + + def pretty_author(self): + return self.author.upper() + + def image(self): + from colorsys import hsv_to_rgb + + img = Image.new('RGB', (self.width, self.height), self.background_color) + draw = ImageDraw.Draw(img) + + if self.epoch in self.epochs: + epoch_color = tuple(int(255 * c) for c in hsv_to_rgb( + float(self.epochs[self.epoch]) / 360, .7, .7)) + else: + epoch_color = '#000' + draw.rectangle((0, 0, self.bar_width, self.height), fill=epoch_color) + + if self.background_img: + src = Image.open(self.background_img) + trg_size = (self.width - self.bar_width, self.height) + if src.size[0] * trg_size[1] < src.size[1] * trg_size[0]: + resized = ( + trg_size[0], + src.size[1] * trg_size[0] / src.size[0] + ) + cut = (resized[1] - trg_size[1]) / 2 + src = src.resize(resized) + src = src.crop((0, cut, src.size[0], src.size[1] - cut)) + else: + resized = ( + src.size[0] * trg_size[1] / src.size[1], + trg_size[1], + ) + cut = (resized[0] - trg_size[0]) / 2 + src = src.resize(resized) + src = src.crop((cut, 0, src.size[0] - cut, src.size[1])) + + img.paste(src, (self.bar_width, 0)) + del src + + box = TextBox(self.title_box_width, self.height, padding_y=20) + box.text(self.pretty_author(), + font=self.author_font, + line_height=self.author_lineskip, + color=self.author_color, + shadow_color=self.author_shadow, + ) + + box.skip(10) + box.draw.line((75, box.height, 275, box.height), + fill=self.author_color, width=2) + box.skip(15) + + box.text(self.pretty_title(), + line_height=self.title_lineskip, + font=self.title_font, + color=epoch_color, + shadow_color=self.title_shadow, + ) + box_img = box.image() + + if self.kind == 'Liryka': + # top + box_top = 100 + elif self.kind == 'Epika': + # bottom + box_top = self.height - 100 - box_img.size[1] + else: + # center + box_top = (self.height - box_img.size[1]) / 2 + + box_left = self.bar_width + (self.width - self.bar_width - + box_img.size[0]) / 2 + draw.rectangle((box_left, box_top, + box_left + box_img.size[0], box_top + box_img.size[1]), + fill='#fff') + img.paste(box_img, (box_left, box_top), box_img) + + return img + + class VirtualoCover(Cover): width = 600 @@@ -226,42 -364,3 +404,42 @@@ class GandalfCover(Cover) logo_bottom = 25 logo_width = 250 format = 'PNG' + + +class ArtaTechCover(Cover): + width = 600 + height = 800 + background_img = get_resource('res/cover-arta-tech.jpg') + author_top = 132 + author_margin_left = 235 + author_margin_right = 23 + author_align = 'r' + author_font = ImageFont.truetype(get_resource('fonts/DroidSans.ttf'), 32) + author_color = '#555555' + author_wrap = False + title_top = 17 + title_margin_right = 21 + title_margin_left = 60 + title_align = 'r' + title_font = ImageFont.truetype(get_resource('fonts/EBGaramond-Regular.ttf'), 42) + title_color = '#222222' + title_wrap = False + format = 'JPEG' + + def pretty_author(self): + return self.author.upper() + + +def ImageCover(img): + """ a class factory for simple image covers """ + img = Image.open(img) + + class ImgCover(Cover): + def image(self): + return img + + @property + def format(self): + return self.image().format + + return ImgCover diff --combined librarian/epub.py index 348df0c,80941eb..48bb2f2 --- a/librarian/epub.py +++ b/librarian/epub.py @@@ -5,7 -5,6 +5,7 @@@ # from __future__ import with_statement +from copy import deepcopy import os import os.path import subprocess @@@ -13,14 -12,11 +13,11 @@@ from StringIO import StringI from copy import deepcopy from lxml import etree import zipfile - from tempfile import mkdtemp + from tempfile import mkdtemp, NamedTemporaryFile from shutil import rmtree - import sys - - from librarian import XMLNamespace, RDFNS, DCNS, WLNS, NCXNS, OPFNS, XHTMLNS, NoDublinCore - from librarian.dcparser import BookInfo - from librarian.cover import ImageCover + from librarian import RDFNS, WLNS, NCXNS, OPFNS, XHTMLNS, OutputFile + from librarian.cover import WLCover from librarian import functions, get_resource @@@ -160,19 -156,23 +157,23 @@@ def add_to_spine(spine, partno) class TOC(object): - def __init__(self, name=None, part_number=None): + def __init__(self, name=None, part_href=None): self.children = [] self.name = name - self.part_number = part_number + self.part_href = part_href self.sub_number = None - def add(self, name, part_number, level=0, is_part=True): + def add(self, name, part_href, level=0, is_part=True, index=None): + assert level == 0 or index is None if level > 0 and self.children: - return self.children[-1].add(name, part_number, level-1, is_part) + return self.children[-1].add(name, part_href, level-1, is_part) else: t = TOC(name) - t.part_number = part_number - self.children.append(t) + t.part_href = part_href + if index is not None: + self.children.insert(index, t) + else: + self.children.append(t) if not is_part: t.sub_number = len(self.children) + 1 return t.sub_number @@@ -189,7 -189,13 +190,13 @@@ 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) @@@ -202,15 -208,26 +209,26 @@@ 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( + "
%s
" % + (depth, child.href(), child.name)) + texts.append(child.html_part(depth+1)) + return "\n".join(texts) + + def html(self): + with open(get_resource('epub/toc.html')) as f: + t = unicode(f.read(), 'utf-8') + return t % self.html_part() + def used_chars(element): """ Lists characters used in an ETree Element """ @@@ -250,9 -267,9 +268,9 @@@ def transform_chunk(chunk_xml, chunk_no toc = TOC() for element in chunk_xml[0]: if element.tag in ("naglowek_czesc", "naglowek_rozdzial", "naglowek_akt", "srodtytul"): - toc.add(node_name(element), chunk_no) + toc.add(node_name(element), "part%d.html" % chunk_no) elif element.tag in ('naglowek_podrozdzial', 'naglowek_scena'): - subnumber = toc.add(node_name(element), chunk_no, level=1, is_part=False) + subnumber = toc.add(node_name(element), "part%d.html" % chunk_no, level=1, is_part=False) element.set('sub', str(subnumber)) if empty: if not _empty_html_static: @@@ -268,44 -285,40 +286,40 @@@ 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 //.epub instead of /.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) @@@ -313,12 -326,12 +327,12 @@@ 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 @@@ -339,55 -352,28 +353,29 @@@ 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 @@@ -401,15 -387,23 +389,29 @@@ '' \ '') - 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( + '')) ++ manifest.append(etree.fromstring( ++ '')) + 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()) @@@ -424,30 -418,10 +426,30 @@@ '')) manifest.append(etree.fromstring( '' % (c_name, c.mime_type()))) - spine.insert(0, etree.fromstring('')) + spine.insert(0, etree.fromstring('')) opf.getroot()[0].append(etree.fromstring('')) - opf.getroot().append(etree.fromstring('')) + guide.append(etree.fromstring('')) + 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( + '' % (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 input_xml.findall('//ilustr'): ++ for ilustr in document.edoc.findall('//ilustr'): + ilustr.tag = 'extra' annotations = etree.Element('annotations') @@@ -455,23 -429,24 +457,24 @@@ '"-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">' \ '' \ - '' \ - 'Strona tytułowa' \ - '') + '') nav_map = toc_file[-1] - toc, chunk_counter, chars, sample = transform_file(input_xml, sample=sample) + if html_toc: + manifest.append(etree.fromstring( + '')) + spine.append(etree.fromstring( + '')) + guide.append(etree.fromstring('')) + + 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( - 'Przypisy'\ - '' % {'i': toc_counter})) - toc_counter += 1 + toc.add("Przypisy", "annotations.html") manifest.append(etree.fromstring( '')) spine.append(etree.fromstring( @@@ -482,44 -457,38 +485,44 @@@ zip.writestr('OPS/annotations.html', etree.tostring( html_tree, method="html", pretty_print=True)) - nav_map.append(etree.fromstring( - 'Strona redakcyjna'\ - '' % {'i': toc_counter})) + toc.add("Strona redakcyjna", "last.html") manifest.append(etree.fromstring( '')) spine.append(etree.fromstring( '')) - 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( + '' % (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')) @@@ -529,5 -498,13 +532,13 @@@ 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(('', title, ''))) + + # write TOC + if html_toc: + toc.add(u"Spis treści", "toc.html", index=1) + zip.writestr('OPS/toc.html', toc.html().encode('utf-8')) + toc.write_to_xml(nav_map) zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True)) zip.close() + + return OutputFile.from_filename(output_file.name) diff --combined librarian/epub/style.css index 7fb53a3,622c8da..a4c61c8 --- a/librarian/epub/style.css +++ b/librarian/epub/style.css @@@ -46,8 -46,7 +46,8 @@@ a img #book-text { - margin: 2em; + /*margin: 2em;*/ + margin: 5px; /*margin-right: 9em;*/ } @@@ -108,28 -107,24 +108,24 @@@ text-align: left; } - .annotation + .annotation-anchor { font-style: normal; font-weight: normal; font-size: 0.875em; - } - - #footnotes .annotation - { display: block; float: left; width: 2.5em; clear: both; } - #footnotes div + .annotation { margin: 0; margin-top: 1.5em; } - #footnotes p + .annotation-body { margin-left: 2.5em; font-size: 0.875em; @@@ -352,13 -347,17 +348,17 @@@ em.author-emphasi text-transform: uppercase; } - p.info + .info { text-align: center; margin-bottom: 1em; } + .info div + { + text-align: center; + } - p.info img + .info img { margin: 0; margin-left: 2em; @@@ -371,12 -370,3 +371,12 @@@ p.minor p.footer { margin-top: 2em; } + +.ilustr { + margin-top: 1em; + margin-bottom: 1em; +} + +.ilustr img { + max-width: 100%; +} diff --combined librarian/epub/xsltContent.xsl index 65bf808,83eb376..ef7ae74 --- a/librarian/epub/xsltContent.xsl +++ b/librarian/epub/xsltContent.xsl @@@ -29,14 -29,15 +29,13 @@@ - - - - - - + + + diff --combined librarian/epub/xsltScheme.xsl index 3065cac,3ddcf97..395e950 --- a/librarian/epub/xsltScheme.xsl +++ b/librarian/epub/xsltScheme.xsl @@@ -168,7 -168,7 +168,7 @@@
-
 
+
 
@@@ -278,18 -278,6 +278,18 @@@
+ +
+ ilustracja + + + + +
+
+ + + @@@ -319,11 -307,11 +319,11 @@@ - + - + diff --combined librarian/packagers.py index 2c543da,36a7b60..9a93e56 --- a/librarian/packagers.py +++ b/librarian/packagers.py @@@ -6,8 -6,8 +6,8 @@@ import os from copy import deepcopy from lxml import etree - from librarian import epub, pdf, DirDocProvider, ParseError, cover - from librarian.dcparser import BookInfo + from librarian import pdf, epub, DirDocProvider, ParseError, cover + from librarian.parser import WLDocument class Packager(object): @@@ -26,8 -26,11 +26,11 @@@ except: pass outfile = os.path.join(output_dir, slug + '.' + cls.ext) - cls.converter.transform(provider, file_path=main_input, output_file=outfile, + + doc = WLDocument.from_file(main_input, provider=provider) + output_file = cls.converter.transform(doc, cover=cls.cover, flags=cls.flags) + doc.save_output_file(output_file, output_path=outfile) @classmethod @@@ -60,12 -63,6 +63,12 @@@ class GandalfEpubPackager(EpubPackager) class GandalfPdfPackager(PdfPackager): cover = cover.GandalfCover +class ArtaTechEpubPackager(EpubPackager): + cover = cover.ArtaTechCover + +class ArtaTechPdfPackager(PdfPackager): + cover = cover.ArtaTechCover + class BookotekaEpubPackager(EpubPackager): cover = cover.BookotekaCover @@@ -84,7 -81,6 +87,6 @@@ class VirtualoEpubPackager(Packager) """ truncates text to at most `limit' bytes in utf-8 """ if text is None: return text - orig_text = text if len(text.encode('utf-8')) > limit: newlimit = limit - 3 while len(text.encode('utf-8')) > newlimit: @@@ -122,7 -118,8 +124,8 @@@ 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) @@@ -133,14 -130,13 +136,13 @@@ product_elem[4][0][1].text = cls.utf_trunc(info.author.last_name, 100) xml.append(product_elem) - cover.VirtualoCover( - u' '.join(info.author.first_names + (info.author.last_name,)), - info.title - ).save(os.path.join(outfile_dir, slug+'.jpg')) + cover.VirtualoCover(info).save(os.path.join(outfile_dir, slug+'.jpg')) outfile = os.path.join(outfile_dir, '1.epub') outfile_sample = os.path.join(outfile_dir, '1.sample.epub') - epub.transform(provider, file_path=main_input, output_file=outfile) - epub.transform(provider, file_path=main_input, output_file=outfile_sample, sample=25) + doc.save_output_file(epub.transform(doc), + output_path=outfile) + doc.save_output_file(epub.transform(doc, sample=25), + output_path=outfile_sample) except ParseError, e: print '%(file)s:%(name)s:%(message)s' % { 'file': main_input, diff --combined scripts/book2epub index 82aaa2b,bdb5ac6..ce8adb5 --- a/scripts/book2epub +++ b/scripts/book2epub @@@ -7,8 -7,8 +7,9 @@@ import os.path import optparse - from librarian import epub, DirDocProvider, ParseError + from librarian import DirDocProvider, ParseError +from librarian.cover import ImageCover + from librarian.parser import WLDocument if __name__ == '__main__': @@@ -20,20 -20,14 +21,22 @@@ 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('-i', '--with-images', action='store_true', dest='images', default=False, + help='add images with ') + 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('-c', '--cover', dest='cover', metavar='FILE', ++ parser.add_option('--cover', dest='cover', metavar='FILE', + help='specifies the cover file') options, input_filenames = parser.parse_args() @@@ -46,31 -40,20 +49,34 @@@ 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,