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 Merge branch 'pretty' into commerce Conflicts: librarian/cover.py librarian/epub.py librarian/epub/xsltContent.xsl librarian/epub/xsltScheme.xsl scripts/book2epub --- 65916b0958b55a24073cb592e31b6bb7ac0585b9 diff --cc librarian/cover.py index 8e61fe3,63e4aa0..e7a8e1b --- a/librarian/cover.py +++ b/librarian/cover.py @@@ -7,7 -7,79 +7,110 @@@ 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' @@@ -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 diff --cc librarian/epub.py index 348df0c,80941eb..48bb2f2 --- a/librarian/epub.py +++ b/librarian/epub.py @@@ -272,22 -290,15 +291,15 @@@ def transform(wldoc, verbose=False sample=None, cover=None, flags=None): """ produces a EPUB file - provider: a DocProvider - slug: slug of file to process, available by provider - output_file: file-like object or path to output file - output_dir: path to directory to save output file to; either this or output_file must be present - make_dir: writes output to //.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 @@@ -362,32 -366,14 +367,15 @@@ 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') @@@ -489,13 -462,7 +490,13 @@@ '')) 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)) diff --cc librarian/epub/xsltContent.xsl index 65bf808,83eb376..ef7ae74 --- a/librarian/epub/xsltContent.xsl +++ b/librarian/epub/xsltContent.xsl @@@ -29,10 -29,8 +29,6 @@@ - - - - - - diff --cc 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__': @@@ -26,14 -28,6 +29,14 @@@ 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,