1# Copyright (c) 2007 Mike Higgins (Falstaff)
2# Modifications from the original:
3#    Copyright (C) 2007 Kovid Goyal <kovid@kovidgoyal.net>
4# Permission is hereby granted, free of charge, to any person obtaining a
5# copy of this software and associated documentation files (the "Software"),
6# to deal in the Software without restriction, including without limitation
7# the rights to use, copy, modify, merge, publish, distribute, sublicense,
8# and/or sell copies of the Software, and to permit persons to whom the
9# Software is furnished to do so, subject to the following conditions:
10
11# The above copyright notice and this permission notice shall be included in
12# all copies or substantial portions of the Software.
13
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20# DEALINGS IN THE SOFTWARE.
21#
22# Current limitations and bugs:
23#   Bug: Does not check if most setting values are valid unless lrf is created.
24#
25#   Unsupported objects: MiniPage, SimpleTextBlock, Canvas, Window,
26#                        PopUpWindow, Sound, Import, SoundStream,
27#                        ObjectInfo
28#
29#   Does not support background images for blocks or pages.
30#
31#   The only button type supported are JumpButtons.
32#
33#   None of the Japanese language tags are supported.
34#
35#   Other unsupported tags: PageDiv, SoundStop, Wait, pos,
36#                           Plot, Image (outside of ImageBlock),
37#                           EmpLine, EmpDots
38
39import os, re, codecs, operator, io
40from xml.sax.saxutils import escape
41from datetime import date
42from xml.etree.ElementTree import Element, SubElement, ElementTree
43
44from .pylrf import (LrfWriter, LrfObject, LrfTag, LrfToc,
45        STREAM_COMPRESSED, LrfTagStream, LrfStreamBase, IMAGE_TYPE_ENCODING,
46        BINDING_DIRECTION_ENCODING, LINE_TYPE_ENCODING, LrfFileStream,
47        STREAM_FORCE_COMPRESSED)
48from calibre.utils.date import isoformat
49
50DEFAULT_SOURCE_ENCODING = "cp1252"      # default is us-windows character set
51DEFAULT_GENREADING      = "fs"          # default is yes to both lrf and lrs
52
53from calibre import __appname__, __version__
54from calibre import entity_to_unicode
55from polyglot.builtins import string_or_bytes, iteritems, native_string_type
56
57
58class LrsError(Exception):
59    pass
60
61
62class ContentError(Exception):
63    pass
64
65
66def _checkExists(filename):
67    if not os.path.exists(filename):
68        raise LrsError("file '%s' not found" % filename)
69
70
71def _formatXml(root):
72    """ A helper to make the LRS output look nicer. """
73    for elem in root.iter():
74        if len(elem) > 0 and (not elem.text or not elem.text.strip()):
75            elem.text = "\n"
76        if not elem.tail or not elem.tail.strip():
77            elem.tail = "\n"
78
79
80def ElementWithText(tag, text, **extra):
81    """ A shorthand function to create Elements with text. """
82    e = Element(tag, **extra)
83    e.text = text
84    return e
85
86
87def ElementWithReading(tag, text, reading=False):
88    """ A helper function that creates reading attributes. """
89
90    # note: old lrs2lrf parser only allows reading = ""
91
92    if text is None:
93        readingText = ""
94    elif isinstance(text, string_or_bytes):
95        readingText = text
96    else:
97        # assumed to be a sequence of (name, sortas)
98        readingText = text[1]
99        text = text[0]
100
101    if not reading:
102        readingText = ""
103    return ElementWithText(tag, text, reading=readingText)
104
105
106def appendTextElements(e, contentsList, se):
107    """ A helper function to convert text streams into the proper elements. """
108
109    def uconcat(text, newText, se):
110        if isinstance(text, bytes):
111            text = text.decode(se)
112        if isinstance(newText, bytes):
113            newText = newText.decode(se)
114
115        return text + newText
116
117    e.text = ""
118    lastElement = None
119
120    for content in contentsList:
121        if not isinstance(content, Text):
122            newElement = content.toElement(se)
123            if newElement is None:
124                continue
125            lastElement = newElement
126            lastElement.tail = ""
127            e.append(lastElement)
128        else:
129            if lastElement is None:
130                e.text = uconcat(e.text, content.text, se)
131            else:
132                lastElement.tail = uconcat(lastElement.tail, content.text, se)
133
134
135class Delegator:
136    """ A mixin class to create delegated methods that create elements. """
137
138    def __init__(self, delegates):
139        self.delegates = delegates
140        self.delegatedMethods = []
141        # self.delegatedSettingsDict = {}
142        # self.delegatedSettings = []
143        for d in delegates:
144            d.parent = self
145            methods = d.getMethods()
146            self.delegatedMethods += methods
147            for m in methods:
148                setattr(self, m, getattr(d, m))
149
150            """
151            for setting in d.getSettings():
152                if isinstance(setting, string_or_bytes):
153                    setting = (d, setting)
154                delegates = \
155                        self.delegatedSettingsDict.setdefault(setting[1], [])
156                delegates.append(setting[0])
157                self.delegatedSettings.append(setting)
158            """
159
160    def applySetting(self, name, value, testValid=False):
161        applied = False
162        if name in self.getSettings():
163            setattr(self, name, value)
164            applied = True
165
166        for d in self.delegates:
167            if hasattr(d, "applySetting"):
168                applied = applied or d.applySetting(name, value)
169            else:
170                if name in d.getSettings():
171                    setattr(d, name, value)
172                    applied = True
173
174        if testValid and not applied:
175            raise LrsError("setting %s not valid" % name)
176
177        return applied
178
179    def applySettings(self, settings, testValid=False):
180        for (setting, value) in settings.items():
181            self.applySetting(setting, value, testValid)
182            """
183            if setting not in self.delegatedSettingsDict:
184                raise LrsError, "setting %s not valid" % setting
185            delegates = self.delegatedSettingsDict[setting]
186            for d in delegates:
187                setattr(d, setting, value)
188            """
189
190    def appendDelegates(self, element, sourceEncoding):
191        for d in self.delegates:
192            e = d.toElement(sourceEncoding)
193            if e is not None:
194                if isinstance(e, list):
195                    for e1 in e:
196                        element.append(e1)
197                else:
198                    element.append(e)
199
200    def appendReferencedObjects(self, parent):
201        for d in self.delegates:
202            d.appendReferencedObjects(parent)
203
204    def getMethods(self):
205        return self.delegatedMethods
206
207    def getSettings(self):
208        return []
209
210    def toLrfDelegates(self, lrfWriter):
211        for d in self.delegates:
212            d.toLrf(lrfWriter)
213
214    def toLrf(self, lrfWriter):
215        self.toLrfDelegates(lrfWriter)
216
217
218class LrsAttributes:
219    """ A mixin class to handle default and user supplied attributes. """
220
221    def __init__(self, defaults, alsoAllow=None, **settings):
222        if alsoAllow is None:
223            alsoAllow = []
224        self.attrs = defaults.copy()
225        for (name, value) in settings.items():
226            if name not in self.attrs and name not in alsoAllow:
227                raise LrsError("%s does not support setting %s" %
228                        (self.__class__.__name__, name))
229            if isinstance(value, int):
230                value = str(value)
231            self.attrs[name] = value
232
233
234class LrsContainer:
235    """ This class is a mixin class for elements that are contained in or
236        contain an unknown number of other elements.
237    """
238
239    def __init__(self, validChildren):
240        self.parent = None
241        self.contents = []
242        self.validChildren = validChildren
243        self.must_append = False  # : If True even an empty container is appended by append_to
244
245    def has_text(self):
246        ''' Return True iff this container has non whitespace text '''
247        if hasattr(self, 'text'):
248            if self.text.strip():
249                return True
250        if hasattr(self, 'contents'):
251            for child in self.contents:
252                if child.has_text():
253                    return True
254        for item in self.contents:
255            if isinstance(item, (Plot, ImageBlock, Canvas, CR)):
256                return True
257        return False
258
259    def append_to(self, parent):
260        '''
261        Append self to C{parent} iff self has non whitespace textual content
262        @type parent: LrsContainer
263        '''
264        if self.contents or self.must_append:
265            parent.append(self)
266
267    def appendReferencedObjects(self, parent):
268        for c in self.contents:
269            c.appendReferencedObjects(parent)
270
271    def setParent(self, parent):
272        if self.parent is not None:
273            raise LrsError("object already has parent")
274
275        self.parent = parent
276
277    def append(self, content, convertText=True):
278        """
279            Appends valid objects to container.  Can auto-covert text strings
280            to Text objects.
281        """
282        for validChild in self.validChildren:
283            if isinstance(content, validChild):
284                break
285        else:
286            raise LrsError("can't append %s to %s" %
287                    (content.__class__.__name__,
288                    self.__class__.__name__))
289
290        if convertText and isinstance(content, string_or_bytes):
291            content = Text(content)
292
293        content.setParent(self)
294
295        if isinstance(content, LrsObject):
296            content.assignId()
297
298        self.contents.append(content)
299        return self
300
301    def get_all(self, predicate=lambda x: x):
302        for child in self.contents:
303            if predicate(child):
304                yield child
305            if hasattr(child, 'get_all'):
306                yield from child.get_all(predicate)
307
308
309class LrsObject:
310    """ A mixin class for elements that need an object id. """
311    nextObjId = 0
312
313    @classmethod
314    def getNextObjId(selfClass):
315        selfClass.nextObjId += 1
316        return selfClass.nextObjId
317
318    def __init__(self, assignId=False):
319        if assignId:
320            self.objId = LrsObject.getNextObjId()
321        else:
322            self.objId = 0
323
324    def assignId(self):
325        if self.objId != 0:
326            raise LrsError("id already assigned to " + self.__class__.__name__)
327
328        self.objId = LrsObject.getNextObjId()
329
330    def lrsObjectElement(self, name, objlabel="objlabel", labelName=None,
331            labelDecorate=True, **settings):
332        element = Element(name)
333        element.attrib["objid"] = str(self.objId)
334        if labelName is None:
335            labelName = name
336        if labelDecorate:
337            label = "%s.%d" % (labelName, self.objId)
338        else:
339            label = str(self.objId)
340        element.attrib[objlabel] = label
341        element.attrib.update(settings)
342        return element
343
344
345class Book(Delegator):
346    """
347        Main class for any lrs or lrf.  All objects must be appended to
348        the Book class in some way or another in order to be rendered as
349        an LRS or LRF file.
350
351        The following settings are available on the constructor of Book:
352
353        author="book author" or author=("book author", "sort as")
354        Author of the book.
355
356        title="book title" or title=("book title", "sort as")
357        Title of the book.
358
359        sourceencoding="codec"
360        Gives the assumed encoding for all non-unicode strings.
361
362
363        thumbnail="thumbnail file name"
364        A small (80x80?) graphics file with a thumbnail of the book's cover.
365
366        bookid="book id"
367        A unique id for the book.
368
369        textstyledefault=<dictionary of settings>
370        Sets the default values for all TextStyles.
371
372        pagetstyledefault=<dictionary of settings>
373        Sets the default values for all PageStyles.
374
375        blockstyledefault=<dictionary of settings>
376        Sets the default values for all BlockStyles.
377
378        booksetting=BookSetting()
379        Override the default BookSetting.
380
381        setdefault=StyleDefault()
382        Override the default SetDefault.
383
384        There are several other settings -- see the BookInfo class for more.
385    """
386
387    def __init__(self, textstyledefault=None, blockstyledefault=None,
388                       pagestyledefault=None,
389                       optimizeTags=False,
390                       optimizeCompression=False,
391                       **settings):
392
393        self.parent = None  # we are the top of the parent chain
394
395        if "thumbnail" in settings:
396            _checkExists(settings["thumbnail"])
397
398        # highly experimental -- use with caution
399        self.optimizeTags = optimizeTags
400        self.optimizeCompression = optimizeCompression
401
402        pageStyle  = PageStyle(**PageStyle.baseDefaults.copy())
403        blockStyle = BlockStyle(**BlockStyle.baseDefaults.copy())
404        textStyle  = TextStyle(**TextStyle.baseDefaults.copy())
405
406        if textstyledefault is not None:
407            textStyle.update(textstyledefault)
408
409        if blockstyledefault is not None:
410            blockStyle.update(blockstyledefault)
411
412        if pagestyledefault is not None:
413            pageStyle.update(pagestyledefault)
414
415        self.defaultPageStyle = pageStyle
416        self.defaultTextStyle = textStyle
417        self.defaultBlockStyle = blockStyle
418        LrsObject.nextObjId += 1
419
420        styledefault = StyleDefault()
421        if 'setdefault' in settings:
422            styledefault = settings.pop('setdefault')
423        Delegator.__init__(self, [BookInformation(), Main(),
424            Template(), Style(styledefault), Solos(), Objects()])
425
426        self.sourceencoding = None
427
428        # apply default settings
429        self.applySetting("genreading", DEFAULT_GENREADING)
430        self.applySetting("sourceencoding", DEFAULT_SOURCE_ENCODING)
431
432        self.applySettings(settings, testValid=True)
433
434        self.allow_new_page = True  # : If False L{create_page} raises an exception
435        self.gc_count = 0
436
437    def set_title(self, title):
438        ot = self.delegates[0].delegates[0].delegates[0].title
439        self.delegates[0].delegates[0].delegates[0].title = (title, ot[1])
440
441    def set_author(self, author):
442        ot = self.delegates[0].delegates[0].delegates[0].author
443        self.delegates[0].delegates[0].delegates[0].author = (author, ot[1])
444
445    def create_text_style(self, **settings):
446        ans = TextStyle(**self.defaultTextStyle.attrs.copy())
447        ans.update(settings)
448        return ans
449
450    def create_block_style(self, **settings):
451        ans = BlockStyle(**self.defaultBlockStyle.attrs.copy())
452        ans.update(settings)
453        return ans
454
455    def create_page_style(self, **settings):
456        if not self.allow_new_page:
457            raise ContentError
458        ans = PageStyle(**self.defaultPageStyle.attrs.copy())
459        ans.update(settings)
460        return ans
461
462    def create_page(self, pageStyle=None, **settings):
463        '''
464        Return a new L{Page}. The page has not been appended to this book.
465        @param pageStyle: If None the default pagestyle is used.
466        @type pageStyle: L{PageStyle}
467        '''
468        if not pageStyle:
469            pageStyle = self.defaultPageStyle
470        return Page(pageStyle=pageStyle, **settings)
471
472    def create_text_block(self, textStyle=None, blockStyle=None, **settings):
473        '''
474        Return a new L{TextBlock}. The block has not been appended to this
475        book.
476        @param textStyle: If None the default text style is used
477        @type textStyle: L{TextStyle}
478        @param blockStyle: If None the default block style is used.
479        @type blockStyle: L{BlockStyle}
480        '''
481        if not textStyle:
482            textStyle = self.defaultTextStyle
483        if not blockStyle:
484            blockStyle = self.defaultBlockStyle
485        return TextBlock(textStyle=textStyle, blockStyle=blockStyle, **settings)
486
487    def pages(self):
488        '''Return list of Page objects in this book '''
489        ans = []
490        for item in self.delegates:
491            if isinstance(item, Main):
492                for candidate in item.contents:
493                    if isinstance(candidate, Page):
494                        ans.append(candidate)
495                break
496        return ans
497
498    def last_page(self):
499        '''Return last Page in this book '''
500        for item in self.delegates:
501            if isinstance(item, Main):
502                temp = list(item.contents)
503                temp.reverse()
504                for candidate in temp:
505                    if isinstance(candidate, Page):
506                        return candidate
507
508    def embed_font(self, file, facename):
509        f = Font(file, facename)
510        self.append(f)
511
512    def getSettings(self):
513        return ["sourceencoding"]
514
515    def append(self, content):
516        """ Find and invoke the correct appender for this content. """
517
518        className = content.__class__.__name__
519        try:
520            method = getattr(self, "append" + className)
521        except AttributeError:
522            raise LrsError("can't append %s to Book" % className)
523
524        method(content)
525
526    def rationalize_font_sizes(self, base_font_size=10):
527        base_font_size *= 10.
528        main = None
529        for obj in self.delegates:
530            if isinstance(obj, Main):
531                main = obj
532                break
533
534        fonts = {}
535        for text in main.get_all(lambda x: isinstance(x, Text)):
536            fs = base_font_size
537            ancestor = text.parent
538            while ancestor:
539                try:
540                    fs = int(ancestor.attrs['fontsize'])
541                    break
542                except (AttributeError, KeyError):
543                    pass
544                try:
545                    fs = int(ancestor.textSettings['fontsize'])
546                    break
547                except (AttributeError, KeyError):
548                    pass
549                try:
550                    fs = int(ancestor.textStyle.attrs['fontsize'])
551                    break
552                except (AttributeError, KeyError):
553                    pass
554                ancestor = ancestor.parent
555            length = len(text.text)
556            fonts[fs] = fonts.get(fs, 0) + length
557        if not fonts:
558            print('WARNING: LRF seems to have no textual content. Cannot rationalize font sizes.')
559            return
560
561        old_base_font_size = float(max(fonts.items(), key=operator.itemgetter(1))[0])
562        factor = base_font_size / old_base_font_size
563
564        def rescale(old):
565            return str(int(int(old) * factor))
566
567        text_blocks = list(main.get_all(lambda x: isinstance(x, TextBlock)))
568        for tb in text_blocks:
569            if 'fontsize' in tb.textSettings:
570                tb.textSettings['fontsize'] = rescale(tb.textSettings['fontsize'])
571            for span in tb.get_all(lambda x: isinstance(x, Span)):
572                if 'fontsize' in span.attrs:
573                    span.attrs['fontsize'] = rescale(span.attrs['fontsize'])
574                if 'baselineskip' in span.attrs:
575                    span.attrs['baselineskip'] = rescale(span.attrs['baselineskip'])
576
577        text_styles = (tb.textStyle for tb in text_blocks)
578        for ts in text_styles:
579            ts.attrs['fontsize'] = rescale(ts.attrs['fontsize'])
580            ts.attrs['baselineskip'] = rescale(ts.attrs['baselineskip'])
581
582    def renderLrs(self, lrsFile, encoding="UTF-8"):
583        if isinstance(lrsFile, string_or_bytes):
584            lrsFile = codecs.open(lrsFile, "wb", encoding=encoding)
585        self.render(lrsFile, outputEncodingName=encoding)
586        lrsFile.close()
587
588    def renderLrf(self, lrfFile):
589        self.appendReferencedObjects(self)
590        if isinstance(lrfFile, string_or_bytes):
591            lrfFile = open(lrfFile, "wb")
592        lrfWriter = LrfWriter(self.sourceencoding)
593
594        lrfWriter.optimizeTags = self.optimizeTags
595        lrfWriter.optimizeCompression = self.optimizeCompression
596
597        self.toLrf(lrfWriter)
598        lrfWriter.writeFile(lrfFile)
599        lrfFile.close()
600
601    def toElement(self, se):
602        root = Element("BBeBXylog", version="1.0")
603        root.append(Element("Property"))
604        self.appendDelegates(root, self.sourceencoding)
605        return root
606
607    def render(self, f, outputEncodingName='UTF-8'):
608        """ Write the book as an LRS to file f. """
609
610        self.appendReferencedObjects(self)
611
612        # create the root node, and populate with the parts of the book
613
614        root = self.toElement(self.sourceencoding)
615
616        # now, add some newlines to make it easier to look at
617
618        _formatXml(root)
619        tree = ElementTree(element=root)
620        tree.write(f, encoding=native_string_type(outputEncodingName), xml_declaration=True)
621
622
623class BookInformation(Delegator):
624    """ Just a container for the Info and TableOfContents elements. """
625
626    def __init__(self):
627        Delegator.__init__(self, [Info(), TableOfContents()])
628
629    def toElement(self, se):
630        bi = Element("BookInformation")
631        self.appendDelegates(bi, se)
632        return bi
633
634
635class Info(Delegator):
636    """ Just a container for the BookInfo and DocInfo elements. """
637
638    def __init__(self):
639        self.genreading = DEFAULT_GENREADING
640        Delegator.__init__(self, [BookInfo(), DocInfo()])
641
642    def getSettings(self):
643        return ["genreading"]  # + self.delegatedSettings
644
645    def toElement(self, se):
646        info = Element("Info", version="1.1")
647        info.append(
648            self.delegates[0].toElement(se, reading="s" in self.genreading))
649        info.append(self.delegates[1].toElement(se))
650        return info
651
652    def toLrf(self, lrfWriter):
653        # this info is set in XML form in the LRF
654        info = Element("Info", version="1.1")
655        # self.appendDelegates(info)
656        info.append(
657            self.delegates[0].toElement(lrfWriter.getSourceEncoding(), reading="f" in self.genreading))
658        info.append(self.delegates[1].toElement(lrfWriter.getSourceEncoding()))
659
660        # look for the thumbnail file and get the filename
661        tnail = info.find("DocInfo/CThumbnail")
662        if tnail is not None:
663            lrfWriter.setThumbnailFile(tnail.get("file"))
664            # does not work: info.remove(tnail)
665
666        _formatXml(info)
667
668        # fix up the doc info to match the LRF format
669        # NB: generates an encoding attribute, which lrs2lrf does not
670        tree = ElementTree(element=info)
671        f = io.BytesIO()
672        tree.write(f, encoding=native_string_type('utf-8'), xml_declaration=True)
673        xmlInfo = f.getvalue().decode('utf-8')
674        xmlInfo = re.sub(r"<CThumbnail.*?>\n", "", xmlInfo)
675        xmlInfo = xmlInfo.replace("SumPage>", "Page>")
676        lrfWriter.docInfoXml = xmlInfo
677
678
679class TableOfContents:
680
681    def __init__(self):
682        self.tocEntries = []
683
684    def appendReferencedObjects(self, parent):
685        pass
686
687    def getMethods(self):
688        return ["addTocEntry"]
689
690    def getSettings(self):
691        return []
692
693    def addTocEntry(self, tocLabel, textBlock):
694        if not isinstance(textBlock, (Canvas, TextBlock, ImageBlock, RuledLine)):
695            raise LrsError("TOC destination must be a Canvas, TextBlock, ImageBlock or RuledLine"+
696                            " not a " + str(type(textBlock)))
697
698        if textBlock.parent is None:
699            raise LrsError("TOC text block must be already appended to a page")
700
701        if False and textBlock.parent.parent is None:
702            raise LrsError("TOC destination page must be already appended to a book")
703
704        if not hasattr(textBlock.parent, 'objId'):
705            raise LrsError("TOC destination must be appended to a container with an objID")
706
707        for tl in self.tocEntries:
708            if tl.label == tocLabel and tl.textBlock == textBlock:
709                return
710
711        self.tocEntries.append(TocLabel(tocLabel, textBlock))
712        textBlock.tocLabel = tocLabel
713
714    def toElement(self, se):
715        if len(self.tocEntries) == 0:
716            return None
717
718        toc = Element("TOC")
719
720        for t in self.tocEntries:
721            toc.append(t.toElement(se))
722
723        return toc
724
725    def toLrf(self, lrfWriter):
726        if len(self.tocEntries) == 0:
727            return
728
729        toc = []
730        for t in self.tocEntries:
731            toc.append((t.textBlock.parent.objId, t.textBlock.objId, t.label))
732
733        lrfToc = LrfToc(LrsObject.getNextObjId(), toc, lrfWriter.getSourceEncoding())
734        lrfWriter.append(lrfToc)
735        lrfWriter.setTocObject(lrfToc)
736
737
738class TocLabel:
739
740    def __init__(self, label, textBlock):
741        self.label = escape(re.sub(r'&(\S+?);', entity_to_unicode, label))
742        self.textBlock = textBlock
743
744    def toElement(self, se):
745        return ElementWithText("TocLabel", self.label,
746                 refobj=str(self.textBlock.objId),
747                 refpage=str(self.textBlock.parent.objId))
748
749
750class BookInfo:
751
752    def __init__(self):
753        self.title = "Untitled"
754        self.author = "Anonymous"
755        self.bookid = None
756        self.pi = None
757        self.isbn = None
758        self.publisher = None
759        self.freetext = "\n\n"
760        self.label = None
761        self.category = None
762        self.classification = None
763
764    def appendReferencedObjects(self, parent):
765        pass
766
767    def getMethods(self):
768        return []
769
770    def getSettings(self):
771        return ["author", "title", "bookid", "isbn", "publisher",
772                "freetext", "label", "category", "classification"]
773
774    def _appendISBN(self, bi):
775        pi = Element("ProductIdentifier")
776        isbnElement = ElementWithText("ISBNPrintable", self.isbn)
777        isbnValueElement = ElementWithText("ISBNValue",
778                self.isbn.replace("-", ""))
779
780        pi.append(isbnElement)
781        pi.append(isbnValueElement)
782        bi.append(pi)
783
784    def toElement(self, se, reading=True):
785        bi = Element("BookInfo")
786        bi.append(ElementWithReading("Title", self.title, reading=reading))
787        bi.append(ElementWithReading("Author", self.author, reading=reading))
788        bi.append(ElementWithText("BookID", self.bookid))
789        if self.isbn is not None:
790            self._appendISBN(bi)
791
792        if self.publisher is not None:
793            bi.append(ElementWithReading("Publisher", self.publisher))
794
795        bi.append(ElementWithReading("Label", self.label, reading=reading))
796        bi.append(ElementWithText("Category", self.category))
797        bi.append(ElementWithText("Classification", self.classification))
798        bi.append(ElementWithText("FreeText", self.freetext))
799        return bi
800
801
802class DocInfo:
803
804    def __init__(self):
805        self.thumbnail = None
806        self.language = "en"
807        self.creator  = None
808        self.creationdate = str(isoformat(date.today()))
809        self.producer = "%s v%s"%(__appname__, __version__)
810        self.numberofpages = "0"
811
812    def appendReferencedObjects(self, parent):
813        pass
814
815    def getMethods(self):
816        return []
817
818    def getSettings(self):
819        return ["thumbnail", "language", "creator", "creationdate",
820                "producer", "numberofpages"]
821
822    def toElement(self, se):
823        docInfo = Element("DocInfo")
824
825        if self.thumbnail is not None:
826            docInfo.append(Element("CThumbnail", file=self.thumbnail))
827
828        docInfo.append(ElementWithText("Language", self.language))
829        docInfo.append(ElementWithText("Creator", self.creator))
830        docInfo.append(ElementWithText("CreationDate", self.creationdate))
831        docInfo.append(ElementWithText("Producer", self.producer))
832        docInfo.append(ElementWithText("SumPage", str(self.numberofpages)))
833        return docInfo
834
835
836class Main(LrsContainer):
837
838    def __init__(self):
839        LrsContainer.__init__(self, [Page])
840
841    def getMethods(self):
842        return ["appendPage", "Page"]
843
844    def getSettings(self):
845        return []
846
847    def Page(self, *args, **kwargs):
848        p = Page(*args, **kwargs)
849        self.append(p)
850        return p
851
852    def appendPage(self, page):
853        self.append(page)
854
855    def toElement(self, sourceEncoding):
856        main = Element(self.__class__.__name__)
857
858        for page in self.contents:
859            main.append(page.toElement(sourceEncoding))
860
861        return main
862
863    def toLrf(self, lrfWriter):
864        pageIds = []
865
866        # set this id now so that pages can see it
867        pageTreeId = LrsObject.getNextObjId()
868        lrfWriter.setPageTreeId(pageTreeId)
869
870        # create a list of all the page object ids while dumping the pages
871
872        for p in self.contents:
873            pageIds.append(p.objId)
874            p.toLrf(lrfWriter)
875
876        # create a page tree object
877
878        pageTree = LrfObject("PageTree", pageTreeId)
879        pageTree.appendLrfTag(LrfTag("PageList", pageIds))
880
881        lrfWriter.append(pageTree)
882
883
884class Solos(LrsContainer):
885
886    def __init__(self):
887        LrsContainer.__init__(self, [Solo])
888
889    def getMethods(self):
890        return ["appendSolo", "Solo"]
891
892    def getSettings(self):
893        return []
894
895    def Solo(self, *args, **kwargs):
896        p = Solo(*args, **kwargs)
897        self.append(p)
898        return p
899
900    def appendSolo(self, solo):
901        self.append(solo)
902
903    def toLrf(self, lrfWriter):
904        for s in self.contents:
905            s.toLrf(lrfWriter)
906
907    def toElement(self, se):
908        solos = []
909        for s in self.contents:
910            solos.append(s.toElement(se))
911
912        if len(solos) == 0:
913            return None
914
915        return solos
916
917
918class Solo(Main):
919    pass
920
921
922class Template:
923    """ Does nothing that I know of. """
924
925    def appendReferencedObjects(self, parent):
926        pass
927
928    def getMethods(self):
929        return []
930
931    def getSettings(self):
932        return []
933
934    def toElement(self, se):
935        t = Element("Template")
936        t.attrib["version"] = "1.0"
937        return t
938
939    def toLrf(self, lrfWriter):
940        # does nothing
941        pass
942
943
944class StyleDefault(LrsAttributes):
945    """
946        Supply some defaults for all TextBlocks.
947        The legal values are a subset of what is allowed on a
948        TextBlock -- ruby, emphasis, and waitprop settings.
949    """
950    defaults = dict(rubyalign="start", rubyadjust="none",
951                rubyoverhang="none", empdotsposition="before",
952                empdotsfontname="Dutch801 Rm BT Roman",
953                empdotscode="0x002e", emplineposition="after",
954                emplinetype="solid", setwaitprop="noreplay")
955
956    alsoAllow = ["refempdotsfont", "rubyAlignAndAdjust"]
957
958    def __init__(self, **settings):
959        LrsAttributes.__init__(self, self.defaults,
960                alsoAllow=self.alsoAllow, **settings)
961
962    def toElement(self, se):
963        return Element("SetDefault", self.attrs)
964
965
966class Style(LrsContainer, Delegator):
967
968    def __init__(self, styledefault=StyleDefault()):
969        LrsContainer.__init__(self, [PageStyle, TextStyle, BlockStyle])
970        Delegator.__init__(self, [BookStyle(styledefault=styledefault)])
971        self.bookStyle = self.delegates[0]
972        self.appendPageStyle = self.appendTextStyle = \
973                self.appendBlockStyle = self.append
974
975    def appendReferencedObjects(self, parent):
976        LrsContainer.appendReferencedObjects(self, parent)
977
978    def getMethods(self):
979        return ["PageStyle", "TextStyle", "BlockStyle",
980                "appendPageStyle", "appendTextStyle", "appendBlockStyle"] + \
981                        self.delegatedMethods
982
983    def getSettings(self):
984        return [(self.bookStyle, x) for x in self.bookStyle.getSettings()]
985
986    def PageStyle(self, *args, **kwargs):
987        ps = PageStyle(*args, **kwargs)
988        self.append(ps)
989        return ps
990
991    def TextStyle(self, *args, **kwargs):
992        ts = TextStyle(*args, **kwargs)
993        self.append(ts)
994        return ts
995
996    def BlockStyle(self, *args, **kwargs):
997        bs = BlockStyle(*args, **kwargs)
998        self.append(bs)
999        return bs
1000
1001    def toElement(self, se):
1002        style = Element("Style")
1003        style.append(self.bookStyle.toElement(se))
1004
1005        for content in self.contents:
1006            style.append(content.toElement(se))
1007
1008        return style
1009
1010    def toLrf(self, lrfWriter):
1011        self.bookStyle.toLrf(lrfWriter)
1012
1013        for s in self.contents:
1014            s.toLrf(lrfWriter)
1015
1016
1017class BookStyle(LrsObject, LrsContainer):
1018
1019    def __init__(self, styledefault=StyleDefault()):
1020        LrsObject.__init__(self, assignId=True)
1021        LrsContainer.__init__(self, [Font])
1022        self.styledefault = styledefault
1023        self.booksetting = BookSetting()
1024        self.appendFont = self.append
1025
1026    def getSettings(self):
1027        return ["styledefault", "booksetting"]
1028
1029    def getMethods(self):
1030        return ["Font", "appendFont"]
1031
1032    def Font(self, *args, **kwargs):
1033        f = Font(*args, **kwargs)
1034        self.append(f)
1035        return
1036
1037    def toElement(self, se):
1038        bookStyle = self.lrsObjectElement("BookStyle", objlabel="stylelabel",
1039                labelDecorate=False)
1040        bookStyle.append(self.styledefault.toElement(se))
1041        bookStyle.append(self.booksetting.toElement(se))
1042        for font in self.contents:
1043            bookStyle.append(font.toElement(se))
1044
1045        return bookStyle
1046
1047    def toLrf(self, lrfWriter):
1048        bookAtr = LrfObject("BookAtr", self.objId)
1049        bookAtr.appendLrfTag(LrfTag("ChildPageTree", lrfWriter.getPageTreeId()))
1050        bookAtr.appendTagDict(self.styledefault.attrs)
1051
1052        self.booksetting.toLrf(lrfWriter)
1053
1054        lrfWriter.append(bookAtr)
1055        lrfWriter.setRootObject(bookAtr)
1056
1057        for font in self.contents:
1058            font.toLrf(lrfWriter)
1059
1060
1061class BookSetting(LrsAttributes):
1062
1063    def __init__(self, **settings):
1064        defaults = dict(bindingdirection="Lr", dpi="1660",
1065                screenheight="800", screenwidth="600", colordepth="24")
1066        LrsAttributes.__init__(self, defaults, **settings)
1067
1068    def toLrf(self, lrfWriter):
1069        a = self.attrs
1070        lrfWriter.dpi = int(a["dpi"])
1071        lrfWriter.bindingdirection = \
1072                BINDING_DIRECTION_ENCODING[a["bindingdirection"]]
1073        lrfWriter.height = int(a["screenheight"])
1074        lrfWriter.width = int(a["screenwidth"])
1075        lrfWriter.colorDepth = int(a["colordepth"])
1076
1077    def toElement(self, se):
1078        return Element("BookSetting", self.attrs)
1079
1080
1081class LrsStyle(LrsObject, LrsAttributes, LrsContainer):
1082    """ A mixin class for styles. """
1083
1084    def __init__(self, elementName, defaults=None, alsoAllow=None, **overrides):
1085        if defaults is None:
1086            defaults = {}
1087
1088        LrsObject.__init__(self)
1089        LrsAttributes.__init__(self, defaults, alsoAllow=alsoAllow, **overrides)
1090        LrsContainer.__init__(self, [])
1091        self.elementName = elementName
1092        self.objectsAppended = False
1093        # self.label = "%s.%d" % (elementName, self.objId)
1094        # self.label = str(self.objId)
1095        # self.parent = None
1096
1097    def update(self, settings):
1098        for name, value in settings.items():
1099            if name not in self.__class__.validSettings:
1100                raise LrsError("%s not a valid setting for %s" % (name, self.__class__.__name__))
1101            self.attrs[name] = value
1102
1103    def getLabel(self):
1104        return str(self.objId)
1105
1106    def toElement(self, se):
1107        element = Element(self.elementName, stylelabel=self.getLabel(),
1108                objid=str(self.objId))
1109        element.attrib.update(self.attrs)
1110        return element
1111
1112    def toLrf(self, lrfWriter):
1113        obj = LrfObject(self.elementName, self.objId)
1114        obj.appendTagDict(self.attrs, self.__class__.__name__)
1115        lrfWriter.append(obj)
1116
1117    def __eq__(self, other):
1118        if hasattr(other, 'attrs'):
1119            return self.__class__ == other.__class__ and self.attrs == other.attrs
1120        return False
1121
1122
1123class TextStyle(LrsStyle):
1124    """
1125        The text style of a TextBlock.  Default is 10 pt. Times Roman.
1126
1127        Setting         Value                   Default
1128        --------        -----                   -------
1129        align           "head","center","foot"  "head" (left aligned)
1130        baselineskip    points * 10             120 (12 pt. distance between
1131                                                  bottoms of lines)
1132        fontsize        points * 10             100 (10 pt.)
1133        fontweight      1 to 1000               400 (normal, 800 is bold)
1134        fontwidth       points * 10 or -10      -10 (use values from font)
1135        linespace       points * 10             10 (min space btw. lines?)
1136        wordspace       points * 10             25 (min space btw. each word)
1137
1138    """
1139    baseDefaults = dict(
1140            columnsep="0", charspace="0",
1141            textlinewidth="2", align="head", linecolor="0x00000000",
1142            column="1", fontsize="100", fontwidth="-10", fontescapement="0",
1143            fontorientation="0", fontweight="400",
1144            fontfacename="Dutch801 Rm BT Roman",
1145            textcolor="0x00000000", wordspace="25", letterspace="0",
1146            baselineskip="120", linespace="10", parindent="0", parskip="0",
1147            textbgcolor="0xFF000000")
1148
1149    alsoAllow = ["empdotscode", "empdotsfontname", "refempdotsfont",
1150                 "rubyadjust", "rubyalign", "rubyoverhang",
1151                 "empdotsposition", 'emplinetype', 'emplineposition']
1152
1153    validSettings = list(baseDefaults) + alsoAllow
1154
1155    defaults = baseDefaults.copy()
1156
1157    def __init__(self, **overrides):
1158        LrsStyle.__init__(self, "TextStyle", self.defaults,
1159                alsoAllow=self.alsoAllow, **overrides)
1160
1161    def copy(self):
1162        tb = TextStyle()
1163        tb.attrs = self.attrs.copy()
1164        return tb
1165
1166
1167class BlockStyle(LrsStyle):
1168    """
1169        The block style of a TextBlock.  Default is an expandable 560 pixel
1170        wide area with no space for headers or footers.
1171
1172        Setting      Value                  Default
1173        --------     -----                  -------
1174        blockwidth   pixels                 560
1175        sidemargin   pixels                 0
1176    """
1177
1178    baseDefaults = dict(
1179            bgimagemode="fix", framemode="square", blockwidth="560",
1180            blockheight="100", blockrule="horz-adjustable", layout="LrTb",
1181            framewidth="0", framecolor="0x00000000", topskip="0",
1182            sidemargin="0", footskip="0", bgcolor="0xFF000000")
1183
1184    validSettings = baseDefaults.keys()
1185    defaults = baseDefaults.copy()
1186
1187    def __init__(self, **overrides):
1188        LrsStyle.__init__(self, "BlockStyle", self.defaults, **overrides)
1189
1190    def copy(self):
1191        tb = BlockStyle()
1192        tb.attrs = self.attrs.copy()
1193        return tb
1194
1195
1196class PageStyle(LrsStyle):
1197    """
1198        Setting         Value                   Default
1199        --------        -----                   -------
1200        evensidemargin  pixels                  20
1201        oddsidemargin   pixels                  20
1202        topmargin       pixels                  20
1203    """
1204    baseDefaults = dict(
1205            topmargin="20", headheight="0", headsep="0",
1206            oddsidemargin="20", textheight="747", textwidth="575",
1207            footspace="0", evensidemargin="20", footheight="0",
1208            layout="LrTb", bgimagemode="fix", pageposition="any",
1209            setwaitprop="noreplay", setemptyview="show")
1210
1211    alsoAllow = ["header", "evenheader", "oddheader",
1212                 "footer", "evenfooter", "oddfooter"]
1213
1214    validSettings = list(baseDefaults) + alsoAllow
1215    defaults = baseDefaults.copy()
1216
1217    @classmethod
1218    def translateHeaderAndFooter(selfClass, parent, settings):
1219        selfClass._fixup(parent, "header", settings)
1220        selfClass._fixup(parent, "footer", settings)
1221
1222    @classmethod
1223    def _fixup(selfClass, parent, basename, settings):
1224        evenbase = "even" + basename
1225        oddbase = "odd" + basename
1226        if basename in settings:
1227            baseObj = settings[basename]
1228            del settings[basename]
1229            settings[evenbase] = settings[oddbase] = baseObj
1230
1231        if evenbase in settings:
1232            evenObj = settings[evenbase]
1233            del settings[evenbase]
1234            if evenObj.parent is None:
1235                parent.append(evenObj)
1236            settings[evenbase + "id"] = str(evenObj.objId)
1237
1238        if oddbase in settings:
1239            oddObj = settings[oddbase]
1240            del settings[oddbase]
1241            if oddObj.parent is None:
1242                parent.append(oddObj)
1243            settings[oddbase + "id"] = str(oddObj.objId)
1244
1245    def appendReferencedObjects(self, parent):
1246        if self.objectsAppended:
1247            return
1248        PageStyle.translateHeaderAndFooter(parent, self.attrs)
1249        self.objectsAppended = True
1250
1251    def __init__(self, **settings):
1252        # self.fixHeaderSettings(settings)
1253        LrsStyle.__init__(self, "PageStyle", self.defaults,
1254                alsoAllow=self.alsoAllow, **settings)
1255
1256
1257class Page(LrsObject, LrsContainer):
1258    """
1259        Pages are added to Books.  Pages can be supplied a PageStyle.
1260        If they are not, Page.defaultPageStyle will be used.
1261    """
1262    defaultPageStyle = PageStyle()
1263
1264    def __init__(self, pageStyle=defaultPageStyle, **settings):
1265        LrsObject.__init__(self)
1266        LrsContainer.__init__(self, [TextBlock, BlockSpace, RuledLine,
1267            ImageBlock, Canvas])
1268
1269        self.pageStyle = pageStyle
1270
1271        for settingName in settings.keys():
1272            if settingName not in PageStyle.defaults and \
1273                    settingName not in PageStyle.alsoAllow:
1274                raise LrsError("setting %s not allowed on Page" % settingName)
1275
1276        self.settings = settings.copy()
1277
1278    def appendReferencedObjects(self, parent):
1279        PageStyle.translateHeaderAndFooter(parent, self.settings)
1280
1281        self.pageStyle.appendReferencedObjects(parent)
1282
1283        if self.pageStyle.parent is None:
1284            parent.append(self.pageStyle)
1285
1286        LrsContainer.appendReferencedObjects(self, parent)
1287
1288    def RuledLine(self, *args, **kwargs):
1289        rl = RuledLine(*args, **kwargs)
1290        self.append(rl)
1291        return rl
1292
1293    def BlockSpace(self, *args, **kwargs):
1294        bs = BlockSpace(*args, **kwargs)
1295        self.append(bs)
1296        return bs
1297
1298    def TextBlock(self, *args, **kwargs):
1299        """ Create and append a new text block (shortcut). """
1300        tb = TextBlock(*args, **kwargs)
1301        self.append(tb)
1302        return tb
1303
1304    def ImageBlock(self, *args, **kwargs):
1305        """ Create and append and new Image block (shorthand). """
1306        ib = ImageBlock(*args, **kwargs)
1307        self.append(ib)
1308        return ib
1309
1310    def addLrfObject(self, objId):
1311        self.stream.appendLrfTag(LrfTag("Link", objId))
1312
1313    def appendLrfTag(self, lrfTag):
1314        self.stream.appendLrfTag(lrfTag)
1315
1316    def toLrf(self, lrfWriter):
1317        # tags:
1318        # ObjectList
1319        # Link to pagestyle
1320        # Parent page tree id
1321        # stream of tags
1322
1323        p = LrfObject("Page", self.objId)
1324        lrfWriter.append(p)
1325
1326        pageContent = set()
1327        self.stream = LrfTagStream(0)
1328        for content in self.contents:
1329            content.toLrfContainer(lrfWriter, self)
1330            if hasattr(content, "getReferencedObjIds"):
1331                pageContent.update(content.getReferencedObjIds())
1332
1333        # print "page contents:", pageContent
1334        # ObjectList not needed and causes slowdown in SONY LRF renderer
1335        # p.appendLrfTag(LrfTag("ObjectList", pageContent))
1336        p.appendLrfTag(LrfTag("Link", self.pageStyle.objId))
1337        p.appendLrfTag(LrfTag("ParentPageTree", lrfWriter.getPageTreeId()))
1338        p.appendTagDict(self.settings)
1339        p.appendLrfTags(self.stream.getStreamTags(lrfWriter.getSourceEncoding()))
1340
1341    def toElement(self, sourceEncoding):
1342        page = self.lrsObjectElement("Page")
1343        page.set("pagestyle", self.pageStyle.getLabel())
1344        page.attrib.update(self.settings)
1345
1346        for content in self.contents:
1347            page.append(content.toElement(sourceEncoding))
1348
1349        return page
1350
1351
1352class TextBlock(LrsObject, LrsContainer):
1353    """
1354        TextBlocks are added to Pages.  They hold Paragraphs or CRs.
1355
1356        If a TextBlock is used in a header, it should be appended to
1357        the Book, not to a specific Page.
1358    """
1359    defaultTextStyle = TextStyle()
1360    defaultBlockStyle = BlockStyle()
1361
1362    def __init__(self, textStyle=defaultTextStyle,
1363                       blockStyle=defaultBlockStyle,
1364                       **settings):
1365        '''
1366        Create TextBlock.
1367        @param textStyle: The L{TextStyle} for this block.
1368        @param blockStyle: The L{BlockStyle} for this block.
1369        @param settings: C{dict} of extra settings to apply to this block.
1370        '''
1371        LrsObject.__init__(self)
1372        LrsContainer.__init__(self, [Paragraph, CR])
1373
1374        self.textSettings = {}
1375        self.blockSettings = {}
1376
1377        for name, value in settings.items():
1378            if name in TextStyle.validSettings:
1379                self.textSettings[name] = value
1380            elif name in BlockStyle.validSettings:
1381                self.blockSettings[name] = value
1382            elif name == 'toclabel':
1383                self.tocLabel = value
1384            else:
1385                raise LrsError("%s not a valid setting for TextBlock" % name)
1386
1387        self.textStyle = textStyle
1388        self.blockStyle = blockStyle
1389
1390        # create a textStyle with our current text settings (for Span to find)
1391        self.currentTextStyle = textStyle.copy() if self.textSettings else textStyle
1392        self.currentTextStyle.attrs.update(self.textSettings)
1393
1394    def appendReferencedObjects(self, parent):
1395        if self.textStyle.parent is None:
1396            parent.append(self.textStyle)
1397
1398        if self.blockStyle.parent is None:
1399            parent.append(self.blockStyle)
1400
1401        LrsContainer.appendReferencedObjects(self, parent)
1402
1403    def Paragraph(self, *args, **kwargs):
1404        """
1405            Create and append a Paragraph to this TextBlock.  A CR is
1406            automatically inserted after the Paragraph.  To avoid this
1407            behavior, create the Paragraph and append it to the TextBlock
1408            in a separate call.
1409        """
1410        p = Paragraph(*args, **kwargs)
1411        self.append(p)
1412        self.append(CR())
1413        return p
1414
1415    def toElement(self, sourceEncoding):
1416        tb = self.lrsObjectElement("TextBlock", labelName="Block")
1417        tb.attrib.update(self.textSettings)
1418        tb.attrib.update(self.blockSettings)
1419        tb.set("textstyle", self.textStyle.getLabel())
1420        tb.set("blockstyle", self.blockStyle.getLabel())
1421        if hasattr(self, "tocLabel"):
1422            tb.set("toclabel", self.tocLabel)
1423
1424        for content in self.contents:
1425            tb.append(content.toElement(sourceEncoding))
1426
1427        return tb
1428
1429    def getReferencedObjIds(self):
1430        ids = [self.objId, self.extraId, self.blockStyle.objId,
1431                self.textStyle.objId]
1432        for content in self.contents:
1433            if hasattr(content, "getReferencedObjIds"):
1434                ids.extend(content.getReferencedObjIds())
1435
1436        return ids
1437
1438    def toLrf(self, lrfWriter):
1439        self.toLrfContainer(lrfWriter, lrfWriter)
1440
1441    def toLrfContainer(self, lrfWriter, container):
1442        # id really belongs to the outer block
1443        extraId = LrsObject.getNextObjId()
1444
1445        b = LrfObject("Block", self.objId)
1446        b.appendLrfTag(LrfTag("Link", self.blockStyle.objId))
1447        b.appendLrfTags(
1448                LrfTagStream(0, [LrfTag("Link", extraId)]).getStreamTags(lrfWriter.getSourceEncoding()))
1449        b.appendTagDict(self.blockSettings)
1450        container.addLrfObject(b.objId)
1451        lrfWriter.append(b)
1452
1453        tb = LrfObject("TextBlock", extraId)
1454        tb.appendLrfTag(LrfTag("Link", self.textStyle.objId))
1455        tb.appendTagDict(self.textSettings)
1456
1457        stream = LrfTagStream(STREAM_COMPRESSED)
1458        for content in self.contents:
1459            content.toLrfContainer(lrfWriter, stream)
1460
1461        if lrfWriter.saveStreamTags:  # true only if testing
1462            tb.saveStreamTags = stream.tags
1463
1464        tb.appendLrfTags(
1465                stream.getStreamTags(lrfWriter.getSourceEncoding(),
1466                    optimizeTags=lrfWriter.optimizeTags,
1467                    optimizeCompression=lrfWriter.optimizeCompression))
1468        lrfWriter.append(tb)
1469
1470        self.extraId = extraId
1471
1472
1473class Paragraph(LrsContainer):
1474    """
1475        Note: <P> alone does not make a paragraph.  Only a CR inserted
1476        into a text block right after a <P> makes a real paragraph.
1477        Two Paragraphs appended in a row act like a single Paragraph.
1478
1479        Also note that there are few autoappenders for Paragraph (and
1480        the things that can go in it.)  It's less confusing (to me) to use
1481        explicit .append methods to build up the text stream.
1482    """
1483
1484    def __init__(self, text=None):
1485        LrsContainer.__init__(self, [Text, CR, DropCaps, CharButton,
1486                                     LrsSimpleChar1, bytes, str])
1487        if text is not None:
1488            if isinstance(text, string_or_bytes):
1489                text = Text(text)
1490            self.append(text)
1491
1492    def CR(self):
1493        # Okay, here's a single autoappender for this common operation
1494        cr = CR()
1495        self.append(cr)
1496        return cr
1497
1498    def getReferencedObjIds(self):
1499        ids = []
1500        for content in self.contents:
1501            if hasattr(content, "getReferencedObjIds"):
1502                ids.extend(content.getReferencedObjIds())
1503
1504        return ids
1505
1506    def toLrfContainer(self, lrfWriter, parent):
1507        parent.appendLrfTag(LrfTag("pstart", 0))
1508        for content in self.contents:
1509            content.toLrfContainer(lrfWriter, parent)
1510        parent.appendLrfTag(LrfTag("pend"))
1511
1512    def toElement(self, sourceEncoding):
1513        p = Element("P")
1514        appendTextElements(p, self.contents, sourceEncoding)
1515        return p
1516
1517
1518class LrsTextTag(LrsContainer):
1519
1520    def __init__(self, text, validContents):
1521        LrsContainer.__init__(self, [Text, bytes, str] + validContents)
1522        if text is not None:
1523            self.append(text)
1524
1525    def toLrfContainer(self, lrfWriter, parent):
1526        if hasattr(self, "tagName"):
1527            tagName = self.tagName
1528        else:
1529            tagName = self.__class__.__name__
1530
1531        parent.appendLrfTag(LrfTag(tagName))
1532
1533        for content in self.contents:
1534            content.toLrfContainer(lrfWriter, parent)
1535
1536        parent.appendLrfTag(LrfTag(tagName + "End"))
1537
1538    def toElement(self, se):
1539        if hasattr(self, "tagName"):
1540            tagName = self.tagName
1541        else:
1542            tagName = self.__class__.__name__
1543
1544        p = Element(tagName)
1545        appendTextElements(p, self.contents, se)
1546        return p
1547
1548
1549class LrsSimpleChar1:
1550
1551    def isEmpty(self):
1552        for content in self.contents:
1553            if not content.isEmpty():
1554                return False
1555        return True
1556
1557    def hasFollowingContent(self):
1558        foundSelf = False
1559        for content in self.parent.contents:
1560            if content == self:
1561                foundSelf = True
1562            elif foundSelf:
1563                if not content.isEmpty():
1564                    return True
1565        return False
1566
1567
1568class DropCaps(LrsTextTag):
1569
1570    def __init__(self, line=1):
1571        LrsTextTag.__init__(self, None, [LrsSimpleChar1])
1572        if int(line) <= 0:
1573            raise LrsError('A DrawChar must span at least one line.')
1574        self.line = int(line)
1575
1576    def isEmpty(self):
1577        return self.text is None or not self.text.strip()
1578
1579    def toElement(self, se):
1580        elem =  Element('DrawChar', line=str(self.line))
1581        appendTextElements(elem, self.contents, se)
1582        return elem
1583
1584    def toLrfContainer(self, lrfWriter, parent):
1585        parent.appendLrfTag(LrfTag('DrawChar', (int(self.line),)))
1586
1587        for content in self.contents:
1588            content.toLrfContainer(lrfWriter, parent)
1589
1590        parent.appendLrfTag(LrfTag("DrawCharEnd"))
1591
1592
1593class Button(LrsObject, LrsContainer):
1594
1595    def __init__(self, **settings):
1596        LrsObject.__init__(self, **settings)
1597        LrsContainer.__init__(self, [PushButton])
1598
1599    def findJumpToRefs(self):
1600        for sub1 in self.contents:
1601            if isinstance(sub1, PushButton):
1602                for sub2 in sub1.contents:
1603                    if isinstance(sub2, JumpTo):
1604                        return (sub2.textBlock.objId, sub2.textBlock.parent.objId)
1605        raise LrsError("%s has no PushButton or JumpTo subs"%self.__class__.__name__)
1606
1607    def toLrf(self, lrfWriter):
1608        (refobj, refpage) = self.findJumpToRefs()
1609        # print "Button writing JumpTo refobj=", jumpto.refobj, ", and refpage=", jumpto.refpage
1610        button = LrfObject("Button", self.objId)
1611        button.appendLrfTag(LrfTag("buttonflags", 0x10))  # pushbutton
1612        button.appendLrfTag(LrfTag("PushButtonStart"))
1613        button.appendLrfTag(LrfTag("buttonactions"))
1614        button.appendLrfTag(LrfTag("jumpto", (int(refpage), int(refobj))))
1615        button.append(LrfTag("endbuttonactions"))
1616        button.appendLrfTag(LrfTag("PushButtonEnd"))
1617        lrfWriter.append(button)
1618
1619    def toElement(self, se):
1620        b = self.lrsObjectElement("Button")
1621
1622        for content in self.contents:
1623            b.append(content.toElement(se))
1624
1625        return b
1626
1627
1628class ButtonBlock(Button):
1629    pass
1630
1631
1632class PushButton(LrsContainer):
1633
1634    def __init__(self, **settings):
1635        LrsContainer.__init__(self, [JumpTo])
1636
1637    def toElement(self, se):
1638        b = Element("PushButton")
1639
1640        for content in self.contents:
1641            b.append(content.toElement(se))
1642
1643        return b
1644
1645
1646class JumpTo(LrsContainer):
1647
1648    def __init__(self, textBlock):
1649        LrsContainer.__init__(self, [])
1650        self.textBlock=textBlock
1651
1652    def setTextBlock(self, textBlock):
1653        self.textBlock = textBlock
1654
1655    def toElement(self, se):
1656        return Element("JumpTo", refpage=str(self.textBlock.parent.objId), refobj=str(self.textBlock.objId))
1657
1658
1659class Plot(LrsSimpleChar1, LrsContainer):
1660
1661    ADJUSTMENT_VALUES = {'center':1, 'baseline':2, 'top':3, 'bottom':4}
1662
1663    def __init__(self, obj, xsize=0, ysize=0, adjustment=None):
1664        LrsContainer.__init__(self, [])
1665        if obj is not None:
1666            self.setObj(obj)
1667        if xsize < 0 or ysize < 0:
1668            raise LrsError('Sizes must be positive semi-definite')
1669        self.xsize = int(xsize)
1670        self.ysize = int(ysize)
1671        if adjustment and adjustment not in Plot.ADJUSTMENT_VALUES.keys():
1672            raise LrsError('adjustment must be one of' + Plot.ADJUSTMENT_VALUES.keys())
1673        self.adjustment = adjustment
1674
1675    def setObj(self, obj):
1676        if not isinstance(obj, (Image, Button)):
1677            raise LrsError('Plot elements can only refer to Image or Button elements')
1678        self.obj = obj
1679
1680    def getReferencedObjIds(self):
1681        return [self.obj.objId]
1682
1683    def appendReferencedObjects(self, parent):
1684        if self.obj.parent is None:
1685            parent.append(self.obj)
1686
1687    def toElement(self, se):
1688        elem =  Element('Plot', xsize=str(self.xsize), ysize=str(self.ysize),
1689                                refobj=str(self.obj.objId))
1690        if self.adjustment:
1691            elem.set('adjustment', self.adjustment)
1692        return elem
1693
1694    def toLrfContainer(self, lrfWriter, parent):
1695        adj = self.adjustment if self.adjustment else 'bottom'
1696        params = (int(self.xsize), int(self.ysize), int(self.obj.objId),
1697                  Plot.ADJUSTMENT_VALUES[adj])
1698        parent.appendLrfTag(LrfTag("Plot", params))
1699
1700
1701class Text(LrsContainer):
1702    """ A object that represents raw text.  Does not have a toElement. """
1703
1704    def __init__(self, text):
1705        LrsContainer.__init__(self, [])
1706        self.text = text
1707
1708    def isEmpty(self):
1709        return not self.text or not self.text.strip()
1710
1711    def toLrfContainer(self, lrfWriter, parent):
1712        if self.text:
1713            if isinstance(self.text, bytes):
1714                parent.appendLrfTag(LrfTag("rawtext", self.text))
1715            else:
1716                parent.appendLrfTag(LrfTag("textstring", self.text))
1717
1718
1719class CR(LrsSimpleChar1, LrsContainer):
1720    """
1721        A line break (when appended to a Paragraph) or a paragraph break
1722        (when appended to a TextBlock).
1723    """
1724
1725    def __init__(self):
1726        LrsContainer.__init__(self, [])
1727
1728    def toElement(self, se):
1729        return Element("CR")
1730
1731    def toLrfContainer(self, lrfWriter, parent):
1732        parent.appendLrfTag(LrfTag("CR"))
1733
1734
1735class Italic(LrsSimpleChar1, LrsTextTag):
1736
1737    def __init__(self, text=None):
1738        LrsTextTag.__init__(self, text, [LrsSimpleChar1])
1739
1740
1741class Sub(LrsSimpleChar1, LrsTextTag):
1742
1743    def __init__(self, text=None):
1744        LrsTextTag.__init__(self, text, [])
1745
1746
1747class Sup(LrsSimpleChar1, LrsTextTag):
1748
1749    def __init__(self, text=None):
1750        LrsTextTag.__init__(self, text, [])
1751
1752
1753class NoBR(LrsSimpleChar1, LrsTextTag):
1754
1755    def __init__(self, text=None):
1756        LrsTextTag.__init__(self, text, [LrsSimpleChar1])
1757
1758
1759class Space(LrsSimpleChar1, LrsContainer):
1760
1761    def __init__(self, xsize=0, x=0):
1762        LrsContainer.__init__(self, [])
1763        if xsize == 0 and x != 0:
1764            xsize = x
1765        self.xsize = xsize
1766
1767    def toElement(self, se):
1768        if self.xsize == 0:
1769            return
1770
1771        return Element("Space", xsize=str(self.xsize))
1772
1773    def toLrfContainer(self, lrfWriter, container):
1774        if self.xsize != 0:
1775            container.appendLrfTag(LrfTag("Space", self.xsize))
1776
1777
1778class Box(LrsSimpleChar1, LrsContainer):
1779    """
1780        Draw a box around text.  Unfortunately, does not seem to do
1781        anything on the PRS-500.
1782    """
1783
1784    def __init__(self, linetype="solid"):
1785        LrsContainer.__init__(self, [Text, bytes, str])
1786        if linetype not in LINE_TYPE_ENCODING:
1787            raise LrsError(linetype + " is not a valid line type")
1788        self.linetype = linetype
1789
1790    def toElement(self, se):
1791        e = Element("Box", linetype=self.linetype)
1792        appendTextElements(e, self.contents, se)
1793        return e
1794
1795    def toLrfContainer(self, lrfWriter, container):
1796        container.appendLrfTag(LrfTag("Box", self.linetype))
1797        for content in self.contents:
1798            content.toLrfContainer(lrfWriter, container)
1799        container.appendLrfTag(LrfTag("BoxEnd"))
1800
1801
1802class Span(LrsSimpleChar1, LrsContainer):
1803
1804    def __init__(self, text=None, **attrs):
1805        LrsContainer.__init__(self, [LrsSimpleChar1, Text, bytes, str])
1806        if text is not None:
1807            if isinstance(text, string_or_bytes):
1808                text = Text(text)
1809            self.append(text)
1810
1811        for attrname in attrs.keys():
1812            if attrname not in TextStyle.defaults and \
1813                    attrname not in TextStyle.alsoAllow:
1814                raise LrsError("setting %s not allowed on Span" % attrname)
1815        self.attrs = attrs
1816
1817    def findCurrentTextStyle(self):
1818        parent = self.parent
1819        while 1:
1820            if parent is None or hasattr(parent, "currentTextStyle"):
1821                break
1822            parent = parent.parent
1823
1824        if parent is None:
1825            raise LrsError("no enclosing current TextStyle found")
1826
1827        return parent.currentTextStyle
1828
1829    def toLrfContainer(self, lrfWriter, container):
1830
1831        # find the currentTextStyle
1832        oldTextStyle = self.findCurrentTextStyle()
1833
1834        # set the attributes we want changed
1835        for (name, value) in tuple(iteritems(self.attrs)):
1836            if name in oldTextStyle.attrs and oldTextStyle.attrs[name] == self.attrs[name]:
1837                self.attrs.pop(name)
1838            else:
1839                container.appendLrfTag(LrfTag(name, value))
1840
1841        # set a currentTextStyle so nested span can put things back
1842        oldTextStyle = self.findCurrentTextStyle()
1843        self.currentTextStyle = oldTextStyle.copy()
1844        self.currentTextStyle.attrs.update(self.attrs)
1845
1846        for content in self.contents:
1847            content.toLrfContainer(lrfWriter, container)
1848
1849        # put the attributes back the way we found them
1850        # the attributes persist beyond the next </P>
1851        # if self.hasFollowingContent():
1852        for name in self.attrs.keys():
1853            container.appendLrfTag(LrfTag(name, oldTextStyle.attrs[name]))
1854
1855    def toElement(self, se):
1856        element = Element('Span')
1857        for (key, value) in self.attrs.items():
1858            element.set(key, str(value))
1859
1860        appendTextElements(element, self.contents, se)
1861        return element
1862
1863
1864class EmpLine(LrsTextTag, LrsSimpleChar1):
1865    emplinetypes = ['none', 'solid', 'dotted', 'dashed', 'double']
1866    emplinepositions = ['before', 'after']
1867
1868    def __init__(self, text=None, emplineposition='before', emplinetype='solid'):
1869        LrsTextTag.__init__(self, text, [LrsSimpleChar1])
1870        if emplineposition not in self.__class__.emplinepositions:
1871            raise LrsError('emplineposition for an EmpLine must be one of: '+str(self.__class__.emplinepositions))
1872        if emplinetype not in self.__class__.emplinetypes:
1873            raise LrsError('emplinetype for an EmpLine must be one of: '+str(self.__class__.emplinetypes))
1874
1875        self.emplinetype     = emplinetype
1876        self.emplineposition = emplineposition
1877
1878    def toLrfContainer(self, lrfWriter, parent):
1879        parent.appendLrfTag(LrfTag(self.__class__.__name__, (self.emplineposition, self.emplinetype)))
1880        parent.appendLrfTag(LrfTag('emplineposition', self.emplineposition))
1881        parent.appendLrfTag(LrfTag('emplinetype', self.emplinetype))
1882        for content in self.contents:
1883            content.toLrfContainer(lrfWriter, parent)
1884
1885        parent.appendLrfTag(LrfTag(self.__class__.__name__ + "End"))
1886
1887    def toElement(self, se):
1888        element = Element(self.__class__.__name__)
1889        element.set('emplineposition', self.emplineposition)
1890        element.set('emplinetype', self.emplinetype)
1891
1892        appendTextElements(element, self.contents, se)
1893        return element
1894
1895
1896class Bold(Span):
1897    """
1898        There is no known "bold" lrf tag. Use Span with a fontweight in LRF,
1899        but use the word Bold in the LRS.
1900    """
1901
1902    def __init__(self, text=None):
1903        Span.__init__(self, text, fontweight=800)
1904
1905    def toElement(self, se):
1906        e = Element("Bold")
1907        appendTextElements(e, self.contents, se)
1908        return e
1909
1910
1911class BlockSpace(LrsContainer):
1912    """ Can be appended to a page to move the text point. """
1913
1914    def __init__(self, xspace=0, yspace=0, x=0, y=0):
1915        LrsContainer.__init__(self, [])
1916        if xspace == 0 and x != 0:
1917            xspace = x
1918        if yspace == 0 and y != 0:
1919            yspace = y
1920        self.xspace = xspace
1921        self.yspace = yspace
1922
1923    def toLrfContainer(self, lrfWriter, container):
1924        if self.xspace != 0:
1925            container.appendLrfTag(LrfTag("xspace", self.xspace))
1926        if self.yspace != 0:
1927            container.appendLrfTag(LrfTag("yspace", self.yspace))
1928
1929    def toElement(self, se):
1930        element = Element("BlockSpace")
1931
1932        if self.xspace != 0:
1933            element.attrib["xspace"] = str(self.xspace)
1934        if self.yspace != 0:
1935            element.attrib["yspace"] = str(self.yspace)
1936
1937        return element
1938
1939
1940class CharButton(LrsSimpleChar1, LrsContainer):
1941    """
1942        Define the text and target of a CharButton.  Must be passed a
1943        JumpButton that is the destination of the CharButton.
1944
1945        Only text or SimpleChars can be appended to the CharButton.
1946    """
1947
1948    def __init__(self, button, text=None):
1949        LrsContainer.__init__(self, [bytes, str, Text, LrsSimpleChar1])
1950        self.button = None
1951        if button is not None:
1952            self.setButton(button)
1953
1954        if text is not None:
1955            self.append(text)
1956
1957    def setButton(self, button):
1958        if not isinstance(button, (JumpButton, Button)):
1959            raise LrsError("CharButton button must be a JumpButton or Button")
1960
1961        self.button = button
1962
1963    def appendReferencedObjects(self, parent):
1964        if self.button.parent is None:
1965            parent.append(self.button)
1966
1967    def getReferencedObjIds(self):
1968        return [self.button.objId]
1969
1970    def toLrfContainer(self, lrfWriter, container):
1971        container.appendLrfTag(LrfTag("CharButton", self.button.objId))
1972
1973        for content in self.contents:
1974            content.toLrfContainer(lrfWriter, container)
1975
1976        container.appendLrfTag(LrfTag("CharButtonEnd"))
1977
1978    def toElement(self, se):
1979        cb = Element("CharButton", refobj=str(self.button.objId))
1980        appendTextElements(cb, self.contents, se)
1981        return cb
1982
1983
1984class Objects(LrsContainer):
1985
1986    def __init__(self):
1987        LrsContainer.__init__(self, [JumpButton, TextBlock, HeaderOrFooter,
1988            ImageStream, Image, ImageBlock, Button, ButtonBlock])
1989        self.appendJumpButton = self.appendTextBlock = self.appendHeader = \
1990                self.appendFooter = self.appendImageStream = \
1991                self.appendImage = self.appendImageBlock = self.append
1992
1993    def getMethods(self):
1994        return ["JumpButton", "appendJumpButton", "TextBlock",
1995                "appendTextBlock", "Header", "appendHeader",
1996                "Footer", "appendFooter", "ImageBlock",
1997                "ImageStream", "appendImageStream",
1998                'Image','appendImage', 'appendImageBlock']
1999
2000    def getSettings(self):
2001        return []
2002
2003    def ImageBlock(self, *args, **kwargs):
2004        ib = ImageBlock(*args, **kwargs)
2005        self.append(ib)
2006        return ib
2007
2008    def JumpButton(self, textBlock):
2009        b = JumpButton(textBlock)
2010        self.append(b)
2011        return b
2012
2013    def TextBlock(self, *args, **kwargs):
2014        tb = TextBlock(*args, **kwargs)
2015        self.append(tb)
2016        return tb
2017
2018    def Header(self, *args, **kwargs):
2019        h = Header(*args, **kwargs)
2020        self.append(h)
2021        return h
2022
2023    def Footer(self, *args, **kwargs):
2024        h = Footer(*args, **kwargs)
2025        self.append(h)
2026        return h
2027
2028    def ImageStream(self, *args, **kwargs):
2029        i = ImageStream(*args, **kwargs)
2030        self.append(i)
2031        return i
2032
2033    def Image(self, *args, **kwargs):
2034        i = Image(*args, **kwargs)
2035        self.append(i)
2036        return i
2037
2038    def toElement(self, se):
2039        o = Element("Objects")
2040
2041        for content in self.contents:
2042            o.append(content.toElement(se))
2043
2044        return o
2045
2046    def toLrf(self, lrfWriter):
2047        for content in self.contents:
2048            content.toLrf(lrfWriter)
2049
2050
2051class JumpButton(LrsObject, LrsContainer):
2052    """
2053        The target of a CharButton.  Needs a parented TextBlock to jump to.
2054        Actually creates several elements in the XML.  JumpButtons must
2055        be eventually appended to a Book (actually, an Object.)
2056    """
2057
2058    def __init__(self, textBlock):
2059        LrsObject.__init__(self)
2060        LrsContainer.__init__(self, [])
2061        self.textBlock = textBlock
2062
2063    def setTextBlock(self, textBlock):
2064        self.textBlock = textBlock
2065
2066    def toLrf(self, lrfWriter):
2067        button = LrfObject("Button", self.objId)
2068        button.appendLrfTag(LrfTag("buttonflags", 0x10))  # pushbutton
2069        button.appendLrfTag(LrfTag("PushButtonStart"))
2070        button.appendLrfTag(LrfTag("buttonactions"))
2071        button.appendLrfTag(LrfTag("jumpto",
2072            (self.textBlock.parent.objId, self.textBlock.objId)))
2073        button.append(LrfTag("endbuttonactions"))
2074        button.appendLrfTag(LrfTag("PushButtonEnd"))
2075        lrfWriter.append(button)
2076
2077    def toElement(self, se):
2078        b = self.lrsObjectElement("Button")
2079        pb = SubElement(b, "PushButton")
2080        SubElement(pb, "JumpTo",
2081            refpage=str(self.textBlock.parent.objId),
2082            refobj=str(self.textBlock.objId))
2083        return b
2084
2085
2086class RuledLine(LrsContainer, LrsAttributes, LrsObject):
2087    """ A line.  Default is 500 pixels long, 2 pixels wide. """
2088
2089    defaults = dict(
2090            linelength="500", linetype="solid", linewidth="2",
2091            linecolor="0x00000000")
2092
2093    def __init__(self, **settings):
2094        LrsContainer.__init__(self, [])
2095        LrsAttributes.__init__(self, self.defaults, **settings)
2096        LrsObject.__init__(self)
2097
2098    def toLrfContainer(self, lrfWriter, container):
2099        a = self.attrs
2100        container.appendLrfTag(LrfTag("RuledLine",
2101            (a["linelength"], a["linetype"], a["linewidth"], a["linecolor"])))
2102
2103    def toElement(self, se):
2104        return Element("RuledLine", self.attrs)
2105
2106
2107class HeaderOrFooter(LrsObject, LrsContainer, LrsAttributes):
2108    """
2109        Creates empty header or footer objects.  Append PutObj objects to
2110        the header or footer to create the text.
2111
2112        Note: it seems that adding multiple PutObjs to a header or footer
2113              only shows the last one.
2114    """
2115    defaults = dict(framemode="square", layout="LrTb", framewidth="0",
2116                framecolor="0x00000000", bgcolor="0xFF000000")
2117
2118    def __init__(self, **settings):
2119        LrsObject.__init__(self)
2120        LrsContainer.__init__(self, [PutObj])
2121        LrsAttributes.__init__(self, self.defaults, **settings)
2122
2123    def put_object(self, obj, x1, y1):
2124        self.append(PutObj(obj, x1, y1))
2125
2126    def PutObj(self, *args, **kwargs):
2127        p = PutObj(*args, **kwargs)
2128        self.append(p)
2129        return p
2130
2131    def toLrf(self, lrfWriter):
2132        hd = LrfObject(self.__class__.__name__, self.objId)
2133        hd.appendTagDict(self.attrs)
2134
2135        stream = LrfTagStream(0)
2136        for content in self.contents:
2137            content.toLrfContainer(lrfWriter, stream)
2138
2139        hd.appendLrfTags(stream.getStreamTags(lrfWriter.getSourceEncoding()))
2140        lrfWriter.append(hd)
2141
2142    def toElement(self, se):
2143        name = self.__class__.__name__
2144        labelName = name.lower() + "label"
2145        hd = self.lrsObjectElement(name, objlabel=labelName)
2146        hd.attrib.update(self.attrs)
2147
2148        for content in self.contents:
2149            hd.append(content.toElement(se))
2150
2151        return hd
2152
2153
2154class Header(HeaderOrFooter):
2155    pass
2156
2157
2158class Footer(HeaderOrFooter):
2159    pass
2160
2161
2162class Canvas(LrsObject, LrsContainer, LrsAttributes):
2163    defaults = dict(framemode="square", layout="LrTb", framewidth="0",
2164                framecolor="0x00000000", bgcolor="0xFF000000",
2165                canvasheight=0, canvaswidth=0, blockrule='block-adjustable')
2166
2167    def __init__(self, width, height, **settings):
2168        LrsObject.__init__(self)
2169        LrsContainer.__init__(self, [PutObj])
2170        LrsAttributes.__init__(self, self.defaults, **settings)
2171
2172        self.settings = self.defaults.copy()
2173        self.settings.update(settings)
2174        self.settings['canvasheight'] = int(height)
2175        self.settings['canvaswidth']  = int(width)
2176
2177    def put_object(self, obj, x1, y1):
2178        self.append(PutObj(obj, x1, y1))
2179
2180    def toElement(self, source_encoding):
2181        el = self.lrsObjectElement("Canvas", **self.settings)
2182        for po in self.contents:
2183            el.append(po.toElement(source_encoding))
2184        return el
2185
2186    def toLrf(self, lrfWriter):
2187        self.toLrfContainer(lrfWriter, lrfWriter)
2188
2189    def toLrfContainer(self, lrfWriter, container):
2190        c = LrfObject("Canvas", self.objId)
2191        c.appendTagDict(self.settings)
2192        stream = LrfTagStream(STREAM_COMPRESSED)
2193        for content in self.contents:
2194            content.toLrfContainer(lrfWriter, stream)
2195        if lrfWriter.saveStreamTags:  # true only if testing
2196            c.saveStreamTags = stream.tags
2197
2198        c.appendLrfTags(
2199                stream.getStreamTags(lrfWriter.getSourceEncoding(),
2200                    optimizeTags=lrfWriter.optimizeTags,
2201                    optimizeCompression=lrfWriter.optimizeCompression))
2202        container.addLrfObject(c.objId)
2203        lrfWriter.append(c)
2204
2205    def has_text(self):
2206        return bool(self.contents)
2207
2208
2209class PutObj(LrsContainer):
2210    """ PutObj holds other objects that are drawn on a Canvas or Header. """
2211
2212    def __init__(self, content, x1=0, y1=0):
2213        LrsContainer.__init__(self, [TextBlock, ImageBlock])
2214        self.content = content
2215        self.x1 = int(x1)
2216        self.y1 = int(y1)
2217
2218    def setContent(self, content):
2219        self.content = content
2220
2221    def appendReferencedObjects(self, parent):
2222        if self.content.parent is None:
2223            parent.append(self.content)
2224
2225    def toLrfContainer(self, lrfWriter, container):
2226        container.appendLrfTag(LrfTag("PutObj", (self.x1, self.y1,
2227            self.content.objId)))
2228
2229    def toElement(self, se):
2230        el = Element("PutObj", x1=str(self.x1), y1=str(self.y1),
2231                    refobj=str(self.content.objId))
2232        return el
2233
2234
2235class ImageStream(LrsObject, LrsContainer):
2236    """
2237        Embed an image file into an Lrf.
2238    """
2239
2240    VALID_ENCODINGS = ["JPEG", "GIF", "BMP", "PNG"]
2241
2242    def __init__(self, file=None, encoding=None, comment=None):
2243        LrsObject.__init__(self)
2244        LrsContainer.__init__(self, [])
2245        _checkExists(file)
2246        self.filename = file
2247        self.comment = comment
2248        # TODO: move encoding from extension to lrf module
2249        if encoding is None:
2250            extension = os.path.splitext(file)[1]
2251            if not extension:
2252                raise LrsError("file must have extension if encoding is not specified")
2253            extension = extension[1:].upper()
2254
2255            if extension == "JPG":
2256                extension = "JPEG"
2257
2258            encoding = extension
2259        else:
2260            encoding = encoding.upper()
2261
2262        if encoding not in self.VALID_ENCODINGS:
2263            raise LrsError("encoding or file extension not JPEG, GIF, BMP, or PNG")
2264
2265        self.encoding = encoding
2266
2267    def toLrf(self, lrfWriter):
2268        with open(self.filename, "rb") as f:
2269            imageData = f.read()
2270
2271        isObj = LrfObject("ImageStream", self.objId)
2272        if self.comment is not None:
2273            isObj.appendLrfTag(LrfTag("comment", self.comment))
2274
2275        streamFlags = IMAGE_TYPE_ENCODING[self.encoding]
2276        stream = LrfStreamBase(streamFlags, imageData)
2277        isObj.appendLrfTags(stream.getStreamTags())
2278        lrfWriter.append(isObj)
2279
2280    def toElement(self, se):
2281        element = self.lrsObjectElement("ImageStream",
2282                                objlabel="imagestreamlabel",
2283                                encoding=self.encoding, file=self.filename)
2284        element.text = self.comment
2285        return element
2286
2287
2288class Image(LrsObject, LrsContainer, LrsAttributes):
2289
2290    defaults = dict()
2291
2292    def __init__(self, refstream, x0=0, x1=0,
2293                 y0=0, y1=0, xsize=0, ysize=0, **settings):
2294        LrsObject.__init__(self)
2295        LrsContainer.__init__(self, [])
2296        LrsAttributes.__init__(self, self.defaults, settings)
2297        self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1)
2298        self.xsize, self.ysize = int(xsize), int(ysize)
2299        self.setRefstream(refstream)
2300
2301    def setRefstream(self, refstream):
2302        self.refstream = refstream
2303
2304    def appendReferencedObjects(self, parent):
2305        if self.refstream.parent is None:
2306            parent.append(self.refstream)
2307
2308    def getReferencedObjIds(self):
2309        return [self.objId, self.refstream.objId]
2310
2311    def toElement(self, se):
2312        element = self.lrsObjectElement("Image", **self.attrs)
2313        element.set("refstream", str(self.refstream.objId))
2314        for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]:
2315            element.set(name, str(getattr(self, name)))
2316        return element
2317
2318    def toLrf(self, lrfWriter):
2319        ib = LrfObject("Image", self.objId)
2320        ib.appendLrfTag(LrfTag("ImageRect",
2321            (self.x0, self.y0, self.x1, self.y1)))
2322        ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize)))
2323        ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId))
2324        lrfWriter.append(ib)
2325
2326
2327class ImageBlock(LrsObject, LrsContainer, LrsAttributes):
2328    """ Create an image on a page. """
2329    # TODO: allow other block attributes
2330
2331    defaults = BlockStyle.baseDefaults.copy()
2332
2333    def __init__(self, refstream, x0="0", y0="0", x1="600", y1="800",
2334                       xsize="600", ysize="800",
2335                       blockStyle=BlockStyle(blockrule='block-fixed'),
2336                       alttext=None, **settings):
2337        LrsObject.__init__(self)
2338        LrsContainer.__init__(self, [Text, Image])
2339        LrsAttributes.__init__(self, self.defaults, **settings)
2340        self.x0, self.y0, self.x1, self.y1 = int(x0), int(y0), int(x1), int(y1)
2341        self.xsize, self.ysize = int(xsize), int(ysize)
2342        self.setRefstream(refstream)
2343        self.blockStyle = blockStyle
2344        self.alttext = alttext
2345
2346    def setRefstream(self, refstream):
2347        self.refstream = refstream
2348
2349    def appendReferencedObjects(self, parent):
2350        if self.refstream.parent is None:
2351            parent.append(self.refstream)
2352
2353        if self.blockStyle is not None and self.blockStyle.parent is None:
2354            parent.append(self.blockStyle)
2355
2356    def getReferencedObjIds(self):
2357        objects =  [self.objId, self.extraId, self.refstream.objId]
2358        if self.blockStyle is not None:
2359            objects.append(self.blockStyle.objId)
2360
2361        return objects
2362
2363    def toLrf(self, lrfWriter):
2364        self.toLrfContainer(lrfWriter, lrfWriter)
2365
2366    def toLrfContainer(self, lrfWriter, container):
2367        # id really belongs to the outer block
2368
2369        extraId = LrsObject.getNextObjId()
2370
2371        b = LrfObject("Block", self.objId)
2372        if self.blockStyle is not None:
2373            b.appendLrfTag(LrfTag("Link", self.blockStyle.objId))
2374        b.appendTagDict(self.attrs)
2375
2376        b.appendLrfTags(
2377            LrfTagStream(0,
2378                [LrfTag("Link", extraId)]).getStreamTags(lrfWriter.getSourceEncoding()))
2379        container.addLrfObject(b.objId)
2380        lrfWriter.append(b)
2381
2382        ib = LrfObject("Image", extraId)
2383
2384        ib.appendLrfTag(LrfTag("ImageRect",
2385            (self.x0, self.y0, self.x1, self.y1)))
2386        ib.appendLrfTag(LrfTag("ImageSize", (self.xsize, self.ysize)))
2387        ib.appendLrfTag(LrfTag("RefObjId", self.refstream.objId))
2388        if self.alttext:
2389            ib.appendLrfTag("Comment", self.alttext)
2390
2391        lrfWriter.append(ib)
2392        self.extraId = extraId
2393
2394    def toElement(self, se):
2395        element = self.lrsObjectElement("ImageBlock", **self.attrs)
2396        element.set("refstream", str(self.refstream.objId))
2397        for name in ["x0", "y0", "x1", "y1", "xsize", "ysize"]:
2398            element.set(name, str(getattr(self, name)))
2399        element.text = self.alttext
2400        return element
2401
2402
2403class Font(LrsContainer):
2404    """ Allows a TrueType file to be embedded in an Lrf. """
2405
2406    def __init__(self, file=None, fontname=None, fontfilename=None, encoding=None):
2407        LrsContainer.__init__(self, [])
2408        try:
2409            _checkExists(fontfilename)
2410            self.truefile = fontfilename
2411        except:
2412            try:
2413                _checkExists(file)
2414                self.truefile = file
2415            except:
2416                raise LrsError("neither '%s' nor '%s' exists"%(fontfilename, file))
2417
2418        self.file = file
2419        self.fontname = fontname
2420        self.fontfilename = fontfilename
2421        self.encoding = encoding
2422
2423    def toLrf(self, lrfWriter):
2424        font = LrfObject("Font", LrsObject.getNextObjId())
2425        lrfWriter.registerFontId(font.objId)
2426        font.appendLrfTag(LrfTag("FontFilename",
2427                                 lrfWriter.toUnicode(self.truefile)))
2428        font.appendLrfTag(LrfTag("FontFacename",
2429                                 lrfWriter.toUnicode(self.fontname)))
2430
2431        stream = LrfFileStream(STREAM_FORCE_COMPRESSED, self.truefile)
2432        font.appendLrfTags(stream.getStreamTags())
2433
2434        lrfWriter.append(font)
2435
2436    def toElement(self, se):
2437        element = Element("RegistFont", encoding="TTF", fontname=self.fontname,
2438                file=self.file, fontfilename=self.file)
2439        return element
2440