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