1 # -*- coding: utf-8 -*-
 
   3 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
 
   4 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 
   8 from subprocess import call, PIPE
 
   9 from tempfile import NamedTemporaryFile, mkdtemp
 
  10 from lxml import etree
 
  11 from urllib import urlretrieve
 
  12 from StringIO import StringIO
 
  13 from Texml.processor import process
 
  14 from librarian import DCNS, XMLNamespace, BuildError
 
  15 from librarian.formats import Format
 
  16 from librarian.output import OutputFile
 
  17 from librarian.renderers import Register, TreeRenderer
 
  18 from librarian.utils import Context, get_resource
 
  19 from librarian import core
 
  21 from ..html import Silent
 
  24 TexmlNS = XMLNamespace('http://getfo.sourceforge.net/texml/ns1')
 
  27 def texml_cmd(name, *parms, **kwargs):
 
  28     cmd = etree.Element(TexmlNS('cmd'), name=name)
 
  29     for opt in kwargs.get('opts', []):
 
  30         etree.SubElement(cmd, TexmlNS('opt')).text = opt
 
  32         etree.SubElement(cmd, TexmlNS('parm')).text = parm
 
  36 class PdfFormat(Format):
 
  40     style = get_resource('formats/pdf/res/default.sty')
 
  43         get_resource('formats/pdf/res/coverimage.sty'),
 
  44         get_resource('formats/pdf/res/insertimage.sty'),
 
  47     renderers = Register()
 
  49     def retrieve_file(self, url, save_as):
 
  53     def add_file(self, ctx, filename, url=None, path=None, image=False):
 
  54         from subprocess import call
 
  55         if not url and not path:
 
  56             raise BuildError('No URL or path for image')
 
  57         save_as = os.path.join(ctx.workdir, filename)
 
  59             ext = path.rsplit('.', 1)[-1]
 
  62                     call(['convert', path, save_as])
 
  64                     # JPEGs with bad density will break LaTeX with 'Dimension too large'.
 
  65                     call(['convert', '-units', 'PixelsPerInch', path, '-density', '300', save_as + '_.' + ext])
 
  66                     shutil.move(save_as + '_.' + ext, save_as)
 
  68                 shutil.copy(path, save_as)
 
  69         elif not self.retrieve_file(url, save_as):
 
  70             if url.startswith('file://'):
 
  71                 url = ctx.files_path + url[7:]
 
  73             if url.startswith('/'):
 
  74                 url = 'http://milpeer.eu' + url
 
  77                 raise BuildError('Linked file without extension: %s' % url)
 
  78             ext = url.rsplit('.', 1)[-1]
 
  80                 urlretrieve(url, save_as + '_.' + ext)
 
  82                     call(['convert', save_as + '_.' + ext, save_as])
 
  84                     # JPEGs with bad density will break LaTeX with 'Dimension too large'.
 
  85                     r = call(['convert', '-units', 'PixelsPerInch', save_as + '_.' + ext, '-density', '300',
 
  86                               save_as + '_2.' + ext])
 
  88                         shutil.move(save_as + '_.' + ext, save_as)
 
  90                         shutil.move(save_as + '_2.' + ext, save_as)
 
  92                 urlretrieve(url, save_as)
 
  94     def get_file(self, ctx, filename):
 
  95         return os.path.join(ctx.workdir, filename)
 
  97     def get_texml(self, build_ctx):
 
  98         t = etree.Element(TexmlNS('TeXML'))
 
 100         self.add_file(build_ctx, 'wl.cls', path=get_resource('formats/pdf/res/wl.cls'))
 
 101         t.append(texml_cmd("documentclass", "wl"))
 
 104         self.add_file(build_ctx, 'style.sty', path=self.style)
 
 105         t.append(texml_cmd("usepackage", "style"))
 
 106         t.append(texml_cmd("usepackage", "hyphenat"))
 
 109         for i, package in enumerate(self.local_packages):
 
 110             self.add_file(build_ctx, "librarianlocalpackage%s.sty" % i, path=package)
 
 111             t.append(texml_cmd("usepackage", "librarianlocalpackage%s" % i))
 
 113         author = ", ". join(self.doc.meta.get(DCNS('creator')) or '')
 
 114         title = self.doc.meta.title()
 
 115         t.append(texml_cmd("author", author))
 
 116         t.append(texml_cmd("title", title))
 
 118         doc = etree.SubElement(t, TexmlNS('env'), name="document")
 
 119         doc.append(texml_cmd("thispagestyle", "empty"))
 
 122         grp = etree.SubElement(doc, 'group')
 
 123         grp.append(texml_cmd("raggedright"))
 
 124         grp.append(texml_cmd("vfill"))
 
 126             p = texml_cmd("par", "")
 
 128             p[0].append(texml_cmd("Large"))
 
 129             p[0].append(texml_cmd("noindent"))
 
 130             p[0].append(texml_cmd("nohyphens", author))
 
 131             p[0].append(texml_cmd("vspace", "1em"))
 
 132             # p[0][-1].tail = author
 
 134             p = texml_cmd("par", "")
 
 136             p[0].append(texml_cmd("Huge"))
 
 137             p[0].append(texml_cmd("noindent"))
 
 138             p[0].append(texml_cmd("nohyphens", title))
 
 139             # p[0][-1].tail = title
 
 141         # IOFile probably would be better
 
 142         cover_logo_url = getattr(build_ctx, 'cover_logo', None)
 
 145         # cover_logo_url = 'http://milpeer.mdrn.pl/media/dynamic/people/logo/nowoczesnapolska.org.pl.png'
 
 147             self.add_file(build_ctx, 'coverlogo.png', cover_logo_url, image=True)
 
 148             size = Image.open(self.get_file(build_ctx, 'coverlogo.png')).size
 
 149             p = texml_cmd("par", "")
 
 151             p[0].append(texml_cmd("noindent"))
 
 152             p[0].append(texml_cmd("insertimage", 'coverlogo.png', "%fcm" % (2.0 * size[0] / size[1]), "2cm"))
 
 153         doc.append(texml_cmd("vspace", "2em"))
 
 155         ctx = Context(build_ctx, format=self, img=1)
 
 156         doc.extend(self.render(self.doc.edoc.getroot(), ctx))
 
 158         # Redakcyjna na końcu.
 
 159         doc.append(texml_cmd("section*", "Information about the resource"))
 
 160         doc.append(texml_cmd("vspace", "1em"))
 
 162         for m, f, multiple in (
 
 163                 ('Publisher: ', DCNS('publisher'), False),
 
 164                 ('Rights: ', DCNS('rights'), False),
 
 165                 ('', DCNS('description'), False)):
 
 167                 v = ', '.join(self.doc.meta.get(f))
 
 169                 v = self.doc.meta.get_one(f)
 
 171                 e = texml_cmd("par", "")
 
 172                 e[0].append(texml_cmd("noindent"))
 
 173                 e[0][0].tail = "%s%s" % (m, v)
 
 175                 doc.append(texml_cmd("vspace", "1em"))
 
 177         e = texml_cmd("par", "")
 
 178         e[0].append(texml_cmd("noindent"))
 
 179         e[0][0].tail = "Resource prepared using "
 
 180         e[0].append(texml_cmd("href", "http://milpeer.eu", "MIL/PEER"))
 
 181         e[0][-1].tail = " editing platform. "
 
 184         source_url = getattr(build_ctx, 'source_url', None)
 
 185         # source_url = 'http://milpeer.mdrn.pl/documents/27/'
 
 187             e = texml_cmd("par", "")
 
 189             e[0].append(texml_cmd("noindent"))
 
 190             e[0][0].tail = "Source available at "
 
 191             e[0].append(texml_cmd("href", source_url, source_url))
 
 195     def get_tex_dir(self, ctx):
 
 196         ctx.workdir = mkdtemp('-wl2pdf')
 
 197         texml = self.get_texml(ctx)
 
 198         tex_path = os.path.join(ctx.workdir, 'doc.tex')
 
 199         with open(tex_path, 'w') as fout:
 
 200             # print etree.tostring(texml)
 
 201             process(StringIO(etree.tostring(texml)), fout, 'utf-8')
 
 204         #     shutil.copy(tex_path, self.save_tex)
 
 206         # for sfile in ['wasysym.sty', 'uwasyvar.fd', 'uwasy.fd']:
 
 207         #     shutil.copy(get_resource(os.path.join('res/wasysym', sfile)), temp)
 
 210     def build(self, ctx=None, verbose=False):
 
 211         temp = self.get_tex_dir(ctx)
 
 212         tex_path = os.path.join(temp, 'doc.tex')
 
 220             for i in range(self.tex_passes):
 
 221                 p = call(['xelatex', tex_path])
 
 223             for i in range(self.tex_passes):
 
 224                 p = call(['xelatex', '-interaction=batchmode', tex_path],
 
 225                          stdout=PIPE, stderr=PIPE)
 
 227             # raise ParseError("Error parsing .tex file: %s" % tex_path)
 
 228             raise RuntimeError("Error parsing .tex file: %s" % tex_path)
 
 233         output_file = NamedTemporaryFile(prefix='librarian', suffix='.pdf', delete=False)
 
 234         pdf_path = os.path.join(temp, 'doc.pdf')
 
 235         shutil.move(pdf_path, output_file.name)
 
 237         os.system("ls -l " + output_file.name)
 
 238         return OutputFile.from_filename(output_file.name)
 
 240     def render(self, element, ctx):
 
 241         return self.renderers.get_for(element).render(element, ctx)
 
 244 class CmdRenderer(TreeRenderer):
 
 249         root = etree.Element(self.root_name)
 
 250         root.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 255 class EnvRenderer(TreeRenderer):
 
 257         root = etree.Element(self.root_name)
 
 258         inner = etree.SubElement(root, 'env', name=self.tag_name)
 
 262 class GroupRenderer(CmdRenderer):
 
 264         root = etree.Element(self.root_name)
 
 265         inner = etree.SubElement(root, 'group')
 
 267             inner.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 271 class SectionRenderer(CmdRenderer):
 
 272     def subcontext(self, element, ctx):
 
 274         return Context(ctx, toc_level=getattr(ctx, 'toc_level', 1) + 2)
 
 277         root = etree.Element(self.root_name)
 
 278         root.append(texml_cmd('pagebreak', opts=['1']))
 
 279         root.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 283 PdfFormat.renderers.register(core.Section, None, SectionRenderer('par'))
 
 286 PdfFormat.renderers.register(core.Header, None, CmdRenderer('section*'))
 
 288 PdfFormat.renderers.register(core.Div, None, CmdRenderer('par'))
 
 291 class ImgRenderer(CmdRenderer):
 
 295     def render(self, element, ctx):
 
 296         root = super(ImgRenderer, self).render(element, ctx)
 
 297         url = element.get('src')
 
 298         nr = getattr(ctx, 'img', 0)
 
 300         ctx.format.add_file(ctx, 'f%d.png' % nr, url, image=True)
 
 301         root[0][0].text = 'f%d.png' % nr
 
 303             size = Image.open(ctx.format.get_file(ctx, 'f%d.png' % nr)).size
 
 304         except IOError:  # not an image
 
 307         root[0][1].text = '15cm'
 
 308         root[0][2].text = '%fcm' % (15.0 * size[1] / size[0])
 
 311 PdfFormat.renderers.register(core.Div, 'img', ImgRenderer('insertimage'))
 
 314 class VideoRenderer(CmdRenderer):
 
 315     def render(self, element, ctx):
 
 316         root = super(VideoRenderer, self).render(element, ctx)
 
 317         url = 'https://www.youtube.com/watch?v=%s' % element.attrib.get('videoid')
 
 318         link = texml_cmd('href', url, url)
 
 319         root[0][0].text = None
 
 320         root[0][0].append(link)
 
 323 PdfFormat.renderers.register(core.Div, 'video', VideoRenderer('par'))
 
 326 PdfFormat.renderers.register(core.Div, 'defined', CmdRenderer('textbf'))
 
 327 PdfFormat.renderers.register(core.Div, 'item', CmdRenderer('item'))
 
 328 PdfFormat.renderers.register(core.Span, 'item', CmdRenderer('item'))
 
 329 PdfFormat.renderers.register(core.Div, 'list', EnvRenderer('itemize'))
 
 330 PdfFormat.renderers.register(core.Div, 'list.enum', EnvRenderer('enumerate'))
 
 333 PdfFormat.renderers.register(core.Span, None, TreeRenderer())
 
 334 PdfFormat.renderers.register(core.Span, 'cite', CmdRenderer('emph'))
 
 335 PdfFormat.renderers.register(core.Span, 'cite.code', CmdRenderer('texttt'))
 
 336 PdfFormat.renderers.register(core.Span, 'emp', CmdRenderer('textbf'))
 
 337 PdfFormat.renderers.register(core.Span, 'emph', CmdRenderer('emph'))
 
 340 class SpanUri(CmdRenderer):
 
 344     def render(self, element, ctx):
 
 345         root = super(SpanUri, self).render(element, ctx)
 
 347         if src.startswith('file://'):
 
 348             src = ctx.files_path + src[7:]
 
 349         root[0][0].text = src
 
 351 PdfFormat.renderers.register(core.Span, 'uri', SpanUri('href'))
 
 354 class SpanLink(CmdRenderer):
 
 358     def render(self, element, ctx):
 
 359         root = super(SpanLink, self).render(element, ctx)
 
 360         src = element.attrib.get('href', '')
 
 361         if src.startswith('file://'):
 
 362             src = ctx.files_path + src[7:]
 
 363         root[0][0].text = src
 
 365 PdfFormat.renderers.register(core.Span, 'link', SpanLink('href'))
 
 368 PdfFormat.renderers.register(core.Aside, None, TreeRenderer())
 
 369 PdfFormat.renderers.register(core.Aside, 'editorial', CmdRenderer('editorialpage'))
 
 370 PdfFormat.renderers.register(core.Aside, 'comment', Silent())