From 724310b910f3ec0aeb2a1fbd0189d5f588a00f8f Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Fri, 15 Feb 2013 16:37:22 +0100 Subject: [PATCH] Basic PDF support. --- librarian/book2anything.py | 4 +- librarian/cover.py | 2 +- librarian/parser.py | 6 +- librarian/pdf.py | 4 +- librarian/pdf/wl.cls | 10 +- librarian/pyhtml.py | 7 +- librarian/pypdf.py | 191 ++++++++++++++++++++----- librarian/styles/wolnelektury/cover.py | 2 +- setup.py | 7 +- 9 files changed, 179 insertions(+), 54 deletions(-) diff --git a/librarian/book2anything.py b/librarian/book2anything.py index b8b8d27..b60cd0f 100755 --- a/librarian/book2anything.py +++ b/librarian/book2anything.py @@ -10,7 +10,6 @@ import optparse from librarian import DirDocProvider, ParseError from librarian.parser import WLDocument -from librarian.cover import WLCover class Option(object): @@ -88,7 +87,7 @@ class Book2Anything(object): for option in cls.parser_options: parser_args[option.name()] = option.value(options) # Prepare additional args for transform method. - transform_args = {} + transform_args = {"verbose": options.verbose} for option in cls.transform_options: transform_args[option.name()] = option.value(options) # Add flags to transform_args, if any. @@ -98,6 +97,7 @@ class Book2Anything(object): transform_args['flags'] = transform_flags # Add cover support, if any. if cls.uses_cover: + from librarian.styles.wolnelektury.cover import WLCover if options.image_cache: def cover_class(*args, **kwargs): return WLCover(image_cache=options.image_cache, *args, **kwargs) diff --git a/librarian/cover.py b/librarian/cover.py index b53de30..7343e68 100644 --- a/librarian/cover.py +++ b/librarian/cover.py @@ -4,7 +4,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import re -import Image, ImageFont, ImageDraw, ImageFilter +from PIL import Image, ImageFont, ImageDraw, ImageFilter from StringIO import StringIO from librarian import get_resource, IOFile diff --git a/librarian/parser.py b/librarian/parser.py index f02b64c..fb2f986 100644 --- a/librarian/parser.py +++ b/librarian/parser.py @@ -204,8 +204,8 @@ class WLDocument(object): return epub.transform(self, *args, **kwargs) def as_pdf(self, *args, **kwargs): - from librarian import pdf - return pdf.transform(self, *args, **kwargs) + from librarian import pypdf + return pypdf.EduModulePDFFormat(self).build(*args, **kwargs) def as_mobi(self, *args, **kwargs): from librarian import mobi @@ -217,7 +217,7 @@ class WLDocument(object): def as_cover(self, cover_class=None, *args, **kwargs): if cover_class is None: - from librarian.cover import WLCover + from librarian.styles.wolnelektury.cover import WLCover cover_class = WLCover return cover_class(self.book_info, *args, **kwargs).output_file() diff --git a/librarian/pdf.py b/librarian/pdf.py index d06a656..2cbc2c0 100644 --- a/librarian/pdf.py +++ b/librarian/pdf.py @@ -265,8 +265,8 @@ class PDFFormat(Format): # Copy style shutil.copy(get_resource('pdf/wl.cls'), temp) shutil.copy(self.style, os.path.join(temp, 'style.sty')) - for sfile in ['wasysym.sty', 'uwasyvar.fd', 'uwasy.fd']: - shutil.copy(get_resource(os.path.join('res/wasysym', sfile)), temp) + #for sfile in ['wasysym.sty', 'uwasyvar.fd', 'uwasy.fd']: + # shutil.copy(get_resource(os.path.join('res/wasysym', sfile)), temp) # Save attachments if self.cover: diff --git a/librarian/pdf/wl.cls b/librarian/pdf/wl.cls index 9e5e6be..4023934 100755 --- a/librarian/pdf/wl.cls +++ b/librarian/pdf/wl.cls @@ -75,7 +75,7 @@ \usepackage[overload]{textcase} \usepackage{scalefnt} % TODO: link color is a style thing -\usepackage[colorlinks=true,linkcolor=black,setpagesize=false,urlcolor=black,xetex]{hyperref} +\usepackage[colorlinks=true,linkcolor=black,setpagesize=false,urlcolor=blue,xetex]{hyperref} \ifenablewlfont \setmainfont [ @@ -418,3 +418,11 @@ Letters={Uppercase} \newcommand*\checkbox{\item[\Square]} \newcommand*\radio{\item[\Circle]} + +\renewcommand{\naglowekrozdzial}[1]{% +\subsection*{\typosubsection{#1}}% +} + +\renewcommand{\naglowekpodrozdzial}[1]{% +\subsubsection*{\typosubsubsection{#1}}% +} diff --git a/librarian/pyhtml.py b/librarian/pyhtml.py index d138701..6d1e914 100644 --- a/librarian/pyhtml.py +++ b/librarian/pyhtml.py @@ -9,6 +9,7 @@ from xmlutils import Xmill, tag, tagged, ifoption, tag_open_close from librarian import functions import re import random +from copy import deepcopy IMAGE_THUMB_WIDTH = 300 @@ -444,12 +445,10 @@ class Luki(Exercise): return question.xpath(".//luka") def solution_html(self, piece): + piece = deepcopy(piece) + piece.tail = None sub = EduModule() return sub.generate(piece) - # print piece.text - # return piece.text + ''.join( - # [etree.tostring(n, encoding=unicode) - # for n in piece]) def handle_pytanie(self, element): qpre, qpost = super(Luki, self).handle_pytanie(element) diff --git a/librarian/pypdf.py b/librarian/pypdf.py index 4cc4d1d..68e0bd9 100644 --- a/librarian/pypdf.py +++ b/librarian/pypdf.py @@ -10,12 +10,14 @@ with TeXML, then runs it by XeLaTeX. """ from __future__ import with_statement +from copy import deepcopy import os import os.path import shutil from StringIO import StringIO from tempfile import mkdtemp, NamedTemporaryFile import re +import random from copy import deepcopy from subprocess import call, PIPE @@ -23,7 +25,7 @@ from Texml.processor import process from lxml import etree from lxml.etree import XMLSyntaxError, XSLTApplyError -from xmlutils import Xmill, tag, tagged, ifoption +from xmlutils import Xmill, tag, tagged, ifoption, tag_open_close from librarian.dcparser import Person from librarian.parser import WLDocument from librarian import ParseError, DCNS, get_resource, IOFile, Format @@ -49,15 +51,18 @@ def escape(really): return deco -def cmd(name, pass_text=False): +def cmd(name, parms=None): def wrap(self, element): - pre = u'' % name - - if pass_text: - pre += "%s" % element.text - return pre + '' - else: - return pre, '' + pre, post = tag_open_close('cmd', name=name) + + if parms: + for parm in parms: + e = etree.Element("parm") + e.text = parm + pre += etree.tostring(e) + pre += "" + post = "" + post + return pre, post return wrap @@ -74,7 +79,7 @@ class EduModule(Xmill): def swap_endlines(txt): if self.options['strofa']: - txt = txt.replace("/\n", '') + txt = txt.replace("/\n", '') return txt self.register_text_filter(functions.substitute_entities) self.register_text_filter(mark_alien_characters) @@ -189,7 +194,7 @@ class EduModule(Xmill): handle_wyroznienie = \ handle_texcommand - _handle_strofa = cmd("strofa", True) + _handle_strofa = cmd("strofa") def handle_strofa(self, element): self.options = {'strofa': True} @@ -250,6 +255,8 @@ class EduModule(Xmill): return def handle_lista(self, element, attrs={}): + if not element.findall("punkt"): + return None ltype = element.attrib.get('typ', 'punkt') if ltype == 'slowniczek': surl = element.attrib.get('href', None) @@ -271,16 +278,19 @@ class EduModule(Xmill): def handle_cwiczenie(self, element): exercise_handlers = { - 'wybor': Wybor} - # 'uporzadkuj': Uporzadkuj, - # 'luki': Luki, - # 'zastap': Zastap, - # 'przyporzadkuj': Przyporzadkuj, - # 'prawdafalsz': PrawdaFalsz + 'wybor': Wybor, + 'uporzadkuj': Uporzadkuj, + 'luki': Luki, + 'zastap': Zastap, + 'przyporzadkuj': Przyporzadkuj, + 'prawdafalsz': PrawdaFalsz + } typ = element.attrib['typ'] + self.exercise_counter += 1 if not typ in exercise_handlers: return '(no handler)' + self.options = {'exercise_counter': self.exercise_counter} handler = exercise_handlers[typ](self.options) return handler.generate(element) @@ -322,7 +332,7 @@ class EduModule(Xmill): # else: frames_c = "" # return u"""""" % frames_c, u"
" return u''' -tabular%s +tabular%s ''' % ('l' * max_col), \ u'''tabular''' @@ -333,10 +343,14 @@ class EduModule(Xmill): @escape(1) def handle_kol(self, element): if element.getnext() is not None: - return u"", u'' + return u"", u'' return u"", u"" - handle_link = cmd('em', True) + def handle_link(self, element): + if element.attrib.get('url'): + return cmd('href', parms=[element.attrib['url']])(self, element) + else: + return cmd('em')(self, element) class Exercise(EduModule): @@ -344,14 +358,22 @@ class Exercise(EduModule): self.question_counter = 0 super(Exercise, self).__init__(*args, **kw) - handle_rozw_kom = ifoption(teacher=True)(cmd('akap', True)) + handle_rozw_kom = ifoption(teacher=True)(cmd('akap')) def handle_cwiczenie(self, element): - self.options = {'exercise': element.attrib['typ']} + self.options = { + 'exercise': element.attrib['typ'], + 'sub_gen': True, + } self.question_counter = 0 self.piece_counter = 0 - pre = u"" + header = etree.Element("parm") + header_cmd = etree.Element("cmd", name="naglowekpodrozdzial") + header_cmd.append(header) + header.text = u"Zadanie %d." % self.options['exercise_counter'] + + pre = etree.tostring(header_cmd, encoding=unicode) post = u"" # Add a single tag if it's not there if not element.xpath(".//pytanie"): @@ -363,29 +385,37 @@ class Exercise(EduModule): def handle_pytanie(self, element): """This will handle element, when there is no """ - opts = {} self.question_counter += 1 self.piece_counter = 0 - solution = element.attrib.get('rozw', None) - if solution: - opts['solution'] = solution + pre = post = u"" + if self.options['teacher'] and element.attrib.get('rozw'): + post += u" [rozwiązanie: %s]" % element.attrib.get('rozw') + return pre, post - handles = element.attrib.get('uchwyty', None) - if handles: - opts['handles'] = handles + def handle_punkt(self, element): + pre, post = super(Exercise, self).handle_punkt(element) + if self.options['teacher'] and element.attrib.get('rozw'): + post += u" [rozwiązanie: %s]" % element.attrib.get('rozw') + return pre, post - minimum = element.attrib.get('min', None) - if minimum: - opts['minimum'] = minimum + def solution_header(self): + par = etree.Element("cmd", name="par") + parm = etree.Element("parm") + parm.text = u"Rozwiązanie:" + par.append(parm) + return etree.tostring(par) - if opts: - self.options = opts - return u"", u"" + def explicit_solution(self): + if self.options['solution']: + par = etree.Element("cmd", name="par") + parm = etree.Element("parm") + parm.text = self.options['solution'] + par.append(parm) + return self.solution_header() + etree.tostring(par) -class Wybor(Exercise): - INSTRUCTION = None +class Wybor(Exercise): def handle_cwiczenie(self, element): pre, post = super(Wybor, self).handle_cwiczenie(element) is_single_choice = True @@ -415,6 +445,89 @@ class Wybor(Exercise): return super(Wybor, self).handle_punkt(element) +class Uporzadkuj(Exercise): + def handle_pytanie(self, element): + order_items = element.xpath(".//punkt/@rozw") + return super(Uporzadkuj, self).handle_pytanie(element) + + +class Przyporzadkuj(Exercise): + def handle_lista(self, lista): + header = etree.Element("parm") + header_cmd = etree.Element("cmd", name="par") + header_cmd.append(header) + if 'nazwa' in lista.attrib: + header.text = u"Kategorie:" + elif 'cel' in lista.attrib: + header.text = u"Elementy do przyporządkowania:" + else: + header.text = u"Lista:" + pre, post = super(Przyporzadkuj, self).handle_lista(lista) + pre = etree.tostring(header_cmd, encoding=unicode) + pre + return pre, post + + +class Luki(Exercise): + def find_pieces(self, question): + return question.xpath(".//luka") + + def solution(self, piece): + piece = deepcopy(piece) + piece.tail = None + sub = EduModule() + return sub.generate(piece) + + def handle_pytanie(self, element): + qpre, qpost = super(Luki, self).handle_pytanie(element) + + luki = self.find_pieces(element) + random.shuffle(luki) + self.words = u"%s" % ( + "".join("%s" % self.solution(luka) for luka in luki) + ) + return qpre, qpost + + def handle_opis(self, element): + return '', self.words + + def handle_luka(self, element): + luka = "_" * 10 + if self.options['teacher']: + piece = deepcopy(element) + piece.tail = None + sub = EduModule() + text = sub.generate(piece) + luka += u" [rozwiązanie: %s]" % text + return luka + + +class Zastap(Luki): + def find_pieces(self, question): + return question.xpath(".//zastap") + + def solution(self, piece): + return piece.attrib['rozw'] + + def list_header(self): + return u"Elementy do wstawienia" + + def handle_zastap(self, element): + piece = deepcopy(element) + piece.tail = None + sub = EduModule() + text = sub.generate(piece) + if self.options['teacher'] and element.attrib.get('rozw'): + text += u" [rozwiązanie: %s]" % element.attrib.get('rozw') + return text + + +class PrawdaFalsz(Exercise): + def handle_punkt(self, element): + pre, post = super(PrawdaFalsz, self).handle_punkt(element) + if 'rozw' in element.attrib: + post += u" [Prawda/Fałsz]" + return pre, post + def fix_lists(tree): @@ -435,7 +548,7 @@ def fix_lists(tree): class EduModulePDFFormat(PDFFormat): def get_texml(self): - edumod = EduModule() + edumod = EduModule({"teacher": True}) texml = edumod.generate(fix_lists(self.wldoc.edoc.getroot())).encode('utf-8') open("/tmp/texml.xml", "w").write(texml) diff --git a/librarian/styles/wolnelektury/cover.py b/librarian/styles/wolnelektury/cover.py index b417216..8181890 100644 --- a/librarian/styles/wolnelektury/cover.py +++ b/librarian/styles/wolnelektury/cover.py @@ -3,7 +3,7 @@ # This file is part of Librarian, licensed under GNU Affero GPLv3 or later. # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -import Image, ImageFont, ImageDraw +from PIL import Image, ImageFont, ImageDraw from StringIO import StringIO from librarian import get_resource, URLOpener from librarian.cover import Cover, TextBox diff --git a/setup.py b/setup.py index a5f105d..0a3682f 100755 --- a/setup.py +++ b/setup.py @@ -28,7 +28,12 @@ setup( maintainer='Radek Czajka', maintainer_email='radoslaw.czajka@nowoczesnapolska.org.pl', url='http://github.com/fnp/librarian', - packages=['librarian'], + packages=[ + 'librarian', + 'librarian.styles', + 'librarian.styles.wolnelektury', + 'librarian.styles.wolnelektury.partners', + ], package_data={'librarian': ['xslt/*.xslt', 'epub/*', 'mobi/*', 'pdf/*', 'fb2/*', 'fonts/*'] + whole_tree(os.path.join(os.path.dirname(__file__), 'librarian'), 'font-optimizer') + whole_tree(os.path.join(os.path.dirname(__file__), 'librarian'), 'res')}, -- 2.20.1