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'
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
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
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')
'<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))
<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" />
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__':
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,