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
 
  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
 
  56         save_as = os.path.join(ctx.workdir, filename)
 
  58             ext = path.rsplit('.', 1)[-1]
 
  61                     call(['convert', path, save_as])
 
  63                     # JPEGs with bad density will break LaTeX with 'Dimension too large'.
 
  64                     call(['convert', '-units', 'PixelsPerInch', path, '-density', '300', save_as + '_.' + ext])
 
  65                     shutil.move(save_as + '_.' + ext, save_as)
 
  67                 shutil.copy(path, save_as)
 
  68         elif not self.retrieve_file(url, save_as):
 
  69             if url.startswith('file://'):
 
  70                 url = ctx.files_path + url[7:]
 
  72             if url.startswith('/'):
 
  73                 url = 'http://milpeer.eu' + url
 
  75             ext = url.rsplit('.', 1)[-1]
 
  77                 urlretrieve(url, save_as + '_.' + ext)
 
  79                     call(['convert', save_as + '_.' + ext, save_as])
 
  81                     # JPEGs with bad density will break LaTeX with 'Dimension too large'.
 
  82                     r = call(['convert', '-units', 'PixelsPerInch', save_as + '_.' + ext, '-density', '300',
 
  83                               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'))):
 
 190             v = self.doc.meta.get_one(f)
 
 192                 e = texml_cmd("par", "")
 
 193                 e[0].append(texml_cmd("noindent"))
 
 194                 e[0][0].tail = "%s%s" % (m, v)
 
 196                 doc.append(texml_cmd("vspace", "1em"))
 
 198         e = texml_cmd("par", "")
 
 199         e[0].append(texml_cmd("noindent"))
 
 200         e[0][0].tail = "Resource prepared using "
 
 201         e[0].append(texml_cmd("href", "http://milpeer.eu", "MIL/PEER"))
 
 202         e[0][-1].tail = " editing platform. "
 
 205         source_url = getattr(build_ctx, 'source_url', None)
 
 206         # source_url = 'http://milpeer.mdrn.pl/documents/27/'
 
 208             e = texml_cmd("par", "")
 
 210             e[0].append(texml_cmd("noindent"))
 
 211             e[0][0].tail = "Source available at "
 
 212             e[0].append(texml_cmd("href", source_url, source_url))
 
 216     def get_tex_dir(self, ctx):
 
 217         ctx.workdir = mkdtemp('-wl2pdf')
 
 218         texml = self.get_texml(ctx)
 
 219         tex_path = os.path.join(ctx.workdir, 'doc.tex')
 
 220         with open(tex_path, 'w') as fout:
 
 221             # print etree.tostring(texml)
 
 222             process(StringIO(etree.tostring(texml)), fout, 'utf-8')
 
 225         #     shutil.copy(tex_path, self.save_tex)
 
 227         # for sfile in ['wasysym.sty', 'uwasyvar.fd', 'uwasy.fd']:
 
 228         #     shutil.copy(get_resource(os.path.join('res/wasysym', sfile)), temp)
 
 231     def build(self, ctx=None, verbose=False):
 
 232         temp = self.get_tex_dir(ctx)
 
 233         tex_path = os.path.join(temp, 'doc.tex')
 
 241             for i in range(self.tex_passes):
 
 242                 p = call(['xelatex', tex_path])
 
 244             for i in range(self.tex_passes):
 
 245                 p = call(['xelatex', '-interaction=batchmode', tex_path],
 
 246                          stdout=PIPE, stderr=PIPE)
 
 248             # raise ParseError("Error parsing .tex file: %s" % tex_path)
 
 249             raise RuntimeError("Error parsing .tex file: %s" % tex_path)
 
 254         output_file = NamedTemporaryFile(prefix='librarian', suffix='.pdf', delete=False)
 
 255         pdf_path = os.path.join(temp, 'doc.pdf')
 
 256         shutil.move(pdf_path, output_file.name)
 
 258         os.system("ls -l " + output_file.name)
 
 259         return OutputFile.from_filename(output_file.name)
 
 261     def render(self, element, ctx):
 
 262         return self.renderers.get_for(element).render(element, ctx)
 
 265 class CmdRenderer(TreeRenderer):
 
 270         root = etree.Element(self.root_name)
 
 271         root.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 276 class EnvRenderer(TreeRenderer):
 
 278         root = etree.Element(self.root_name)
 
 279         inner = etree.SubElement(root, 'env', name=self.tag_name)
 
 283 class GroupRenderer(CmdRenderer):
 
 285         root = etree.Element(self.root_name)
 
 286         inner = etree.SubElement(root, 'group')
 
 288             inner.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 292 class SectionRenderer(CmdRenderer):
 
 293     def subcontext(self, element, ctx):
 
 295         return Context(ctx, toc_level=getattr(ctx, 'toc_level', 1) + 2)
 
 298         root = etree.Element(self.root_name)
 
 299         root.append(texml_cmd('pagebreak', opts=['1']))
 
 300         root.append(texml_cmd(self.tag_name, *(self.parms() + [""])))
 
 304 PdfFormat.renderers.register(core.Section, None, SectionRenderer('par'))
 
 307 PdfFormat.renderers.register(core.Header, None, CmdRenderer('section*'))
 
 309 PdfFormat.renderers.register(core.Div, None, CmdRenderer('par'))
 
 312 class ImgRenderer(CmdRenderer):
 
 316     def render(self, element, ctx):
 
 317         root = super(ImgRenderer, self).render(element, ctx)
 
 318         url = element.get('src')
 
 319         nr = getattr(ctx, 'img', 0)
 
 321         ctx.format.add_file(ctx, 'f%d.png' % nr, url, image=True)
 
 322         root[0][0].text = 'f%d.png' % nr
 
 324             size = Image.open(ctx.format.get_file(ctx, 'f%d.png' % nr)).size
 
 325         except IOError:  # not an image
 
 328         root[0][1].text = '15cm'
 
 329         root[0][2].text = '%fcm' % (15.0 * size[1] / size[0])
 
 332 PdfFormat.renderers.register(core.Div, 'img', ImgRenderer('insertimage'))
 
 335 PdfFormat.renderers.register(core.Div, 'defined', CmdRenderer('textbf'))
 
 336 PdfFormat.renderers.register(core.Div, 'item', CmdRenderer('item'))
 
 337 PdfFormat.renderers.register(core.Div, 'list', EnvRenderer('itemize'))
 
 338 PdfFormat.renderers.register(core.Div, 'list.enum', EnvRenderer('enumerate'))
 
 341 PdfFormat.renderers.register(core.Span, None, TreeRenderer())
 
 342 PdfFormat.renderers.register(core.Span, 'cite', CmdRenderer('emph'))
 
 343 PdfFormat.renderers.register(core.Span, 'cite.code', CmdRenderer('texttt'))
 
 344 PdfFormat.renderers.register(core.Span, 'emp', CmdRenderer('textbf'))
 
 345 PdfFormat.renderers.register(core.Span, 'emph', CmdRenderer('emph'))
 
 348 class SpanUri(CmdRenderer):
 
 352     def render(self, element, ctx):
 
 353         root = super(SpanUri, self).render(element, ctx)
 
 355         if src.startswith('file://'):
 
 356             src = ctx.files_path + src[7:]
 
 357         root[0][0].text = src
 
 359 PdfFormat.renderers.register(core.Span, 'uri', SpanUri('href'))
 
 362 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'))
 
 376 PdfFormat.renderers.register(core.Aside, None, TreeRenderer())
 
 377 PdfFormat.renderers.register(core.Aside, 'editorial', CmdRenderer('editorialpage'))
 
 378 PdfFormat.renderers.register(core.Aside, 'comment', Silent())