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.
 
   9 from subprocess import call, PIPE
 
  10 from tempfile import NamedTemporaryFile, mkdtemp
 
  11 from lxml import etree
 
  12 from urllib import urlretrieve
 
  13 from StringIO import StringIO
 
  14 from Texml.processor import process
 
  15 from librarian import DCNS, XMLNamespace
 
  16 from librarian.formats import Format
 
  17 from librarian.output import OutputFile
 
  18 from librarian.renderers import Register, TreeRenderer
 
  19 from librarian.utils import Context, get_resource, extend_element
 
  20 from librarian import core
 
  22 from ..html import Silent
 
  25 TexmlNS = XMLNamespace('http://getfo.sourceforge.net/texml/ns1')
 
  28 def texml_cmd(name, *parms, **kwargs):
 
  29     cmd = etree.Element(TexmlNS('cmd'), name=name)
 
  30     for opt in kwargs.get('opts', []):
 
  31         etree.SubElement(cmd, TexmlNS('opt')).text = opt
 
  33         etree.SubElement(cmd, TexmlNS('parm')).text = parm
 
  37 class PdfFormat(Format):
 
  41     style = get_resource('formats/pdf/res/default.sty')
 
  44         get_resource('formats/pdf/res/coverimage.sty'),
 
  45         get_resource('formats/pdf/res/insertimage.sty'),
 
  48     renderers = Register()
 
  50     def retrieve_file(self, url, save_as):
 
  54     def add_file(self, ctx, filename, url=None, path=None, image=False):
 
  55         from subprocess import call
 
  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
 
  76             ext = url.rsplit('.', 1)[-1]
 
  78                 urlretrieve(url, save_as + '_.' + ext)
 
  80                     call(['convert', save_as + '_.' + ext, save_as])
 
  82                     # JPEGs with bad density will break LaTeX with 'Dimension too large'.
 
  83                     r = call(['convert', '-units', 'PixelsPerInch', save_as + '_.' + ext, '-density', '300', save_as + '_2.' + ext])
 
  85                         shutil.move(save_as + '_.' + ext, save_as)
 
  87                         shutil.move(save_as + '_2.' + ext, save_as)
 
  89                 urlretrieve(url, save_as)
 
  91     def get_file(self, ctx, filename):
 
  92         return os.path.join(ctx.workdir, filename)
 
  94     def get_texml(self, build_ctx):
 
  95         t = etree.Element(TexmlNS('TeXML'))
 
  97         self.add_file(build_ctx, 'wl.cls', path=get_resource('formats/pdf/res/wl.cls'))
 
  98         t.append(texml_cmd("documentclass", "wl"))
 
 101         self.add_file(build_ctx, 'style.sty', path=self.style)
 
 102         t.append(texml_cmd("usepackage", "style"))
 
 103         t.append(texml_cmd("usepackage", "hyphenat"))
 
 106         for i, package in enumerate(self.local_packages):
 
 107             self.add_file(build_ctx, "librarianlocalpackage%s.sty" % i, path=package)
 
 108             t.append(texml_cmd("usepackage", "librarianlocalpackage%s" % i))
 
 110         author = ", ". join(self.doc.meta.get(DCNS('creator')) or '')
 
 111         title = self.doc.meta.title()
 
 112         t.append(texml_cmd("author", author))
 
 113         t.append(texml_cmd("title", title))
 
 115         doc = etree.SubElement(t, TexmlNS('env'), name="document")
 
 116         doc.append(texml_cmd("thispagestyle", "empty"))
 
 120         cover_url = self.doc.meta.get_one(DCNS('relation.coverimage.url'))
 
 122             self.add_file(build_ctx, 'cover.png', cover_url, image=True)
 
 124             img = Image.open(self.get_file(build_ctx, 'cover.png'))
 
 127             if (size[1] > size[0]):
 
 128                 img = img.crop((0, 0, size[0], size[0]))
 
 129                 img.save(self.get_file(build_ctx, 'cover.png'), format=img.format, quality=90)
 
 132             # TODO: hardcoded paper size here
 
 133             height = 210.0 * size[1] / size[0]
 
 134             doc.append(texml_cmd("makecover", "%fmm" % height))
 
 136             doc.append(texml_cmd("vfill*"))
 
 139         grp = etree.SubElement(doc, 'group')
 
 140         grp.append(texml_cmd("raggedright"))
 
 141         grp.append(texml_cmd("vfill"))
 
 143             p = texml_cmd("par", "")
 
 145             p[0].append(texml_cmd("Large"))
 
 146             p[0].append(texml_cmd("noindent"))
 
 147             p[0].append(texml_cmd("nohyphens", author))
 
 148             p[0].append(texml_cmd("vspace", "1em"))
 
 149             #p[0][-1].tail = author
 
 151             p = texml_cmd("par", "")
 
 153             p[0].append(texml_cmd("Huge"))
 
 154             p[0].append(texml_cmd("noindent"))
 
 155             p[0].append(texml_cmd("nohyphens", title))
 
 156             #p[0][-1].tail = title
 
 157         doc.append(texml_cmd("vfill"))
 
 158         doc.append(texml_cmd("vfill"))
 
 160         # IOFile probably would be better
 
 161         cover_logo_url = getattr(build_ctx, 'cover_logo', None)
 
 164         #cover_logo_url = 'http://milpeer.mdrn.pl/media/dynamic/people/logo/nowoczesnapolska.org.pl.png'
 
 166             self.add_file(build_ctx, 'coverlogo.png', cover_logo_url, image=True)
 
 167             size = Image.open(self.get_file(build_ctx, 'coverlogo.png')).size
 
 168             p = texml_cmd("par", "")
 
 170             p[0].append(texml_cmd("noindent"))
 
 171             p[0].append(texml_cmd("insertimage", 'coverlogo.png', "%fcm" % (1.0 * size[0] / size[1]), "1cm"))
 
 174         doc.append(texml_cmd("clearpage"))
 
 176         ctx = Context(build_ctx, format=self, img=1)
 
 177         doc.extend(self.render(self.doc.edoc.getroot(), ctx))
 
 179         # Redakcyjna na końcu.
 
 180         doc.append(texml_cmd("clearpage"))
 
 182         doc.append(texml_cmd("section*", "Information about the resource"))
 
 183         doc.append(texml_cmd("vspace", "1em"))
 
 186             ('Publisher: ', DCNS('publisher')),
 
 187             ('Rights: ', DCNS('rights')),
 
 188             ('Intended audience: ', DCNS('audience')),
 
 189             ('', DCNS('description')),
 
 191             v = self.doc.meta.get_one(f)
 
 193                 e = texml_cmd("par", "")
 
 194                 e[0].append(texml_cmd("noindent"))
 
 195                 e[0][0].tail = "%s%s" % (m, v)
 
 197                 doc.append(texml_cmd("vspace", "1em"))
 
 200         e = texml_cmd("par", "")
 
 201         e[0].append(texml_cmd("noindent"))
 
 202         e[0][0].tail = "Resource prepared using "
 
 203         e[0].append(texml_cmd("href", "http://milpeer.eu", "MIL/PEER"))
 
 204         e[0][-1].tail = " editing platform. "
 
 207         source_url = getattr(build_ctx, 'source_url', None)
 
 208         #source_url = 'http://milpeer.mdrn.pl/documents/27/'
 
 210             e = texml_cmd("par", "")
 
 212             e[0].append(texml_cmd("noindent"))
 
 213             e[0][0].tail = "Source available at "
 
 214             e[0].append(texml_cmd("href", source_url, source_url))
 
 218     def get_tex_dir(self, ctx):
 
 219         ctx.workdir = mkdtemp('-wl2pdf')
 
 220         texml = self.get_texml(ctx)
 
 221         tex_path = os.path.join(ctx.workdir, 'doc.tex')
 
 222         with open(tex_path, 'w') as fout:
 
 223             #print etree.tostring(texml)
 
 224             process(StringIO(etree.tostring(texml)), fout, 'utf-8')
 
 227             #~ shutil.copy(tex_path, self.save_tex)
 
 231         #for sfile in ['wasysym.sty', 'uwasyvar.fd', 'uwasy.fd']:
 
 232         #    shutil.copy(get_resource(os.path.join('res/wasysym', sfile)), temp)
 
 235     def build(self, ctx=None, verbose=False):
 
 236         temp = self.get_tex_dir(ctx)
 
 237         tex_path = os.path.join(temp, 'doc.tex')
 
 245             for i in range(self.tex_passes):
 
 246                 p = call(['xelatex', tex_path])
 
 248             for i in range(self.tex_passes):
 
 249                 p = call(['xelatex', '-interaction=batchmode', tex_path],
 
 250                             stdout=PIPE, stderr=PIPE)
 
 252             #raise ParseError("Error parsing .tex file: %s" % tex_path)
 
 253             raise RuntimeError("Error parsing .tex file: %s" % tex_path)
 
 258         output_file = NamedTemporaryFile(prefix='librarian', suffix='.pdf', delete=False)
 
 259         pdf_path = os.path.join(temp, 'doc.pdf')
 
 260         shutil.move(pdf_path, output_file.name)
 
 262         os.system("ls -l " + output_file.name)
 
 263         return OutputFile.from_filename(output_file.name)
 
 265     def render(self, element, ctx):
 
 266         return self.renderers.get_for(element).render(element, ctx)
 
 271 class CmdRenderer(TreeRenderer):
 
 275         root = etree.Element(self.root_name)
 
 276         root.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 280 class EnvRenderer(TreeRenderer):
 
 282         root = etree.Element(self.root_name)
 
 283         inner = etree.SubElement(root, 'env', name=self.tag_name)
 
 286 class GroupRenderer(CmdRenderer):
 
 288         root = etree.Element(self.root_name)
 
 289         inner = etree.SubElement(root, 'group')
 
 291             inner.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 295 class SectionRenderer(CmdRenderer):
 
 296     def subcontext(self, element, ctx):
 
 298         return Context(ctx, toc_level=getattr(ctx, 'toc_level', 1) + 2)
 
 301         root = etree.Element(self.root_name)
 
 302         root.append(texml_cmd('pagebreak', opts=['1']))
 
 303         root.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 307 PdfFormat.renderers.register(core.Section, None, SectionRenderer('par'))
 
 310 PdfFormat.renderers.register(core.Header, None, CmdRenderer('section*'))
 
 312 PdfFormat.renderers.register(core.Div, None, CmdRenderer('par'))
 
 314 class ImgRenderer(CmdRenderer):
 
 318     def render(self, element, ctx):
 
 319         root = super(ImgRenderer, self).render(element, ctx)
 
 320         url = element.get('src')
 
 321         nr = getattr(ctx, 'img', 0)
 
 323         ctx.format.add_file(ctx, 'f%d.png' % nr, url, image=True)
 
 324         root[0][0].text = 'f%d.png' % nr
 
 326             size = Image.open(ctx.format.get_file(ctx, 'f%d.png' % nr)).size
 
 327         except IOError: # not an image
 
 330         root[0][1].text = '15cm'
 
 331         root[0][2].text = '%fcm' % (15.0 * size[1] / size[0])
 
 334 PdfFormat.renderers.register(core.Div, 'img', ImgRenderer('insertimage'))
 
 337 PdfFormat.renderers.register(core.Div, 'defined', CmdRenderer('textbf'))
 
 338 PdfFormat.renderers.register(core.Div, 'item', CmdRenderer('item'))
 
 339 PdfFormat.renderers.register(core.Div, 'list', EnvRenderer('itemize'))
 
 340 PdfFormat.renderers.register(core.Div, 'list.enum', EnvRenderer('enumerate'))
 
 344 PdfFormat.renderers.register(core.Span, None, TreeRenderer())
 
 345 PdfFormat.renderers.register(core.Span, 'cite', CmdRenderer('emph'))
 
 346 PdfFormat.renderers.register(core.Span, 'cite.code', CmdRenderer('texttt'))
 
 347 PdfFormat.renderers.register(core.Span, 'emp', CmdRenderer('textbf'))
 
 348 PdfFormat.renderers.register(core.Span, 'emph', CmdRenderer('emph'))
 
 350 class SpanUri(CmdRenderer):
 
 353     def render(self, element, ctx):
 
 354         root = super(SpanUri, self).render(element, ctx)
 
 356         if src.startswith('file://'):
 
 357            src = ctx.files_path + src[7:]
 
 358         root[0][0].text = src
 
 360 PdfFormat.renderers.register(core.Span, 'uri', SpanUri('href'))
 
 363 class SpanLink(CmdRenderer):
 
 366     def render(self, element, ctx):
 
 367         root = super(SpanLink, self).render(element, ctx)
 
 368         src = element.attrib.get('href', '')
 
 369         if src.startswith('file://'):
 
 370            src = ctx.files_path + src[7:]
 
 371         root[0][0].text = src
 
 373 PdfFormat.renderers.register(core.Span, 'link', SpanLink('href'))
 
 378 PdfFormat.renderers.register(core.Aside, None, TreeRenderer())
 
 379 PdfFormat.renderers.register(core.Aside, 'editorial', CmdRenderer('editorialpage'))
 
 380 PdfFormat.renderers.register(core.Aside, 'comment', Silent())