diff --git a/src/libprs500/lrf/pylrs/__init__.py b/src/libprs500/lrf/pylrs/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/libprs500/lrf/pylrs/__init__.py @@ -0,0 +1 @@ + diff --git a/src/libprs500/lrf/pylrs/elements.py b/src/libprs500/lrf/pylrs/elements.py new file mode 100644 index 0000000000..0337277e72 --- /dev/null +++ b/src/libprs500/lrf/pylrs/elements.py @@ -0,0 +1,79 @@ +""" elements.py -- replacements and helpers for ElementTree """ + +class ElementWriter(object): + def __init__(self, e, header=False, sourceEncoding="ascii", spaceBeforeClose=True): + self.header = header + self.e = e + self.sourceEncoding=sourceEncoding + self.spaceBeforeClose = spaceBeforeClose + + + def _encodeCdata(self, rawText): + if type(rawText) is str: + rawText = rawText.decode(self.sourceEncoding) + + text = rawText.replace("&", "&") + text = text.replace("<", "<") + text = text.replace(">", ">") + return text + + + def _writeAttribute(self, f, name, value): + f.write(u' %s="' % unicode(name)) + if not isinstance(value, basestring): + value = unicode(value) + value = self._encodeCdata(value) + value = value.replace('"', '"') + f.write(value) + f.write(u'"') + + + def _writeText(self, f, rawText): + text = self._encodeCdata(rawText) + f.write(text) + + + def _write(self, f, e): + f.write(u'<' + unicode(e.tag)) + + attributes = e.items() + attributes.sort() + for name, value in attributes: + self._writeAttribute(f, name, value) + + if e.text is not None or len(e) > 0: + f.write(u'>') + + if e.text: + self._writeText(f, e.text) + + for e2 in e: + self._write(f, e2) + + f.write(u'' % e.tag) + else: + if self.spaceBeforeClose: + f.write(' ') + f.write(u'/>') + + if e.tail is not None: + self._writeText(f, e.tail) + + + def toString(self): + class x: + pass + buffer = [] + x.write = buffer.append + self.write(x) + return u''.join(buffer) + + + def write(self, f): + if self.header: + f.write(u'\n') + + self._write(f, self.e) + + + diff --git a/src/libprs500/lrf/pylrs/pylrf.py b/src/libprs500/lrf/pylrs/pylrf.py new file mode 100644 index 0000000000..d0dcaf315f --- /dev/null +++ b/src/libprs500/lrf/pylrs/pylrf.py @@ -0,0 +1,770 @@ +""" + pylrf.py -- very low level interface to create lrf files. See pylrs for + higher level interface that can use this module to render books to lrf. +""" + +import struct +import zlib +import StringIO +import codecs +import os + +from pylrfopt import tagListOptimizer + +PYLRF_VERSION = "1.0" + +# +# Acknowledgement: +# This software would not have been possible without the pioneering +# efforts of the author of lrf2lrs.py, Igor Skochinsky. +# +# Copyright (c) 2007 Mike Higgins (Falstaff) +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# +# Change History: +# +# V1.0 06 Feb 2007 +# Initial Release. + +# +# Current limitations and bugs: +# Never "scrambles" any streams (even if asked to). This does not seem +# to hurt anything. +# +# Not based on any official documentation, so many assumptions had to be made. +# +# Can be used to create lrf files that can lock up an eBook reader. +# This is your only warning. +# +# Unsupported objects: Canvas, Window, PopUpWindow, Sound, Import, +# SoundStream, ObjectInfo +# +# The only button type supported is JumpButton. +# +# Unsupported tags: SoundStop, Wait, pos on BlockSpace (and those used by +# unsupported objects). +# +# Tags supporting Japanese text and Asian layout have not been tested. +# +# Tested on Python 2.4 and 2.5, Windows XP and Sony PRS-500. +# +# Commented even less than pylrs, but not very useful when called directly, +# anyway. +# + +def writeByte(f, byte): + f.write(struct.pack("I", color)) + +def writeLineWidth(f, width): + writeWord(f, int(width)//5) + +def writeUnicode(f, string, encoding): + if isinstance(string, str): + string = string.decode(encoding) + + string = string.encode("utf-16-le") + writeWord(f, len(string)) + writeString(f, string) + +def writeRaw(f, string, encoding): + if isinstance(string, str): + string = string.decode(encoding) + + string = string.encode("utf-16-le") + writeString(f, string) + +def writeRubyAA(f, rubyAA): + ralign, radjust = rubyAA + radjust = {"line-edge":0x10, "none":0}[radjust] + ralign = {"start":1, "center":2}[ralign] + writeWord(f, ralign | radjust) + +def writeBgImage(f, bgInfo): + imode, iid = bgInfo + imode = {"pfix": 0, "fix":1, "tile":2, "centering":3}[imode] + writeWord(f, imode) + writeDWord(f, iid) + +def writeEmpDots(f, dotsInfo, encoding): + refDotsFont, dotsFontName, dotsCode = dotsInfo + writeDWord(f, refDotsFont) + LrfTag("fontfacename", dotsFontName).write(f, encoding) + writeWord(f, int(dotsCode, 0)) + +def writeRuledLine(f, lineInfo): + lineLength, lineType, lineWidth, lineColor = lineInfo + writeWord(f, lineLength) + writeWord(f, LINE_TYPE_ENCODING[lineType]) + writeWord(f, lineWidth) + writeColor(f, lineColor) + + +LRF_SIGNATURE = "L\x00R\x00F\x00\x00\x00" + +#XOR_KEY = 48 +XOR_KEY = 65024 # that's what lrf2lrs says -- not used, anyway... + +LRF_VERSION = 1000 # is 999 for librie? lrf2lrs uses 1000 + +IMAGE_TYPE_ENCODING = dict(GIF=0x14, PNG=0x12, BMP=0x13, JPEG=0x11, JPG=0x11) + +OBJECT_TYPE_ENCODING = dict( + PageTree = 0x01, + Page = 0x02, + Header = 0x03, + Footer = 0x04, + PageAtr = 0x05, PageStyle=0x05, + Block = 0x06, + BlockAtr = 0x07, BlockStyle=0x07, + MiniPage = 0x08, + TextBlock = 0x0A, Text=0x0A, + TextAtr = 0x0B, TextStyle=0x0B, + ImageBlock = 0x0C, Image=0x0C, + Canvas = 0x0D, + ESound = 0x0E, + ImageStream = 0x11, + Import = 0x12, + Button = 0x13, + Window = 0x14, + PopUpWindow = 0x15, + Sound = 0x16, + SoundStream = 0x17, + Font = 0x19, + ObjectInfo = 0x1A, + BookAtr = 0x1C, BookStyle=0x1C, + SimpleTextBlock = 0x1D, + TOC=0x1E +) + +LINE_TYPE_ENCODING = { + 'none':0, 'solid':0x10, 'dashed':0x20, 'double':0x30, 'dotted':0x40 +} + +BINDING_DIRECTION_ENCODING = dict(Lr=1, Rl=16) + + +TAG_INFO = dict( + rawtext = (0, writeRaw), + ObjectStart = (0xF500, " 1: + raise LrfError("only one parameter allowed on tag %s" % name) + + if len(parameters) == 0: + self.parameter = None + else: + self.parameter = parameters[0] + + + def write(self, lrf, encoding=None): + if self.type != 0: + writeWord(lrf, self.type) + + p = self.parameter + if p is None: + return + + #print " Writing tag", self.name + for f in self.format: + if isinstance(f, dict): + p = f[p] + elif isinstance(f, str): + if isinstance(p, tuple): + writeString(lrf, struct.pack(f, *p)) + else: + writeString(lrf, struct.pack(f, p)) + else: + if f in [writeUnicode, writeRaw, writeEmpDots]: + if encoding is None: + raise LrfError, "Tag requires encoding" + f(lrf, p, encoding) + else: + f(lrf, p) + + +STREAM_SCRAMBLED = 0x200 +STREAM_COMPRESSED = 0x100 +STREAM_FORCE_COMPRESSED = 0x8100 +STREAM_TOC = 0x0051 + +class LrfStreamBase(object): + def __init__(self, streamFlags, streamData=None): + self.streamFlags = streamFlags + self.streamData = streamData + + + def setStreamData(self, streamData): + self.streamData = streamData + + + def getStreamTags(self, optimize=False): + # tags: + # StreamFlags + # StreamSize + # StreamStart + # (data) + # StreamEnd + # + # if flags & 0x200, stream is scrambled + # if flags & 0x100, stream is compressed + + + flags = self.streamFlags + streamBuffer = self.streamData + + # implement scramble? I never scramble anything... + + if flags & STREAM_FORCE_COMPRESSED == STREAM_FORCE_COMPRESSED: + optimize = False + + if flags & STREAM_COMPRESSED == STREAM_COMPRESSED: + uncompLen = len(streamBuffer) + compStreamBuffer = zlib.compress(streamBuffer) + if optimize and uncompLen <= len(compStreamBuffer) + 4: + flags &= ~STREAM_COMPRESSED + else: + streamBuffer = struct.pack(" 0 and tagList[-1].name == tagName: + del tagList[-1] + + +def tagListOptimizer(tagList): + # this function eliminates redundant or unnecessary tags + # it scans a list of tags, looking for text settings that are + # changed before any text is output + # for example, + # fontsize=100, fontsize=200, text, fontsize=100, fontsize=200 + # should be: + # fontsize=200 text + oldSize = len(tagList) + _optimize(tagList, "fontsize", int) + _optimize(tagList, "fontweight", int) + return oldSize - len(tagList) + + diff --git a/src/libprs500/lrf/pylrs/pylrs.py b/src/libprs500/lrf/pylrs/pylrs.py new file mode 100644 index 0000000000..4953596002 --- /dev/null +++ b/src/libprs500/lrf/pylrs/pylrs.py @@ -0,0 +1,2139 @@ +""" + pylrs.py -- a package to create LRS (and LRF) e-Books for the Sony PRS-500. +""" + +import os +import re +import codecs +import sys +from datetime import date +try: + from elementtree.ElementTree import (Element, SubElement) +except ImportError: + from xml.etree.ElementTree import (Element, SubElement) + +from elements import ElementWriter +from pylrf import (LrfWriter, LrfObject, LrfTag, LrfToc, + STREAM_COMPRESSED, LrfTagStream, LrfStreamBase, IMAGE_TYPE_ENCODING, + BINDING_DIRECTION_ENCODING, LINE_TYPE_ENCODING, LrfFileStream, + STREAM_FORCE_COMPRESSED) + +PYLRS_VERSION = "1.0" + +DEFAULT_SOURCE_ENCODING = "cp1252" # defualt is us-windows character set +DEFAULT_GENREADING = "f" # default is yes to lrf, no to lrs + +# +# Acknowledgement: +# This software would not have been possible without the pioneering +# efforts of the author of lrf2lrs.py, Igor Skochinsky. +# +# Copyright (c) 2007 Mike Higgins (Falstaff) +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# Check www.falstaffshouse.com for possible updates to this code. +# Email contact: falstaff (at) falstaffshouse.com + +# +# Change History: +# +# V1.0 06 Feb 2007 +# Initial Release. +# + +# +# Current limitations and bugs: +# Bug: using two instances of Book() at the same time can cause +# incorrect output if any default styles are used. Workaround: +# supply all styles explicitly, or use only one Book class at a time. +# Bug: Does not check if most setting values are valid unless lrf is created. +# +# Unsupported objects: MiniPage, SimpleTextBlock, Canvas, Window, +# PopUpWindow, Sound, Import, SoundStream, +# ObjectInfo +# +# Does not support background images for blocks or pages. +# +# The only button type supported are JumpButtons. +# +# None of the Japanese language tags are supported. +# +# Other unsupported tags: PageDiv, SoundStop, Wait, pos, +# Plot, Image (outside of ImageBlock), +# EmpLine, EmpDots +# +# Tested on Python 2.4 and 2.5, Windows XP and PRS-500. +# + +class LrsError(Exception): + pass + + +def _checkExists(filename): + if not os.path.exists(filename): + raise LrsError, "file '%s' not found" % filename + + +def _formatXml(root): + """ A helper to make the LRS output look nicer. """ + for elem in root.getiterator(): + if len(elem) > 0 and (not elem.text or not elem.text.strip()): + elem.text = "\n" + if not elem.tail or not elem.tail.strip(): + elem.tail = "\n" + + + +def ElementWithText(tag, text, **extra): + """ A shorthand function to create Elements with text. """ + e = Element(tag, **extra) + e.text = text + return e + + + +def ElementWithReading(tag, text, reading=False): + """ A helper function that creates reading attributes. """ + + # note: old lrs2lrf parser only allows reading = "" + + if text is None: + readingText = "" + elif isinstance(text, basestring): + readingText = text + else: + # assumed to be a sequence of (name, sortas) + readingText = text[1] + text = text[0] + + if not reading: + readingText = "" + + return ElementWithText(tag, text, reading=readingText) + + + +def appendTextElements(e, contentsList, se): + """ A helper function to convert text streams into the proper elements. """ + + def uconcat(text, newText, se): + if type(newText) != type(text): + if type(text) is str: + text = text.decode(se) + else: + newText = newText.decode(se) + + return text + newText + + + e.text = "" + lastElement = None + + for content in contentsList: + if not isinstance(content, Text): + newElement = content.toElement(se) + if newElement is None: + continue + lastElement = newElement + lastElement.tail = "" + e.append(lastElement) + else: + if lastElement is None: + e.text = uconcat(e.text, content.text, se) + else: + lastElement.tail = uconcat(lastElement.tail, content.text, se) + + + +class Delegator(object): + """ A mixin class to create delegated methods that create elements. """ + def __init__(self, delegates): + self.delegates = delegates + self.delegatedMethods = [] + #self.delegatedSettingsDict = {} + #self.delegatedSettings = [] + for d in delegates: + d.parent = self + methods = d.getMethods() + self.delegatedMethods += methods + for m in methods: + setattr(self, m, getattr(d, m)) + + """ + for setting in d.getSettings(): + if isinstance(setting, basestring): + setting = (d, setting) + delegates = \ + self.delegatedSettingsDict.setdefault(setting[1], []) + delegates.append(setting[0]) + self.delegatedSettings.append(setting) + """ + + + def applySetting(self, name, value, testValid=False): + applied = False + if name in self.getSettings(): + setattr(self, name, value) + applied = True + + for d in self.delegates: + if hasattr(d, "applySetting"): + applied = applied or d.applySetting(name, value) + else: + if name in d.getSettings(): + setattr(d, name, value) + applied = True + + if testValid and not applied: + raise LrsError, "setting %s not valid" % name + + return applied + + + def applySettings(self, settings, testValid=False): + for (setting, value) in settings.items(): + self.applySetting(setting, value, testValid) + """ + if setting not in self.delegatedSettingsDict: + raise LrsError, "setting %s not valid" % setting + delegates = self.delegatedSettingsDict[setting] + for d in delegates: + setattr(d, setting, value) + """ + + + def appendDelegates(self, element, sourceEncoding): + for d in self.delegates: + e = d.toElement(sourceEncoding) + if e is not None: + if isinstance(e, list): + for e1 in e: element.append(e1) + else: + element.append(e) + + + def appendReferencedObjects(self, parent): + for d in self.delegates: + d.appendReferencedObjects(parent) + + + def getMethods(self): + return self.delegatedMethods + + + def getSettings(self): + return [] + + + def toLrfDelegates(self, lrfWriter): + for d in self.delegates: + d.toLrf(lrfWriter) + + + def toLrf(self, lrfWriter): + self.toLrfDelegates(lrfWriter) + + + +class LrsAttributes(object): + """ A mixin class to handle default and user supplied attributes. """ + def __init__(self, defaults, alsoAllow=None, **settings): + if alsoAllow is None: + alsoAllow = [] + self.attrs = defaults.copy() + for (name, value) in settings.items(): + if name not in self.attrs and name not in alsoAllow: + raise LrsError, "%s does not support setting %s" % \ + (self.__class__.__name__, name) + if type(value) is int: + value = str(value) + self.attrs[name] = value + + +class LrsContainer(object): + """ This class is a mixin class for elements that are contained in or + contain an unknown number of other elements. + """ + def __init__(self, validChildren): + self.parent = None + self.contents = [] + self.validChildren = validChildren + + + def appendReferencedObjects(self, parent): + for c in self.contents: + c.appendReferencedObjects(parent) + + + def setParent(self, parent): + if self.parent is not None: + raise LrsError, "object already has parent" + + self.parent = parent + + + def append(self, content, convertText=True): + """ + Appends valid objects to container. Can auto-covert text strings + to Text objects. + """ + for validChild in self.validChildren: + if isinstance(content, validChild): + break + else: + raise LrsError, "can't append %s to %s" % \ + (content.__class__.__name__, + self.__class__.__name__) + + if convertText and isinstance(content, basestring): + content = Text(content) + + content.setParent(self) + + if isinstance(content, LrsObject): + content.assignId() + + self.contents.append(content) + return self + + + +class LrsObject(object): + """ A mixin class for elements that need an object id. """ + NextObjId = 0 + + @classmethod + def getNextObjId(selfClass): + selfClass.NextObjId += 1 + return selfClass.NextObjId + + def __init__(self, assignId=False): + if assignId: + self.objId = LrsObject.getNextObjId() + else: + self.objId = 0 + + + def assignId(self): + if self.objId != 0: + raise LrsError, "id already assigned to " + self.__class__.__name__ + + self.objId = LrsObject.getNextObjId() + + + def lrsObjectElement(self, name, objlabel="objlabel", labelName=None, + labelDecorate=True, **settings): + element = Element(name) + element.attrib["objid"] = str(self.objId) + if labelName is None: + labelName = name + if labelDecorate: + label = "%s.%d" % (labelName, self.objId) + else: + label = str(self.objId) + element.attrib[objlabel] = label + element.attrib.update(settings) + return element + + + +class Book(Delegator): + """ + Main class for any lrs or lrf. All objects must be appended to + the Book class in some way or another in order to be rendered as + an LRS or LRF file. + + The following settings are available on the contructor of Book: + + author="book author" or author=("book author, "sort as") + Author of the book. + + title="book title" or title=("book title", "sort as") + Title of the book. + + sourceencoding="codec" + Gives the assumed encoding for all non-unicode strings. + + + thumbnail="thumbnail file name" + A small (80x80?) graphics file with a thumbnail of the book's cover. + + bookid="book id" + A unique id for the book. + + textstyledefault= + Sets the default values for all TextStyles. + + pagetstyledefault= + Sets the default values for all PageStyles. + + blockstyledefault= + Sets the default values for all BlockStyles. + + booksetting=BookSetting() + Override the default BookSetting. + + setdefault=SetDefault() + Override the defalut SetDefault. + + There are several other settings -- see the BookInfo class for more. + """ + def __init__(self, textstyledefault=None, blockstyledefault=None, + pagestyledefault=None, + optimizeTags=False, + optimizeCompression=False, + **settings): + + self.parent = None # we are the top of the parent chain + + if "thumbnail" in settings: + _checkExists(settings["thumbnail"]) + + # highly experimental -- use with caution + self.optimizeTags = optimizeTags + self.optimizeCompression = optimizeCompression + + TextStyle.resetDefaults() + BlockStyle.resetDefaults() + PageStyle.resetDefaults() + + if textstyledefault is not None: + TextStyle.setDefaults(textstyledefault) + + if blockstyledefault is not None: + BlockStyle.setDefaults(blockstyledefault) + + if pagestyledefault is not None: + PageStyle.setDefaults(pagestyledefault) + + Page.defaultPageStyle = PageStyle() + TextBlock.defaultTextStyle = TextStyle() + TextBlock.defaultBlockStyle = BlockStyle() + LrsObject.nextObjId = 1 + + Delegator.__init__(self, [BookInformation(), Main(), + Template(), Style(), Solos(), Objects()]) + + self.sourceencoding = None + + # apply default settings + self.applySetting("genreading", DEFAULT_GENREADING) + self.applySetting("sourceencoding", DEFAULT_SOURCE_ENCODING) + + self.applySettings(settings, testValid=True) + + + def getSettings(self): + return ["sourceencoding"] + + + def append(self, content): + """ Find and invoke the correct appender for this content. """ + + className = content.__class__.__name__ + try: + method = getattr(self, "append" + className) + except AttributeError: + raise LrsError, "can't append %s to Book" % className + + method(content) + + + def renderLrs(self, lrsFilename): + lrsFile = codecs.open(lrsFilename, "wb", encoding="utf-16") + self.render(lrsFile) + lrsFile.close() + + + def renderLrf(self, lrfFilename): + self.appendReferencedObjects(self) + lrfFile = file(lrfFilename, "wb") + lrfWriter = LrfWriter(self.sourceencoding) + + lrfWriter.optimizeTags = self.optimizeTags + lrfWriter.optimizeCompression = self.optimizeCompression + + self.toLrf(lrfWriter) + lrfWriter.writeFile(lrfFile) + lrfFile.close() + + + def toElement(self, se): + root = Element("BBeBXylog", version="1.0") + root.append(Element("Property")) + self.appendDelegates(root, self.sourceencoding) + return root + + + def render(self, f): + """ Write the book as an LRS to file f. """ + + self.appendReferencedObjects(self) + + # create the root node, and populate with the parts of the book + + root = self.toElement(self.sourceencoding) + + # now, add some newlines to make it easier to look at + + _formatXml(root) + + writer = ElementWriter(root, header=True, + sourceEncoding=self.sourceencoding, + spaceBeforeClose=False) + writer.write(f) + + + + + + + + +class BookInformation(Delegator): + """ Just a container for the Info and TableOfContents elements. """ + def __init__(self): + Delegator.__init__(self, [Info(), TableOfContents()]) + + + def toElement(self, se): + bi = Element("BookInformation") + self.appendDelegates(bi, se) + return bi + + + +class Info(Delegator): + """ Just a container for the BookInfo and DocInfo elements. """ + def __init__(self): + self.genreading = DEFAULT_GENREADING + Delegator.__init__(self, [BookInfo(), DocInfo()]) + + + def getSettings(self): + return ["genreading"] #+ self.delegatedSettings + + + def toElement(self, se): + info = Element("Info", version="1.1") + info.append( + self.delegates[0].toElement(se, reading="s" in self.genreading)) + info.append(self.delegates[1].toElement(se)) + return info + + + def toLrf(self, lrfWriter): + # this info is set in XML form in the LRF + info = Element("Info", version="1.1") + #self.appendDelegates(info) + info.append( + self.delegates[0].toElement(lrfWriter.getSourceEncoding(), reading="f" in self.genreading)) + info.append(self.delegates[1].toElement(lrfWriter.getSourceEncoding())) + + # look for the thumbnail file and get the filename + tnail = info.find("DocInfo/CThumbnail") + if tnail is not None: + lrfWriter.setThumbnailFile(tnail.get("file")) + # does not work: info.remove(tnail) + + + _formatXml(info) + + # fix up the doc info to match the LRF format + # NB: generates an encoding attribute, which lrs2lrf does not + xmlInfo = ElementWriter(info, header=True, sourceEncoding=lrfWriter.getSourceEncoding(), + spaceBeforeClose=False).toString() + + xmlInfo = re.sub(r"\n", "", xmlInfo) + xmlInfo = xmlInfo.replace("SumPage>", "Page>") + lrfWriter.docInfoXml = xmlInfo + + + +class TableOfContents(object): + def __init__(self): + self.tocEntries = [] + + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return ["addTocEntry"] + + + def getSettings(self): + return [] + + + def addTocEntry(self, tocLabel, textBlock): + if not isinstance(textBlock, TextBlock) and not isinstance(textBlock, ImageBlock): + raise LrsError, "TOC destination must be a TextBlock or an ImageBlock" + + if textBlock.parent is None or not isinstance(textBlock.parent, Page): + raise LrsError, "TOC text block must be already appended to a page" + + if textBlock.parent.parent is None: + raise LrsError, \ + "TOC destination page must be already appended to a book" + + self.tocEntries.append(TocLabel(tocLabel, textBlock)) + textBlock.tocLabel = tocLabel + + + def toElement(self, se): + if len(self.tocEntries) == 0: + return None + + toc = Element("TOC") + + for t in self.tocEntries: + toc.append(t.toElement(se)) + + return toc + + + def toLrf(self, lrfWriter): + if len(self.tocEntries) == 0: + return + + toc = [] + for t in self.tocEntries: + toc.append((t.textBlock.parent.objId, t.textBlock.objId, t.label)) + + lrfToc = LrfToc(LrsObject.getNextObjId(), toc, lrfWriter.getSourceEncoding()) + lrfWriter.append(lrfToc) + lrfWriter.setTocObject(lrfToc) + + + +class TocLabel(object): + def __init__(self, label, textBlock): + self.label = label + self.textBlock = textBlock + + + def toElement(self, se): + return ElementWithText("TocLabel", self.label, + refobj=str(self.textBlock.objId), + refpage=str(self.textBlock.parent.objId)) + + + +class BookInfo(object): + def __init__(self): + self.title = "Untitled" + self.author = "Anonymous" + self.bookid = None + self.pi = None + self.isbn = None + self.publisher = None + self.freetext = "\n\n" + self.label = None + self.category = None + self.classification = None + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return [] + + + def getSettings(self): + return ["author", "title", "bookid", "isbn", "publisher", + "freetext", "label", "category", "classification"] + + + def _appendISBN(self, bi): + pi = Element("ProductIdentifier") + isbnElement = ElementWithText("ISBNPrintable", self.isbn) + isbnValueElement = ElementWithText("ISBNValue", + self.isbn.replace("-", "")) + + pi.append(isbnElement) + pi.append(isbnValueElement) + bi.append(pi) + + + def toElement(self, se, reading=False): + bi = Element("BookInfo") + bi.append(ElementWithReading("Title", self.title, reading=reading)) + bi.append(ElementWithReading("Author", self.author, reading=reading)) + bi.append(ElementWithText("BookID", self.bookid)) + if self.isbn is not None: + self._appendISBN(bi) + + if self.publisher is not None: + bi.append(ElementWithReading("Publisher", self.publisher)) + + bi.append(ElementWithReading("Label", self.label, reading=reading)) + bi.append(ElementWithText("Category", self.category)) + bi.append(ElementWithText("Classification", self.classification)) + bi.append(ElementWithText("FreeText", self.freetext)) + return bi + + + +class DocInfo(object): + def __init__(self): + self.thumbnail = None + self.language = "en" + self.creator = None + self.creationdate = date.today().isoformat() + self.producer = "pylrs 0.1" + self.numberofpages = "0" + + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return [] + + + def getSettings(self): + return ["thumbnail", "language", "creator", "creationdate", + "producer", "numberofpages"] + + + def toElement(self, se): + docInfo = Element("DocInfo") + + if self.thumbnail is not None: + docInfo.append(Element("CThumbnail", file=self.thumbnail)) + + docInfo.append(ElementWithText("Language", self.language)) + docInfo.append(ElementWithText("Creator", self.creator)) + docInfo.append(ElementWithText("CreationDate", self.creationdate)) + docInfo.append(ElementWithText("Producer", self.producer)) + docInfo.append(ElementWithText("SumPage", str(self.numberofpages))) + return docInfo + + + +class Main(LrsContainer): + def __init__(self): + LrsContainer.__init__(self, [Page]) + + + def getMethods(self): + return ["appendPage", "Page"] + + + def getSettings(self): + return [] + + + def Page(self, *args, **kwargs): + p = Page(*args, **kwargs) + self.append(p) + return p + + + def appendPage(self, page): + self.append(page) + + + def toElement(self, sourceEncoding): + main = Element(self.__class__.__name__) + + for page in self.contents: + main.append(page.toElement(sourceEncoding)) + + return main + + + def toLrf(self, lrfWriter): + pageIds = [] + + # set this id now so that pages can see it + pageTreeId = LrsObject.getNextObjId() + lrfWriter.setPageTreeId(pageTreeId) + + # create a list of all the page object ids while dumping the pages + + for p in self.contents: + pageIds.append(p.objId) + p.toLrf(lrfWriter) + + # create a page tree object + + pageTree = LrfObject("PageTree", pageTreeId) + pageTree.appendLrfTag(LrfTag("PageList", pageIds)) + + lrfWriter.append(pageTree) + + + +class Solos(LrsContainer): + def __init__(self): + LrsContainer.__init__(self, [Solo]) + + + def getMethods(self): + return ["appendSolo", "Solo"] + + + def getSettings(self): + return [] + + + def Solo(self, *args, **kwargs): + p = Solo(*args, **kwargs) + self.append(p) + return p + + + def appendSolo(self, solo): + self.append(solo) + + + def toLrf(self, lrfWriter): + for s in self.contents: + s.toLrf(lrfWriter) + + + def toElement(self, se): + solos = [] + for s in self.contents: + solos.append(s.toElement(se)) + + if len(solos) == 0: + return None + + + return solos + + + +class Solo(Main): + pass + + +class Template(object): + """ Does nothing that I know of. """ + + def appendReferencedObjects(self, parent): + pass + + + def getMethods(self): + return [] + + + def getSettings(self): + return [] + + + def toElement(self, se): + t = Element("Template") + t.attrib["version"] = "1.0" + return t + + def toLrf(self, lrfWriter): + # does nothing + pass + + + +class Style(LrsContainer, Delegator): + def __init__(self): + LrsContainer.__init__(self, [PageStyle, TextStyle, BlockStyle]) + Delegator.__init__(self, [BookStyle()]) + self.bookStyle = self.delegates[0] + self.appendPageStyle = self.appendTextStyle = \ + self.appendBlockStyle = self.append + + + def appendReferencedObjects(self, parent): + LrsContainer.appendReferencedObjects(self, parent) + + + def getMethods(self): + return ["PageStyle", "TextStyle", "BlockStyle", + "appendPageStyle", "appendTextStyle", "appendBlockStyle"] + \ + self.delegatedMethods + + + + def getSettings(self): + return [(self.bookStyle, x) for x in self.bookStyle.getSettings()] + + + def PageStyle(self, *args, **kwargs): + ps = PageStyle(*args, **kwargs) + self.append(ps) + return ps + + + def TextStyle(self, *args, **kwargs): + ts = TextStyle(*args, **kwargs) + self.append(ts) + return ts + + + def BlockStyle(self, *args, **kwargs): + bs = BlockStyle(*args, **kwargs) + self.append(bs) + return bs + + + def toElement(self, se): + style = Element("Style") + style.append(self.bookStyle.toElement(se)) + + for content in self.contents: + style.append(content.toElement(se)) + + return style + + + def toLrf(self, lrfWriter): + self.bookStyle.toLrf(lrfWriter) + + for s in self.contents: + s.toLrf(lrfWriter) + + + +class BookStyle(LrsObject, LrsContainer): + def __init__(self): + LrsObject.__init__(self, assignId=True) + LrsContainer.__init__(self, [Font]) + self.styledefault = StyleDefault() + self.booksetting = BookSetting() + self.appendFont = self.append + + + def getSettings(self): + return ["styledefault", "booksetting"] + + + def getMethods(self): + return ["Font", "appendFont"] + + + def Font(self, *args, **kwargs): + f = Font(*args, **kwargs) + self.append(f) + return + + + def toElement(self, se): + bookStyle = self.lrsObjectElement("BookStyle", objlabel="stylelabel", + labelDecorate=False) + bookStyle.append(self.styledefault.toElement(se)) + bookStyle.append(self.booksetting.toElement(se)) + for font in self.contents: + bookStyle.append(font.toElement(se)) + + return bookStyle + + + def toLrf(self, lrfWriter): + bookAtr = LrfObject("BookAtr", self.objId) + bookAtr.appendLrfTag(LrfTag("ChildPageTree", lrfWriter.getPageTreeId())) + bookAtr.appendTagDict(self.styledefault.attrs) + + self.booksetting.toLrf(lrfWriter) + + lrfWriter.append(bookAtr) + lrfWriter.setRootObject(bookAtr) + + for font in self.contents: + font.toLrf(lrfWriter) + + + +class StyleDefault(LrsAttributes): + """ + Supply some defaults for all TextBlocks. + The legal values are a subset of what is allowed on a + TextBlock -- ruby, emphasis, and waitprop settings. + """ + defaults = dict(rubyalign="start", rubyadjust="none", + rubyoverhang="none", empdotsposition="before", + empdotsfontname="Dutch801 Rm BT Roman", + empdotscode="0x002e", emplineposition="before", + emplinetype = "solid", setwaitprop="noreplay") + + alsoAllow = ["refempdotsfont"] + + def __init__(self, **settings): + LrsAttributes.__init__(self, self.defaults, + alsoAllow=self.alsoAllow, **settings) + + + def toElement(self, se): + return Element("SetDefault", self.attrs) + + + +class BookSetting(LrsAttributes): + def __init__(self, **settings): + defaults = dict(bindingdirection="Lr", dpi="1600", + screenheight="800", screenwidth="600", colordepth="24") + LrsAttributes.__init__(self, defaults, **settings) + + + def toLrf(self, lrfWriter): + a = self.attrs + lrfWriter.dpi = int(a["dpi"]) + lrfWriter.bindingdirection = \ + BINDING_DIRECTION_ENCODING[a["bindingdirection"]] + lrfWriter.height = int(a["screenheight"]) + lrfWriter.width = int(a["screenwidth"]) + lrfWriter.colorDepth = int(a["colordepth"]) + + def toElement(self, se): + return Element("BookSetting", self.attrs) + + + +class LrsStyle(LrsObject, LrsAttributes, LrsContainer): + """ A mixin class for styles. """ + def __init__(self, elementName, defaults=None, alsoAllow=None, **overrides): + if defaults is None: + defaults = {} + + LrsObject.__init__(self) + LrsAttributes.__init__(self, defaults, alsoAllow=alsoAllow, **overrides) + LrsContainer.__init__(self, []) + self.elementName = elementName + self.objectsAppended = False + #self.label = "%s.%d" % (elementName, self.objId) + #self.label = str(self.objId) + #self.parent = None + + + @classmethod + def resetDefaults(selfClass): + selfClass.defaults = selfClass.baseDefaults.copy() + + + @classmethod + def setDefaults(selfClass, settings): + for name, value in settings.items(): + if name not in selfClass.validSettings: + raise LrsError, "default setting %s not recognized" + selfClass.defaults[name] = value + + + def getLabel(self): + return str(self.objId) + + + def toElement(self, se): + element = Element(self.elementName, stylelabel=self.getLabel(), + objid=str(self.objId)) + element.attrib.update(self.attrs) + return element + + + def toLrf(self, lrfWriter): + obj = LrfObject(self.elementName, self.objId) + obj.appendTagDict(self.attrs, self.__class__.__name__) + lrfWriter.append(obj) + + +class TextStyle(LrsStyle): + """ + The text style of a TextBlock. Default is 10 pt. Times Roman. + + Setting Value Default + -------- ----- ------- + align "head","center","foot" "head" (left aligned) + baselineskip points * 10 120 (12 pt. distance between + bottoms of lines) + fontsize points * 10 100 (10 pt.) + fontweight 1 to 1000 400 (normal, 800 is bold) + fontwidth points * 10 or -10 -10 (use values from font) + linespace points * 10 10 (min space btw. lines?) + wordspace points * 10 25 (min space btw. each word) + + """ + baseDefaults = dict( + columnsep="0", charspace="0", + textlinewidth="0", align="head", linecolor="0x00000000", + column="1", fontsize="100", fontwidth="-10", fontescapement="0", + fontorientation="0", fontweight="400", + fontfacename="Dutch801 Rm BT Roman", + textcolor="0x00000000", wordspace="25", letterspace="0", + baselineskip="120", linespace="10", parindent="0", parskip="0", + textbgcolor="0xFF000000") + + alsoAllow = ["empdotscode", "empdotsfontname", "refempdotsfont", + "rubyadjust", "rubyalign", "rubyoverhang", + "empdotsposition", "emplineposition", "emplinetype"] + + validSettings = baseDefaults.keys() + alsoAllow + + defaults = baseDefaults.copy() + + def __init__(self, **overrides): + LrsStyle.__init__(self, "TextStyle", self.defaults, + alsoAllow=self.alsoAllow, **overrides) + + def copy(self): + tb = TextStyle() + tb.attrs = self.attrs.copy() + return tb + + + +class BlockStyle(LrsStyle): + """ + The block style of a TextBlock. Default is an expandable 560 pixel + wide area with no space for headers or footers. + + Setting Value Default + -------- ----- ------- + blockwidth pixels 560 + sidemargin pixels 0 + """ + + baseDefaults = dict( + bgimagemode="fix", framemode="square", blockwidth="560", + blockheight="100", blockrule="horz-adjustable", layout="LrTb", + framewidth="0", framecolor="0x00000000", topskip="0", + sidemargin="0", footskip="0", bgcolor="0xFF000000") + + validSettings = baseDefaults.keys() + defaults = baseDefaults.copy() + + def __init__(self, **overrides): + LrsStyle.__init__(self, "BlockStyle", self.defaults, **overrides) + + + +class PageStyle(LrsStyle): + """ + Setting Value Default + -------- ----- ------- + evensidemargin pixels 20 + oddsidemargin pixels 20 + topmargin pixels 20 + """ + baseDefaults = dict( + topmargin="20", headheight="0", headsep="0", + oddsidemargin="20", textheight="800", textwidth="600", + footspace="0", evensidemargin="20", footheight="0", + layout="LrTb", bgimagemode="fix", pageposition="any", + setwaitprop="noreplay", setemptyview="show") + + alsoAllow = ["header", "evenheader", "oddheader", + "footer", "evenfooter", "oddfooter"] + + validSettings = baseDefaults.keys() + alsoAllow + defaults = baseDefaults.copy() + + @classmethod + def translateHeaderAndFooter(selfClass, parent, settings): + selfClass._fixup(parent, "header", settings) + selfClass._fixup(parent, "footer", settings) + + + @classmethod + def _fixup(selfClass, parent, basename, settings): + evenbase = "even" + basename + oddbase = "odd" + basename + if basename in settings: + baseObj = settings[basename] + del settings[basename] + settings[evenbase] = settings[oddbase] = baseObj + + if evenbase in settings: + evenObj = settings[evenbase] + del settings[evenbase] + if evenObj.parent is None: + parent.append(evenObj) + settings[evenbase + "id"] = str(evenObj.objId) + + if oddbase in settings: + oddObj = settings[oddbase] + del settings[oddbase] + if oddObj.parent is None: + parent.append(oddObj) + settings[oddbase + "id"] = str(oddObj.objId) + + + def appendReferencedObjects(self, parent): + if self.objectsAppended: + return + PageStyle.translateHeaderAndFooter(parent, self.attrs) + self.objectsAppended = True + + + + def __init__(self, **settings): + #self.fixHeaderSettings(settings) + LrsStyle.__init__(self, "PageStyle", self.defaults, + alsoAllow=self.alsoAllow, **settings) + + +class Page(LrsObject, LrsContainer): + """ + Pages are added to Books. Pages can be supplied a PageStyle. + If they are not, Page.defaultPageStyle will be used. + """ + defaultPageStyle = PageStyle() + + def __init__(self, *args, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [TextBlock, BlockSpace, RuledLine, + ImageBlock]) + + if len(args) > 0: + pageStyle = args[0] + else: + pageStyle = Page.defaultPageStyle + + self.pageStyle = pageStyle + + for settingName in settings.keys(): + if settingName not in PageStyle.defaults and \ + settingName not in PageStyle.alsoAllow: + raise LrsError, "setting %s not allowed on Page" % settingName + + # TODO: should these be copied? + self.settings = settings + + + def appendReferencedObjects(self, parent): + PageStyle.translateHeaderAndFooter(parent, self.settings) + + self.pageStyle.appendReferencedObjects(parent) + + if self.pageStyle.parent is None: + parent.append(self.pageStyle) + + LrsContainer.appendReferencedObjects(self, parent) + + + def RuledLine(self, *args, **kwargs): + rl = RuledLine(*args, **kwargs) + self.append(rl) + return rl + + + def BlockSpace(self, *args, **kwargs): + bs = BlockSpace(*args, **kwargs) + self.append(bs) + return bs + + + def TextBlock(self, *args, **kwargs): + """ Create and append a new text block (shortcut). """ + tb = TextBlock(*args, **kwargs) + self.append(tb) + return tb + + + def ImageBlock(self, *args, **kwargs): + """ Create and append and new Image block (shorthand). """ + ib = ImageBlock(*args, **kwargs) + self.append(ib) + return ib + + + def addLrfObject(self, objId): + self.stream.appendLrfTag(LrfTag("Link", objId)) + + + def appendLrfTag(self, lrfTag): + self.stream.appendLrfTag(lrfTag) + + + def toLrf(self, lrfWriter): + # tags: + # ObjectList + # Link to pagestyle + # Parent page tree id + # stream of tags + + p = LrfObject("Page", self.objId) + lrfWriter.append(p) + + pageContent = set() + self.stream = LrfTagStream(0) + for content in self.contents: + content.toLrfContainer(lrfWriter, self) + if hasattr(content, "getReferencedObjIds"): + pageContent.update(content.getReferencedObjIds()) + + + #print "page contents:", pageContent + p.appendLrfTag(LrfTag("ObjectList", pageContent)) + p.appendLrfTag(LrfTag("Link", self.pageStyle.objId)) + p.appendLrfTag(LrfTag("ParentPageTree", lrfWriter.getPageTreeId())) + p.appendTagDict(self.settings) + p.appendLrfTags(self.stream.getStreamTags(lrfWriter.getSourceEncoding())) + + + def toElement(self, sourceEncoding): + page = self.lrsObjectElement("Page") + page.set("pagestyle", self.pageStyle.getLabel()) + page.attrib.update(self.settings) + + for content in self.contents: + page.append(content.toElement(sourceEncoding)) + + return page + + + + + +class TextBlock(LrsObject, LrsContainer): + """ + TextBlocks are added to Pages. They hold Paragraphs or CRs. + TextBlocks can be supplied a TextStyle and a BlockStyle as the first + two arguments to the constructor, but these can be left off + and defaults will be used (since the spec says you have to have + them). + + If a TextBlock is used in a header, it should be appended to + the Book, not to a specific Page. + """ + defaultTextStyle = TextStyle() + defaultBlockStyle = BlockStyle() + + def __init__(self, *args, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [Paragraph, CR]) + + textStyle = TextBlock.defaultTextStyle + blockStyle = TextBlock.defaultBlockStyle + if len(args) > 0: + textStyle = args[0] + if len(args) > 1: + blockStyle = args[1] + if len(args) > 2: + raise LrsError, \ + "too many style arguments to TextBlock" + + self.textSettings = {} + self.blockSettings = {} + for name, value in settings.items(): + if name in TextStyle.validSettings: + self.textSettings[name] = value + elif name in BlockStyle.validSettings: + self.blockSettings[name] = value + else: + raise LrsError, "%s not a valid setting for TextBlock" % name + + self.textStyle = textStyle + self.blockStyle = blockStyle + + # create a textStyle with our current text settings (for Span to find) + self.currentTextStyle = textStyle.copy() + self.currentTextStyle.attrs.update(self.textSettings) + + + def appendReferencedObjects(self, parent): + if self.textStyle.parent is None: + parent.append(self.textStyle) + + if self.blockStyle.parent is None: + parent.append(self.blockStyle) + + LrsContainer.appendReferencedObjects(self, parent) + + + def Paragraph(self, *args, **kwargs): + """ + Create and append a Paragraph to this TextBlock. A CR is + automatically inserted after the Paragraph. To avoid this + behavior, create the Paragraph and append it to the TextBlock + in a separate call. + """ + p = Paragraph(*args, **kwargs) + self.append(p) + self.append(CR()) + return p + + + def toElement(self, sourceEncoding): + tb = self.lrsObjectElement("TextBlock", labelName="Block") + tb.attrib.update(self.textSettings) + tb.attrib.update(self.blockSettings) + tb.set("textstyle", self.textStyle.getLabel()) + tb.set("blockstyle", self.blockStyle.getLabel()) + if hasattr(self, "tocLabel"): + tb.set("toclabel", self.tocLabel) + + for content in self.contents: + tb.append(content.toElement(sourceEncoding)) + + return tb + + + def getReferencedObjIds(self): + ids = [self.objId, self.extraId, self.blockStyle.objId, + self.textStyle.objId] + for content in self.contents: + if hasattr(content, "getReferencedObjIds"): + ids.extend(content.getReferencedObjIds()) + + return ids + + + def toLrf(self, lrfWriter): + self.toLrfContainer(lrfWriter, lrfWriter) + + + def toLrfContainer(self, lrfWriter, container): + # id really belongs to the outer block + + extraId = LrsObject.getNextObjId() + + b = LrfObject("Block", self.objId) + b.appendLrfTag(LrfTag("Link", self.blockStyle.objId)) + b.appendLrfTags( + LrfTagStream(0, [LrfTag("Link", extraId)]). \ + getStreamTags(lrfWriter.getSourceEncoding())) + b.appendTagDict(self.blockSettings) + container.addLrfObject(b.objId) + lrfWriter.append(b) + + tb = LrfObject("TextBlock", extraId) + tb.appendLrfTag(LrfTag("Link", self.textStyle.objId)) + tb.appendTagDict(self.textSettings) + + stream = LrfTagStream(STREAM_COMPRESSED) + for content in self.contents: + content.toLrfContainer(lrfWriter, stream) + + if lrfWriter.saveStreamTags: # true only if testing + tb.saveStreamTags = stream.tags + + tb.appendLrfTags( + stream.getStreamTags(lrfWriter.getSourceEncoding(), + optimizeTags=lrfWriter.optimizeTags, + optimizeCompression=lrfWriter.optimizeCompression)) + lrfWriter.append(tb) + + self.extraId = extraId + + +class Paragraph(LrsContainer): + """ + Note:

alone does not make a paragraph. Only a CR inserted + into a text block right after a

makes a real paragraph. + Two Paragraphs appended in a row act like a single Paragraph. + + Also note that there are few autoappenders for Paragraph (and + the things that can go in it.) It's less confusing (to me) to use + explicit .append methods to build up the text stream. + """ + def __init__(self, text=None): + LrsContainer.__init__(self, [Text, CR, LrsDrawChar, CharButton, + LrsSimpleChar1, basestring]) + if text is not None: + self.append(text) + + + def CR(self): + # Okay, here's a single autoappender for this common operation + cr = CR() + self.append(cr) + return cr + + + def getReferencedObjIds(self): + ids = [] + for content in self.contents: + if hasattr(content, "getReferencedObjIds"): + ids.extend(content.getReferencedObjIds()) + + return ids + + + def toLrfContainer(self, lrfWriter, parent): + parent.appendLrfTag(LrfTag("pstart", 0)) + for content in self.contents: + content.toLrfContainer(lrfWriter, parent) + parent.appendLrfTag(LrfTag("pend")) + + + def toElement(self, sourceEncoding): + p = Element("P") + appendTextElements(p, self.contents, sourceEncoding) + return p + + + +class LrsTextTag(LrsContainer): + def __init__(self, text, validContents): + LrsContainer.__init__(self, [Text, basestring] + validContents) + if text is not None: + self.append(text) + + + def toLrfContainer(self, lrfWriter, parent): + if hasattr(self, "tagName"): + tagName = self.tagName + else: + tagName = self.__class__.__name__ + + parent.appendLrfTag(LrfTag(tagName)) + + for content in self.contents: + content.toLrfContainer(lrfWriter, parent) + + parent.appendLrfTag(LrfTag(tagName + "End")) + + + def toElement(self, se): + if hasattr(self, "tagName"): + tagName = self.tagName + else: + tagName = self.__class__.__name__ + + p = Element(tagName) + appendTextElements(p, self.contents, se) + return p + + +class LrsSimpleChar1(object): + pass + + +class LrsDrawChar(LrsSimpleChar1): + pass + + +class Text(LrsContainer): + """ A object that represents raw text. Does not have a toElement. """ + def __init__(self, text): + LrsContainer.__init__(self, []) + self.text = text + + def toLrfContainer(self, lrfWriter, parent): + if self.text: + parent.appendLrfTag(LrfTag("rawtext", self.text)) + + + +class CR(LrsDrawChar, LrsContainer): + """ + A line break (when appended to a Paragraph) or a paragraph break + (when appended to a TextBlock). + """ + def __init__(self): + LrsContainer.__init__(self, []) + + + def toElement(self, se): + return Element("CR") + + + def toLrfContainer(self, lrfWriter, parent): + parent.appendLrfTag(LrfTag("CR")) + + + +class Italic(LrsDrawChar, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, [LrsDrawChar]) + + + + +class Sub(LrsDrawChar, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, []) + + + +class Sup(LrsDrawChar, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, []) + + + +class NoBR(LrsDrawChar, LrsTextTag): + def __init__(self, text=None): + LrsTextTag.__init__(self, text, [LrsSimpleChar1]) + + +# TODO: Plot, Image + +class Space(LrsSimpleChar1, LrsContainer): + def __init__(self, xsize=0, x=0): + LrsContainer.__init__(self, []) + if xsize == 0 and x != 0: xsize = x + self.xsize = xsize + + + def toElement(self, se): + if self.xsize == 0: + return + + return Element("Space", xsize=str(self.xsize)) + + + def toLrfContainer(self, lrfWriter, container): + if self.xsize != 0: + container.appendLrfTag(LrfTag("Space", self.xsize)) + + +class Box(LrsSimpleChar1, LrsContainer): + """ + Draw a box around text. Unfortunately, does not seem to do + anything on the PRS-500. + """ + def __init__(self, linetype="solid"): + LrsContainer.__init__(self, [Text, basestring]) + if linetype not in LINE_TYPE_ENCODING: + raise LrsError, linetype + " is not a valid line type" + self.linetype = linetype + + + def toElement(self, se): + e = Element("Box", linetype=self.linetype) + appendTextElements(e, self.contents, se) + return e + + + def toLrfContainer(self, lrfWriter, container): + container.appendLrfTag(LrfTag("Box", self.linetype)) + for content in self.contents: + content.toLrfContainer(lrfWriter, container) + container.appendLrfTag(LrfTag("BoxEnd")) + + + + +class Span(LrsDrawChar, LrsContainer): + def __init__(self, text=None, **attrs): + LrsContainer.__init__(self, [LrsDrawChar, Text, basestring]) + if text is not None: + self.append(text) + + for attrname in attrs.keys(): + if attrname not in TextStyle.defaults and \ + attrname not in TextStyle.alsoAllow: + raise LrsError, "setting %s not allowed on Span" % attrname + self.attrs = attrs + + + def findCurrentTextStyle(self): + parent = self.parent + while 1: + if parent is None or hasattr(parent, "currentTextStyle"): + break + parent = parent.parent + + if parent is None: + raise LrsError, "no enclosing current TextStyle found" + + return parent.currentTextStyle + + + def toLrfContainer(self, lrfWriter, container): + + # set the attributes we want changed + for (name, value) in self.attrs.items(): + container.appendLrfTag(LrfTag(name, value)) + + # set a currentTextStyle so nested span can put things back + oldTextStyle = self.findCurrentTextStyle() + self.currentTextStyle = oldTextStyle.copy() + self.currentTextStyle.attrs.update(self.attrs) + + for content in self.contents: + content.toLrfContainer(lrfWriter, container) + + # put the attributes back the way we found them + for name in self.attrs.keys(): + container.appendLrfTag(LrfTag(name, oldTextStyle.attrs[name])) + + + def toElement(self, se): + element = Element("Span") + for (key, value) in self.attrs.items(): + element.set(key, str(value)) + + appendTextElements(element, self.contents, se) + return element + + +class Bold(Span): + """ + There is no known "bold" lrf tag. Use Span with a fontweight in LRF, + but use the word Bold in the LRS. + """ + def __init__(self, text=None): + Span.__init__(self, text, fontweight=800) + + def toElement(self, se): + e = Element("Bold") + appendTextElements(e, self.contents, se) + return e + + +class BlockSpace(LrsContainer): + """ Can be appended to a page to move the text point. """ + def __init__(self, xspace=0, yspace=0, x=0, y=0): + LrsContainer.__init__(self, []) + if xspace == 0 and x != 0: xspace = x + if yspace == 0 and y != 0: yspace = y + self.xspace = xspace + self.yspace = yspace + + + def toLrfContainer(self, lrfWriter, container): + if self.xspace != 0: + container.appendLrfTag(LrfTag("xspace", self.xspace)) + if self.yspace != 0: + container.appendLrfTag(LrfTag("yspace", self.yspace)) + + + def toElement(self, se): + element = Element("BlockSpace") + + if self.xspace != 0: + element.attrib["xspace"] = str(self.xspace) + if self.yspace != 0: + element.attrib["yspace"] = str(self.yspace) + + return element + + + +class CharButton(LrsDrawChar, LrsContainer): + """ + Define the text and target of a CharButton. Must be passed a + JumpButton that is the destination of the CharButton. + + Only text or SimpleChars can be appended to the CharButton. + """ + def __init__(self, jumpButton, text=None): + LrsContainer.__init__(self, [basestring, Text, LrsSimpleChar1]) + if not isinstance(jumpButton, JumpButton): + raise LrsError, "first argument to CharButton must be a JumpButton" + + self.jumpButton = jumpButton + if text is not None: + self.append(text) + + + def appendReferencedObjects(self, parent): + if self.jumpButton.parent is None: + parent.append(self.jumpButton) + + + def getReferencedObjIds(self): + return [self.jumpButton.objId] + + + def toLrfContainer(self, lrfWriter, container): + container.appendLrfTag(LrfTag("CharButton", self.jumpButton.objId)) + + for content in self.contents: + content.toLrfContainer(lrfWriter, container) + + container.appendLrfTag(LrfTag("CharButtonEnd")) + + + def toElement(self, se): + cb = Element("CharButton", refobj=str(self.jumpButton.objId)) + appendTextElements(cb, self.contents, se) + return cb + + + +class Objects(LrsContainer): + def __init__(self): + LrsContainer.__init__(self, [JumpButton, TextBlock, HeaderOrFooter, + ImageStream]) + self.appendJumpButton = self.appendTextBlock = self.appendHeader = \ + self.appendFooter = self.appendImageStream = self.append + + + def getMethods(self): + return ["JumpButton", "appendJumpButton", "TextBlock", + "appendTextBlock", "Header", "appendHeader", + "Footer", "appendFooter", + "ImageStream", "appendImageStream"] + + + def getSettings(self): + return [] + + + + def JumpButton(self, textBlock): + b = JumpButton(textBlock) + self.append(b) + return b + + + def TextBlock(self, *args, **kwargs): + tb = TextBlock(*args, **kwargs) + self.append(tb) + return tb + + + def Header(self, *args, **kwargs): + h = Header(*args, **kwargs) + self.append(h) + return h + + + def Footer(self, *args, **kwargs): + h = Footer(*args, **kwargs) + self.append(h) + return h + + + def ImageStream(self, *args, **kwargs): + i = ImageStream(*args, **kwargs) + self.append(i) + return i + + + def toElement(self, se): + o = Element("Objects") + + for content in self.contents: + o.append(content.toElement(se)) + + return o + + + def toLrf(self, lrfWriter): + for content in self.contents: + content.toLrf(lrfWriter) + + +class JumpButton(LrsObject, LrsContainer): + """ + The target of a CharButton. Needs a parented TextBlock to jump to. + Actually creates several elements in the XML. JumpButtons must + be eventually appended to a Book (actually, an Object.) + """ + def __init__(self, textBlock): + LrsObject.__init__(self) + LrsContainer.__init__(self, []) + self.textBlock = textBlock + + + def toLrf(self, lrfWriter): + button = LrfObject("Button", self.objId) + button.appendLrfTag(LrfTag("buttonflags", 0x10)) # pushbutton + button.appendLrfTag(LrfTag("PushButtonStart")) + button.appendLrfTag(LrfTag("buttonactions")) + button.appendLrfTag(LrfTag("jumpto", + (self.textBlock.parent.objId, self.textBlock.objId))) + button.append(LrfTag("endbuttonactions")) + button.appendLrfTag(LrfTag("PushButtonEnd")) + lrfWriter.append(button) + + + def toElement(self, se): + b = self.lrsObjectElement("Button") + pb = SubElement(b, "PushButton") + jt = SubElement(pb, "JumpTo", + refpage=str(self.textBlock.parent.objId), + refobj=str(self.textBlock.objId)) + return b + + + +class RuledLine(LrsContainer, LrsAttributes): + """ A line. Default is 500 pixels long, 2 pixels wide. """ + + defaults = dict( + linelength="500", linetype="solid", linewidth="2", + linecolor="0x00000000") + + def __init__(self, **settings): + LrsContainer.__init__(self, []) + LrsAttributes.__init__(self, self.defaults, **settings) + + + def toLrfContainer(self, lrfWriter, container): + a = self.attrs + container.appendLrfTag(LrfTag("RuledLine", + (a["linelength"], a["linetype"], a["linewidth"], a["linecolor"]))) + + + def toElement(self, se): + return Element("RuledLine", self.attrs) + + + +class HeaderOrFooter(LrsObject, LrsContainer, LrsAttributes): + """ + Creates empty header or footer objects. Append PutObj objects to + the header or footer to create the text. + + Note: it seems that adding multiple PutObjs to a header or footer + only shows the last one. + """ + defaults = dict(framemode="square", layout="LrTb", framewidth="0", + framecolor="0x00000000", bgcolor="0xFF000000") + + def __init__(self, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, [PutObj]) + LrsAttributes.__init__(self, self.defaults, **settings) + + + def PutObj(self, *args, **kwargs): + p = PutObj(*args, **kwargs) + self.append(p) + return p + + + def toLrf(self, lrfWriter): + hd = LrfObject(self.__class__.__name__, self.objId) + hd.appendTagDict(self.attrs) + + stream = LrfTagStream(0) + for content in self.contents: + content.toLrfContainer(lrfWriter, stream) + + hd.appendLrfTags(stream.getStreamTags(lrfWriter.getSourceEncoding())) + lrfWriter.append(hd) + + + def toElement(self, se): + name = self.__class__.__name__ + labelName = name.lower() + "label" + hd = self.lrsObjectElement(name, objlabel=labelName) + hd.attrib.update(self.attrs) + + for content in self.contents: + hd.append(content.toElement(se)) + + return hd + + +class Header(HeaderOrFooter): + pass + + + +class Footer(HeaderOrFooter): + pass + + +class PutObj(LrsContainer): + """ PutObj holds other objects that are drawn on a Canvas or Header. """ + + def __init__(self, content, x1=0, y1=0, x=0, y=0): + LrsContainer.__init__(self, []) + self.content = content + if x1 == 0 and x != 0: x1 = x + if y1 == 0 and y != 0: y1 = y + self.x1 = x1 + self.y1 = y1 + + + def appendReferencedObjects(self, parent): + if self.content.parent is None: + parent.append(self.content) + + + def toLrfContainer(self, lrfWriter, container): + container.appendLrfTag(LrfTag("PutObj", (self.x1, self.y1, + self.content.objId))) + + + def toElement(self, se): + return Element("PutObj", x1=str(self.x1), y1=str(self.y1), + refobj=str(self.content.objId)) + + + +class ImageStream(LrsObject, LrsContainer): + """ + Embed an image file into an Lrf. + """ + + VALID_ENCODINGS = [ "JPEG", "GIF", "BMP", "PNG" ] + + def __init__(self, filename, encoding=None, comment=None): + LrsObject.__init__(self) + LrsContainer.__init__(self, []) + _checkExists(filename) + self.filename = filename + self.comment = comment + # TODO: move encoding from extension to lrf module + if encoding is None: + extension = os.path.splitext(filename)[1] + if not extension: + raise LrsError, \ + "file must have extension if encoding is not specified" + extension = extension[1:].upper() + + if extension == "JPG": + extension = "JPEG" + + encoding = extension + else: + encoding = encoding.upper() + + if encoding not in self.VALID_ENCODINGS: + raise LrsError, \ + "encoding or file extension not JPEG, GIF, BMP, or PNG" + + self.encoding = encoding + + + def toLrf(self, lrfWriter): + imageFile = file(self.filename, "rb") + imageData = imageFile.read() + imageFile.close() + + isObj = LrfObject("ImageStream", self.objId) + if self.comment is not None: + isObj.appendLrfTag(LrfTag("comment", self.comment)) + + streamFlags = IMAGE_TYPE_ENCODING[self.encoding] + stream = LrfStreamBase(streamFlags, imageData) + isObj.appendLrfTags(stream.getStreamTags()) + lrfWriter.append(isObj) + + + def toElement(self, se): + element = self.lrsObjectElement("ImageStream", + objlabel="imagestreamlabel", + encoding=self.encoding, file=self.filename) + element.text = self.comment + return element + + + +class ImageBlock(LrsObject, LrsContainer, LrsAttributes): + """ Create an image on a page. """ + # TODO: allow other block attributes + + defaults = dict(blockwidth="600", blockheight="800") + + def __init__(self, refstream, x0="0", y0="0", x1="600", y1="800", + xsize="600", ysize="800", blockStyle=None, + alttext=None, **settings): + LrsObject.__init__(self) + LrsContainer.__init__(self, []) + LrsAttributes.__init__(self, self.defaults, **settings) + self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1) + self.xsize, self.ysize = int(xsize), int(ysize) + self.refstream = refstream + self.blockStyle = blockStyle + self.alttext = alttext + + + def appendReferencedObjects(self, parent): + if self.refstream.parent is None: + parent.append(self.refstream) + + if self.blockStyle is not None and self.blockStyle.parent is None: + parent.append(self.blockStyle) + + + def getReferencedObjIds(self): + objects = [self.objId, self.extraId, self.refstream.objId] + if self.blockStyle is not None: + objects.append(self.blockStyle.objId) + + return objects + + + def toLrf(self, lrfWriter): + self.toLrfContainer(lrfWriter, lrfWriter) + + + def toLrfContainer(self, lrfWriter, container): + # id really belongs to the outer block + + extraId = LrsObject.getNextObjId() + + b = LrfObject("Block", self.objId) + if self.blockStyle is not None: + b.appendLrfTag(LrfTag("Link", self.blockStyle.objId)) + b.appendTagDict(self.attrs) + + b.appendLrfTags( + LrfTagStream(0, + [LrfTag("Link", extraId)]).getStreamTags(lrfWriter.getSourceEncoding())) + container.addLrfObject(b.objId) + lrfWriter.append(b) + + ib = LrfObject("Image", extraId) + + ib.appendLrfTag(LrfTag("ImageRect", + (self.x0, self.y0, self.x1, self.y1))) + ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize))) + ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId)) + if self.alttext: + ib.appendLrfTag("Comment", self.alttext) + + + lrfWriter.append(ib) + self.extraId = extraId + + + def toElement(self, se): + element = self.lrsObjectElement("ImageBlock", **self.attrs) + element.set("refstream", str(self.refstream.objId)) + for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]: + element.set(name, str(getattr(self, name))) + element.text = self.alttext + return element + + + +class Font(LrsContainer): + """ Allows a TrueType file to embedded in an Lrf. """ + def __init__(self, filename, facename): + LrsContainer.__init__(self, []) + _checkExists(filename) + self.filename = filename + self.facename = facename + + + def toLrf(self, lrfWriter): + font = LrfObject("Font", LrsObject.getNextObjId()) + lrfWriter.registerFontId(font.objId) + font.appendLrfTag(LrfTag("FontFilename", + lrfWriter.toUnicode(self.filename))) + font.appendLrfTag(LrfTag("FontFacename", + lrfWriter.toUnicode(self.facename))) + + stream = LrfFileStream(STREAM_FORCE_COMPRESSED, self.filename) + font.appendLrfTags(stream.getStreamTags()) + + lrfWriter.append(font) + + + def toElement(self, se): + element = Element("RegistFont", encoding="TTF", fontname=self.facename, + file=self.filename, fontfilename=self.filename) + return element + + +