mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-10-31 10:37:00 -04:00 
			
		
		
		
	Re-implemented rendering engine without using Qt's rich text facilities. Much faster this way.
This commit is contained in:
		
							parent
							
								
									126a8771fe
								
							
						
					
					
						commit
						4da344abf5
					
				| @ -14,23 +14,20 @@ | ||||
| ##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
| '''''' | ||||
| 
 | ||||
| import operator, collections, copy, re, sys | ||||
| import collections | ||||
| 
 | ||||
| from PyQt4.QtCore import Qt, QByteArray, SIGNAL, QVariant, QUrl | ||||
| from PyQt4.QtCore import Qt, QByteArray, SIGNAL | ||||
| from PyQt4.QtGui import QGraphicsRectItem, QGraphicsScene, QPen, \ | ||||
|                         QBrush, QColor, QGraphicsTextItem, QFontDatabase, \ | ||||
|                         QFont, QGraphicsItem, QGraphicsLineItem, QPixmap, \ | ||||
|                         QGraphicsPixmapItem, QTextCharFormat, QTextFrameFormat, \ | ||||
|                         QTextBlockFormat, QTextCursor, QTextImageFormat, \ | ||||
|                         QTextDocument, QTextOption | ||||
|                         QBrush, QColor, QFontDatabase, \ | ||||
|                         QGraphicsItem, QGraphicsLineItem | ||||
|                          | ||||
| from libprs500.gui2.lrf_renderer.text import TextBlock, FontLoader, COLOR, PixmapItem | ||||
| 
 | ||||
| 
 | ||||
| from libprs500.ebooks.lrf.fonts import FONT_MAP | ||||
| from libprs500.gui2 import qstring_to_unicode | ||||
| from libprs500.ebooks.hyphenate import hyphenate_word | ||||
| from libprs500.ebooks.BeautifulSoup import Tag | ||||
| from libprs500.ebooks.lrf.objects import RuledLine as _RuledLine | ||||
| from libprs500.ebooks.lrf.objects import Canvas as __Canvas | ||||
| 
 | ||||
| 
 | ||||
| class Color(QColor): | ||||
|     def __init__(self, color): | ||||
|         QColor.__init__(self, color.r, color.g, color.b, 0xff-color.a) | ||||
| @ -40,280 +37,6 @@ class Pen(QPen): | ||||
|         QPen.__init__(self, QBrush(Color(color)), width, | ||||
|                       (Qt.SolidLine if width > 0 else Qt.NoPen)) | ||||
| 
 | ||||
| WEIGHT_MAP = lambda wt : int((wt/10.)-1) | ||||
| 
 | ||||
| class FontLoader(object): | ||||
|      | ||||
|     font_map = { | ||||
|                 'Swis721 BT Roman'     : 'Liberation Sans', | ||||
|                 'Dutch801 Rm BT Roman' : 'Liberation Serif', | ||||
|                 'Courier10 BT Roman'   : 'Liberation Mono', | ||||
|                 } | ||||
|      | ||||
|     def __init__(self, font_map, dpi): | ||||
|         self.face_map = {} | ||||
|         self.cache = {} | ||||
|         self.dpi = dpi | ||||
|         self.face_map = font_map | ||||
|              | ||||
|     def font(self, text_style): | ||||
|         device_font = text_style.fontfacename in FONT_MAP | ||||
|         if device_font: | ||||
|             face = self.font_map[text_style.fontfacename] | ||||
|         else: | ||||
|             face = self.face_map[text_style.fontfacename] | ||||
|          | ||||
|         sz = text_style.fontsize | ||||
|         wt = text_style.fontweight | ||||
|         style = text_style.fontstyle | ||||
|         font = (face, wt, style, sz,) | ||||
|         if font in self.cache: | ||||
|             rfont = self.cache[font] | ||||
|         else: | ||||
|             italic = font[2] == QFont.StyleItalic              | ||||
|             rfont = QFont(font[0], font[3], font[1], italic) | ||||
|             rfont.setPixelSize(font[3]) | ||||
|             rfont.setBold(wt>=69) | ||||
|             self.cache[font] = rfont | ||||
|         qfont = rfont | ||||
|         if text_style.emplinetype != 'none': | ||||
|             qfont = QFont(rfont)             | ||||
|             qfont.setOverline(text_style.emplineposition == 'before') | ||||
|             qfont.setUnderline(text_style.emplineposition == 'after') | ||||
|         return qfont | ||||
|          | ||||
| class ParSkip(object): | ||||
|     def __init__(self, parskip): | ||||
|         self.height = parskip | ||||
|          | ||||
|     def __str__(self): | ||||
|         return 'Parskip: '+str(self.height) | ||||
| 
 | ||||
| class PixmapItem(QGraphicsPixmapItem): | ||||
|     def __init__(self, data, encoding, x0, y0, x1, y1, xsize, ysize): | ||||
|         p = QPixmap() | ||||
|         p.loadFromData(data, encoding, Qt.AutoColor) | ||||
|         w, h = p.width(), p.height() | ||||
|         p = p.copy(x0, y0, min(w, x1-x0), min(h, y1-y0)) | ||||
|         if p.width() != xsize or p.height() != ysize: | ||||
|             p = p.scaled(xsize, ysize, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)  | ||||
|         QGraphicsPixmapItem.__init__(self, p) | ||||
|         self.height, self.width = ysize, xsize | ||||
|         self.setTransformationMode(Qt.SmoothTransformation) | ||||
|         self.setShapeMode(QGraphicsPixmapItem.BoundingRectShape) | ||||
| 
 | ||||
| 
 | ||||
| class Plot(PixmapItem): | ||||
|      | ||||
|     def __init__(self, plot, dpi): | ||||
|         img = plot.refobj | ||||
|         xsize, ysize = dpi*plot.attrs['xsize']/720., dpi*plot.attrs['xsize']/720. | ||||
|         x0, y0, x1, y1 = img.x0, img.y0, img.x1, img.y1 | ||||
|         data, encoding = img.data, img.encoding | ||||
|         PixmapItem.__init__(self, data, encoding, x0, y0, x1, y1, xsize, ysize) | ||||
| 
 | ||||
| 
 | ||||
| class Line(QGraphicsRectItem): | ||||
|     whitespace = re.compile(r'\s+') | ||||
|     no_pen = QPen(Qt.NoPen) | ||||
|     inactive_brush = QBrush(QColor(0x00, 0x00, 0x00, 0x09)) | ||||
|     active_brush   = QBrush(QColor(0x00, 0x00, 0x00, 0x59)) | ||||
|      | ||||
|     line_map = { | ||||
|                 'none'   : QTextCharFormat.NoUnderline, | ||||
|                 'solid'  : QTextCharFormat.SingleUnderline, | ||||
|                 'dotted' : QTextCharFormat.DotLine, | ||||
|                 'dashed' : QTextCharFormat.DashUnderline, | ||||
|                 'double' : QTextCharFormat.WaveUnderline, | ||||
|                 } | ||||
|     dto = QTextOption(Qt.AlignJustify) | ||||
|      | ||||
|     def __init__(self, offset, linespace, linelength, align, hyphenate, ts, block_id): | ||||
|         QGraphicsRectItem.__init__(self, 0, 0, 0, 0) | ||||
|         self.offset, self.line_space, self.line_length = offset, linespace, linelength | ||||
|         self.align = align | ||||
|         self.do_hyphenation = hyphenate | ||||
|         self.setPen(self.__class__.no_pen) | ||||
|         self.is_empty = True | ||||
|         self.highlight_rect = None        | ||||
|         self.cursor = None | ||||
|         self.item = None | ||||
|         self.plot_counter = 0 | ||||
|         self.create_text_item(ts) | ||||
|         self.block_id = block_id | ||||
|                  | ||||
|     def hoverEnterEvent(self, event): | ||||
|         if self.highlight_rect is not None: | ||||
|             self.highlight_rect.setBrush(self.__class__.active_brush) | ||||
|          | ||||
|     def hoverLeaveEvent(self, event): | ||||
|         if self.highlight_rect is not None: | ||||
|             self.highlight_rect.setBrush(self.__class__.inactive_brush) | ||||
|          | ||||
|     def mousePressEvent(self, event): | ||||
|         if self.highlight_rect is not None: | ||||
|             self.hoverLeaveEvent(None) | ||||
|             self.link[1](self.link[0]) | ||||
|      | ||||
|     def create_link(self, pos, in_link): | ||||
|         if not self.acceptsHoverEvents(): | ||||
|             self.setAcceptsHoverEvents(True) | ||||
|             self.highlight_rect = QGraphicsRectItem(pos, 0, 0, 0, self) | ||||
|             self.highlight_rect.setCursor(Qt.PointingHandCursor) | ||||
|             self.link = in_link | ||||
|             self.link_end = sys.maxint | ||||
|              | ||||
|     def end_link(self): | ||||
|         self.link_end = self.item.boundingRect().width() - self.highlight_rect.boundingRect().x() | ||||
|      | ||||
|     def add_plot(self, plot, ts, in_link): | ||||
|         label='plot%d'%(self.plot_counter,) | ||||
|         self.plot_counter += 1 | ||||
|         pos = self.item.boundingRect().width() | ||||
|         self.item.document().addResource(QTextDocument.ImageResource, QUrl(label), | ||||
|                                          QVariant(plot.pixmap())) | ||||
|         qif = QTextImageFormat() | ||||
|         qif.setHeight(plot.height) | ||||
|         qif.setWidth(plot.width) | ||||
|         qif.setName(label) | ||||
|         self.cursor.insertImage(qif, QTextFrameFormat.InFlow) | ||||
|         if in_link: | ||||
|             self.create_link(pos, in_link) | ||||
|          | ||||
|          | ||||
|     def can_add_plot(self, plot): | ||||
|         pos = self.item.boundingRect().width() if self.item is not None else 0 | ||||
|         return self.line_length - pos >= plot.width | ||||
|      | ||||
|     def create_text_item(self, ts): | ||||
|         self.item = QGraphicsTextItem(self) | ||||
|         doc = self.item.document() | ||||
|         doc.setDefaultTextOption(self.__class__.dto) | ||||
|         self.cursor = QTextCursor(doc) | ||||
|         f = self.cursor.currentFrame() | ||||
|         ff = QTextFrameFormat() | ||||
|         ff.setBorder(0) | ||||
|         ff.setPadding(0) | ||||
|         ff.setMargin(0) | ||||
|         f.setFrameFormat(ff) | ||||
|         bf = QTextBlockFormat() | ||||
|         bf.setTopMargin(0) | ||||
|         bf.setRightMargin(0) | ||||
|         bf.setBottomMargin(0) | ||||
|         bf.setRightMargin(0) | ||||
|         bf.setNonBreakableLines(True) | ||||
|         self.cursor.setBlockFormat(bf) | ||||
|          | ||||
|          | ||||
|     def build_char_format(self, ts): | ||||
|         tcf = QTextCharFormat() | ||||
|         tcf.setFont(ts.font) | ||||
|         tcf.setVerticalAlignment(ts.valign) | ||||
|         tcf.setForeground(ts.textcolor) | ||||
|         tcf.setUnderlineColor(ts.linecolor) | ||||
|         if ts.emplineposition == 'after': | ||||
|             tcf.setUnderlineStyle(self.line_map[ts.emplinetype]) | ||||
|         return tcf | ||||
|      | ||||
|     def populate(self, phrase, ts, wordspace, in_link): | ||||
|         phrase_pos = 0 | ||||
|         processed = False | ||||
|         matches = self.__class__.whitespace.finditer(phrase) | ||||
|         tcf = self.build_char_format(ts) | ||||
|         if in_link: | ||||
|             start = self.item.boundingRect().width() | ||||
|         for match in matches: | ||||
|             processed = True | ||||
|             left, right = match.span() | ||||
|             if wordspace == 0: | ||||
|                 right = left | ||||
|             word = phrase[phrase_pos:right] | ||||
|             self.cursor.insertText(word, tcf) | ||||
|             if self.item.boundingRect().width() > self.line_length: | ||||
|                 self.cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, | ||||
|                                          right-left) | ||||
|                 self.cursor.removeSelectedText() | ||||
|                 if self.item.boundingRect().width() <= self.line_length: | ||||
|                     if in_link: self.create_link(start, in_link) | ||||
|                     return right, True | ||||
|                 self.cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, | ||||
|                                          left-phrase_pos) | ||||
|                 self.cursor.removeSelectedText() | ||||
|                 if self.do_hyphenation: | ||||
|                     tokens = hyphenate_word(word) | ||||
|                     for i in range(len(tokens)-2, -1, -1): | ||||
|                         part = ''.join(tokens[0:i+1]) | ||||
|                         self.cursor.insertText(part+'-', tcf) | ||||
|                         if self.item.boundingRect().width() <= self.line_length: | ||||
|                             if in_link: self.create_link(start, in_link) | ||||
|                             return phrase_pos + len(part), True | ||||
|                         self.cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, | ||||
|                                          len(part)+1) | ||||
|                         self.cursor.removeSelectedText() | ||||
|                 if self.cursor.position() < 1: # Force hyphenation as word is longer than line | ||||
|                     for i in range(len(word)-5, 0, -5): | ||||
|                         part = word[:i] | ||||
|                         self.cursor.insertText(part+'-', tcf) | ||||
|                         if self.item.boundingRect().width() <= self.line_length: | ||||
|                             if in_link: self.create_link(start, in_link) | ||||
|                             return phrase_pos + len(part), True | ||||
|                         self.cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, | ||||
|                                          len(part)+1) | ||||
|                         self.cursor.removeSelectedText() | ||||
|                 return phrase_pos, True | ||||
|                          | ||||
|             if in_link: self.create_link(start, in_link) | ||||
|             phrase_pos = right | ||||
|                  | ||||
|         if not processed: | ||||
|             return self.populate(phrase+' ', ts, 0, in_link) | ||||
|              | ||||
|         return phrase_pos, False | ||||
|              | ||||
|      | ||||
|          | ||||
|     def finalize(self, wordspace, vdebug): | ||||
|         crect = self.childrenBoundingRect() | ||||
|         self.width = crect.width() - wordspace | ||||
|         self.height = crect.height() + self.line_space | ||||
|         self.setRect(crect) | ||||
|          | ||||
|         if vdebug: | ||||
|             self.setPen(QPen(Qt.yellow, 1, Qt.DotLine)) | ||||
|         if self.highlight_rect is not None: | ||||
|             x = self.highlight_rect.boundingRect().x() | ||||
|             if self.link_end == sys.maxint: | ||||
|                 self.link_end = crect.width()-x | ||||
|             self.highlight_rect.setRect(crect) | ||||
|             erect = self.highlight_rect.boundingRect() | ||||
|             erect.setX(x) | ||||
|             erect.setWidth(self.link_end) | ||||
|             self.highlight_rect.setRect(erect) | ||||
|             self.highlight_rect.setBrush(self.__class__.inactive_brush) | ||||
|             self.highlight_rect.setZValue(-1) | ||||
|             self.highlight_rect.setPen(self.__class__.no_pen) | ||||
|          | ||||
|         return self.height | ||||
|          | ||||
|     def getx(self, textwidth): | ||||
|         if self.align == 'head': | ||||
|             return self.offset | ||||
|         if self.align == 'foot': | ||||
|             return textwidth - self.width | ||||
|         if self.align == 'center':         | ||||
|             return (textwidth-self.width)/2. | ||||
|          | ||||
|     def __unicode__(self): | ||||
|         s = u'' | ||||
|         for word in self.children(): | ||||
|             if not hasattr(word, 'toPlainText'): | ||||
|                 continue | ||||
|             s += qstring_to_unicode(word.toPlainText()) | ||||
|         return s | ||||
|      | ||||
|     def __str__(self): | ||||
|         return unicode(self).encode('utf-8') | ||||
|          | ||||
| 
 | ||||
| class ContentObject(object): | ||||
|      | ||||
| @ -323,226 +46,6 @@ class ContentObject(object): | ||||
|         self.has_content = True | ||||
| 
 | ||||
| 
 | ||||
| NULL   = lambda a, b: a | ||||
| COLOR  = lambda a, b: QColor(*a) | ||||
| WEIGHT = lambda a, b: WEIGHT_MAP(a) | ||||
| 
 | ||||
| class Style(object): | ||||
|     map = collections.defaultdict(lambda : NULL) | ||||
|      | ||||
|     def __init__(self, style, dpi): | ||||
|         self.fdpi = dpi/720. | ||||
|         self.update(style.as_dict()) | ||||
|              | ||||
|     def update(self, *args, **kwds): | ||||
|         if len(args) > 0: | ||||
|             kwds = args[0] | ||||
|         for attr in kwds: | ||||
|             setattr(self, attr, self.__class__.map[attr](kwds[attr], self.fdpi)) | ||||
|              | ||||
|     def copy(self): | ||||
|         return copy.copy(self) | ||||
|              | ||||
| 
 | ||||
| class TextStyle(Style): | ||||
|      | ||||
|     map = collections.defaultdict(lambda : NULL, | ||||
|         fontsize         = operator.mul, | ||||
|         fontwidth        = operator.mul, | ||||
|         fontweight       = WEIGHT, | ||||
|         textcolor        = COLOR, | ||||
|         textbgcolor      = COLOR, | ||||
|         wordspace        = operator.mul, | ||||
|         letterspace      = operator.mul, | ||||
|         baselineskip     = operator.mul, | ||||
|         linespace        = operator.mul, | ||||
|         parindent        = operator.mul, | ||||
|         parskip          = operator.mul, | ||||
|         textlinewidth    = operator.mul, | ||||
|         charspace        = operator.mul, | ||||
|         linecolor        = COLOR,  | ||||
|         ) | ||||
|      | ||||
|     def __init__(self, style, font_loader, ruby_tags): | ||||
|         self.font_loader = font_loader | ||||
|         self.fontstyle   = QFont.StyleNormal | ||||
|         self.valign      = QTextCharFormat.AlignBottom | ||||
|         for attr in ruby_tags: | ||||
|             setattr(self, attr, ruby_tags[attr]) | ||||
|         Style.__init__(self, style, font_loader.dpi) | ||||
|         self.emplinetype = 'none' | ||||
|         self.font = self.font_loader.font(self) | ||||
|          | ||||
|          | ||||
|     def update(self, *args, **kwds): | ||||
|         Style.update(self, *args, **kwds) | ||||
|         self.font = self.font_loader.font(self) | ||||
|          | ||||
|      | ||||
| class BlockStyle(Style): | ||||
|     map = collections.defaultdict(lambda : NULL, | ||||
|         bgcolor          = COLOR, | ||||
|         framecolor       = COLOR, | ||||
|         ) | ||||
|      | ||||
| 
 | ||||
| class TextBlock(ContentObject): | ||||
|      | ||||
|     has_content = property(fget=lambda self: self.peek_index < len(self.lines)-1) | ||||
|     XML_ENTITIES = dict(zip(Tag.XML_SPECIAL_CHARS_TO_ENTITIES.values(), Tag.XML_SPECIAL_CHARS_TO_ENTITIES.keys()))  | ||||
|     XML_ENTITIES["quot"] = '"' | ||||
|      | ||||
|     class HeightExceeded(Exception):  | ||||
|         pass | ||||
| 
 | ||||
|      | ||||
|     def __init__(self, tb, font_loader, respect_max_y, text_width, logger,  | ||||
|                  opts, ruby_tags, link_activated,  | ||||
|                  parent=None, x=0, y=0): | ||||
|         ContentObject.__init__(self) | ||||
|         self.block_id = tb.id | ||||
|         self.bs, self.ts = BlockStyle(tb.style, font_loader.dpi), \ | ||||
|                             TextStyle(tb.textstyle, font_loader, ruby_tags) | ||||
|         self.bs.update(tb.attrs) | ||||
|         self.ts.update(tb.attrs) | ||||
|         self.lines = [] | ||||
|         self.line_length = min(self.bs.blockwidth, text_width) | ||||
|         self.line_length -= 2*self.bs.sidemargin | ||||
|         self.line_offset = self.bs.sidemargin | ||||
|         self.first_line = True | ||||
|         self.current_style = self.ts.copy() | ||||
|         self.current_line = None | ||||
|         self.font_loader, self.logger, self.opts = font_loader, logger, opts | ||||
|         self.in_link = False | ||||
|         self.link_activated = link_activated | ||||
|         self.max_y = self.bs.blockheight if (respect_max_y or self.bs.blockrule.lower() in ('vert-fixed', 'block-fixed')) else sys.maxint | ||||
|         self.height = 0 | ||||
|         try: | ||||
|             if self.max_y > 0: | ||||
|                 self.populate(tb.content) | ||||
|                 self.end_line() | ||||
|         except TextBlock.HeightExceeded: | ||||
|             logger.warning('TextBlock height exceeded, truncating.') | ||||
|         self.peek_index = -1 | ||||
|          | ||||
|          | ||||
|          | ||||
|     def peek(self): | ||||
|         return self.lines[self.peek_index+1] | ||||
|      | ||||
|     def commit(self): | ||||
|         self.peek_index += 1 | ||||
|      | ||||
|     def reset(self): | ||||
|         self.peek_index = -1 | ||||
|          | ||||
|     def end_link(self): | ||||
|         self.link_activated(self.in_link[0], on_creation=True) | ||||
|         self.in_link = False | ||||
|      | ||||
|     def populate(self, tb): | ||||
|         self.create_line() | ||||
|         open_containers = collections.deque() | ||||
|         self.in_para = False | ||||
|         for i in tb.content: | ||||
|             if isinstance(i, basestring): | ||||
|                 self.process_text(i) | ||||
|             elif i is None: | ||||
|                 if len(open_containers) > 0:  | ||||
|                     for a, b in open_containers.pop(): | ||||
|                         if callable(a): | ||||
|                             a(*b) | ||||
|                         else: | ||||
|                             setattr(self, a, b) | ||||
|             elif i.name == 'P': | ||||
|                 open_containers.append((('in_para', False),)) | ||||
|                 self.in_para = True         | ||||
|             elif i.name == 'CR': | ||||
|                 if self.in_para:                     | ||||
|                     self.end_line() | ||||
|                     self.create_line() | ||||
|                 else: | ||||
|                     self.end_line() | ||||
|                     delta = self.current_style.parskip | ||||
|                     if isinstance(self.lines[-1], ParSkip): | ||||
|                         delta += self.current_style.baselineskip | ||||
|                     self.lines.append(ParSkip(delta)) | ||||
|                     self.first_line = True | ||||
|             elif i.name == 'Span': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                 self.current_style.update(i.attrs)                 | ||||
|             elif i.name == 'CharButton': | ||||
|                 open_containers.append(((self.end_link, []),)) | ||||
|                 self.in_link = (i.attrs['refobj'], self.link_activated) | ||||
|             elif i.name == 'Italic': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                 self.current_style.update(fontstyle=QFont.StyleItalic) | ||||
|             elif i.name == 'Plot': | ||||
|                 plot = Plot(i, self.font_loader.dpi) | ||||
|                 if self.current_line is None: | ||||
|                     self.create_line() | ||||
|                 if not self.current_line.can_add_plot(plot): | ||||
|                     self.end_line() | ||||
|                     self.create_line() | ||||
|                 self.current_line.add_plot(plot, self.current_style, self.in_link) | ||||
|             elif i.name == 'Sup': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                 self.current_style.valign=QTextCharFormat.AlignSuperScript | ||||
|             elif i.name == 'Sub': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                 self.current_style.valign=QTextCharFormat.AlignSubScript | ||||
|             elif i.name == 'EmpLine': | ||||
|                 if i.attrs: | ||||
|                     open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                     self.current_style.update(i.attrs) | ||||
|             else: | ||||
|                 self.logger.warning('Unhandled TextTag %s'%(i.name,)) | ||||
|                 if not i.self_closing: | ||||
|                     open_containers.append([]) | ||||
|                  | ||||
|     def __iter__(self): | ||||
|         for line in self.lines: yield line | ||||
|                  | ||||
|     def end_line(self): | ||||
|         if self.current_line is not None: | ||||
|             self.height += self.current_line.finalize(self.current_style.wordspace, self.opts.visual_debug) | ||||
|             if self.height > self.max_y+10: | ||||
|                 raise TextBlock.HeightExceeded | ||||
|             self.lines.append(self.current_line)             | ||||
|             self.current_line = None | ||||
|      | ||||
|     def create_line(self): | ||||
|         line_length = self.line_length | ||||
|         line_offset = self.line_offset | ||||
|         if self.first_line: | ||||
|             line_length -= self.current_style.parindent | ||||
|             line_offset += self.current_style.parindent | ||||
|         self.current_line = Line(line_offset, self.current_style.linespace,  | ||||
|                                  line_length, self.current_style.align,  | ||||
|                                  self.opts.hyphenate, self.current_style, self.block_id) | ||||
|         self.first_line = False | ||||
|                  | ||||
|     def process_text(self, raw): | ||||
|         for ent, rep in TextBlock.XML_ENTITIES.items(): | ||||
|             raw = raw.replace(u'&%s;'%ent, rep) | ||||
|         while len(raw) > 0: | ||||
|             if self.current_line is None: | ||||
|                 self.create_line() | ||||
|             pos, line_filled = self.current_line.populate(raw, self.current_style,  | ||||
|                                              self.current_style.wordspace, self.in_link) | ||||
|             raw = raw[pos:] | ||||
|             if line_filled: | ||||
|                 self.end_line() | ||||
| 
 | ||||
|                      | ||||
|     def __unicode__(self): | ||||
|         return u'\n'.join(unicode(l) for l in self.lines) | ||||
|      | ||||
|     def __str__(self): | ||||
|          | ||||
|         return '<TextBlock>\n'+unicode(self).encode('utf-8')+'\n</TextBlock>' | ||||
|              | ||||
| 
 | ||||
| class RuledLine(QGraphicsLineItem, ContentObject): | ||||
|      | ||||
|     map = {'solid': Qt.SolidLine, 'dashed': Qt.DashLine, 'dotted': Qt.DotLine, 'double': Qt.DashDotLine} | ||||
| @ -567,9 +70,10 @@ class ImageBlock(PixmapItem, ContentObject): | ||||
| def object_factory(container, obj, respect_max_y=False): | ||||
|     if hasattr(obj, 'name'): | ||||
|         if obj.name.endswith('TextBlock'): | ||||
|              | ||||
|             return TextBlock(obj, container.font_loader, respect_max_y, container.text_width, | ||||
|                              container.logger,  | ||||
|                              container.opts, container.ruby_tags, container.link_activated) | ||||
|                              container.logger, container.opts, container.ruby_tags,  | ||||
|                              container.link_activated) | ||||
|         elif obj.name.endswith('ImageBlock'): | ||||
|             return ImageBlock(obj) | ||||
|     elif isinstance(obj, _RuledLine): | ||||
| @ -623,7 +127,9 @@ class _Canvas(QGraphicsRectItem): | ||||
|             if isinstance(line, QGraphicsItem): | ||||
|                 line.setParentItem(self) | ||||
|                 line.setPos(x + line.getx(textwidth), y) | ||||
|             y += line.height | ||||
|                 y += line.height | ||||
|             else: | ||||
|                 y += line.height | ||||
|             if not block.has_content: | ||||
|                 y += block.bs.footskip | ||||
|                 block_consumed = True | ||||
|  | ||||
							
								
								
									
										520
									
								
								src/libprs500/gui2/lrf_renderer/text.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										520
									
								
								src/libprs500/gui2/lrf_renderer/text.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,520 @@ | ||||
| ##    Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net | ||||
| ##    This program is free software; you can redistribute it and/or modify | ||||
| ##    it under the terms of the GNU General Public License as published by | ||||
| ##    the Free Software Foundation; either version 2 of the License, or | ||||
| ##    (at your option) any later version. | ||||
| ## | ||||
| ##    This program is distributed in the hope that it will be useful, | ||||
| ##    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ##    GNU General Public License for more details. | ||||
| ## | ||||
| ##    You should have received a copy of the GNU General Public License along | ||||
| ##    with this program; if not, write to the Free Software Foundation, Inc., | ||||
| ##    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
| from libprs500.gui2 import qstring_to_unicode | ||||
| '''''' | ||||
| 
 | ||||
| import sys, collections, operator, copy, re | ||||
| 
 | ||||
| from PyQt4.QtCore import Qt, QRectF, QString | ||||
| from PyQt4.QtGui import QFont, QColor, QPixmap, QGraphicsPixmapItem, \ | ||||
|                         QGraphicsItem, QFontMetrics, QPen, QBrush, QGraphicsRectItem | ||||
| 
 | ||||
| from libprs500.ebooks.lrf.fonts import FONT_MAP | ||||
| from libprs500.ebooks.BeautifulSoup import Tag | ||||
| from libprs500.ebooks.hyphenate import hyphenate_word | ||||
| 
 | ||||
| WEIGHT_MAP = lambda wt : int((wt/10.)-1) | ||||
| NULL       = lambda a, b: a | ||||
| COLOR      = lambda a, b: QColor(*a) | ||||
| WEIGHT     = lambda a, b: WEIGHT_MAP(a) | ||||
| 
 | ||||
| class PixmapItem(QGraphicsPixmapItem): | ||||
|     def __init__(self, data, encoding, x0, y0, x1, y1, xsize, ysize): | ||||
|         p = QPixmap() | ||||
|         p.loadFromData(data, encoding, Qt.AutoColor) | ||||
|         w, h = p.width(), p.height() | ||||
|         p = p.copy(x0, y0, min(w, x1-x0), min(h, y1-y0)) | ||||
|         if p.width() != xsize or p.height() != ysize: | ||||
|             p = p.scaled(xsize, ysize, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)  | ||||
|         QGraphicsPixmapItem.__init__(self, p) | ||||
|         self.height, self.width = ysize, xsize | ||||
|         self.setTransformationMode(Qt.SmoothTransformation) | ||||
|         self.setShapeMode(QGraphicsPixmapItem.BoundingRectShape) | ||||
| 
 | ||||
| 
 | ||||
| class Plot(PixmapItem): | ||||
|      | ||||
|     def __init__(self, plot, dpi): | ||||
|         img = plot.refobj | ||||
|         xsize, ysize = dpi*plot.attrs['xsize']/720., dpi*plot.attrs['xsize']/720. | ||||
|         x0, y0, x1, y1 = img.x0, img.y0, img.x1, img.y1 | ||||
|         data, encoding = img.data, img.encoding | ||||
|         PixmapItem.__init__(self, data, encoding, x0, y0, x1, y1, xsize, ysize) | ||||
| 
 | ||||
| 
 | ||||
| class FontLoader(object): | ||||
|      | ||||
|     font_map = { | ||||
|                 'Swis721 BT Roman'     : 'Liberation Sans', | ||||
|                 'Dutch801 Rm BT Roman' : 'Liberation Serif', | ||||
|                 'Courier10 BT Roman'   : 'Liberation Mono', | ||||
|                 } | ||||
|      | ||||
|     def __init__(self, font_map, dpi): | ||||
|         self.face_map = {} | ||||
|         self.cache = {} | ||||
|         self.dpi = dpi | ||||
|         self.face_map = font_map | ||||
|              | ||||
|     def font(self, text_style): | ||||
|         device_font = text_style.fontfacename in FONT_MAP | ||||
|         if device_font: | ||||
|             face = self.font_map[text_style.fontfacename] | ||||
|         else: | ||||
|             face = self.face_map[text_style.fontfacename] | ||||
|          | ||||
|         sz = text_style.fontsize | ||||
|         wt = text_style.fontweight | ||||
|         style = text_style.fontstyle | ||||
|         font = (face, wt, style, sz,) | ||||
|         if font in self.cache: | ||||
|             rfont = self.cache[font] | ||||
|         else: | ||||
|             italic = font[2] == QFont.StyleItalic              | ||||
|             rfont = QFont(font[0], font[3], font[1], italic) | ||||
|             rfont.setPixelSize(font[3]) | ||||
|             rfont.setBold(wt>=69) | ||||
|             self.cache[font] = rfont | ||||
|         qfont = rfont | ||||
|         if text_style.emplinetype != 'none': | ||||
|             qfont = QFont(rfont)             | ||||
|             qfont.setOverline(text_style.emplineposition == 'before') | ||||
|             qfont.setUnderline(text_style.emplineposition == 'after') | ||||
|         return qfont | ||||
| 
 | ||||
| class Style(object): | ||||
|     map = collections.defaultdict(lambda : NULL) | ||||
|      | ||||
|     def __init__(self, style, dpi): | ||||
|         self.fdpi = dpi/720. | ||||
|         self.update(style.as_dict()) | ||||
|              | ||||
|     def update(self, *args, **kwds): | ||||
|         if len(args) > 0: | ||||
|             kwds = args[0] | ||||
|         for attr in kwds: | ||||
|             setattr(self, attr, self.__class__.map[attr](kwds[attr], self.fdpi)) | ||||
|              | ||||
|     def copy(self): | ||||
|         return copy.copy(self) | ||||
|              | ||||
| 
 | ||||
| class TextStyle(Style): | ||||
|      | ||||
|     map = collections.defaultdict(lambda : NULL, | ||||
|         fontsize         = operator.mul, | ||||
|         fontwidth        = operator.mul, | ||||
|         fontweight       = WEIGHT, | ||||
|         textcolor        = COLOR, | ||||
|         textbgcolor      = COLOR, | ||||
|         wordspace        = operator.mul, | ||||
|         letterspace      = operator.mul, | ||||
|         baselineskip     = operator.mul, | ||||
|         linespace        = operator.mul, | ||||
|         parindent        = operator.mul, | ||||
|         parskip          = operator.mul, | ||||
|         textlinewidth    = operator.mul, | ||||
|         charspace        = operator.mul, | ||||
|         linecolor        = COLOR,  | ||||
|         ) | ||||
|      | ||||
|     def __init__(self, style, font_loader, ruby_tags): | ||||
|         self.font_loader = font_loader | ||||
|         self.fontstyle   = QFont.StyleNormal | ||||
|         for attr in ruby_tags: | ||||
|             setattr(self, attr, ruby_tags[attr]) | ||||
|         Style.__init__(self, style, font_loader.dpi) | ||||
|         self.emplinetype = 'none' | ||||
|         self.font = self.font_loader.font(self)         | ||||
|          | ||||
|          | ||||
|     def update(self, *args, **kwds): | ||||
|         Style.update(self, *args, **kwds) | ||||
|         self.font = self.font_loader.font(self) | ||||
|          | ||||
|      | ||||
| class BlockStyle(Style): | ||||
|     map = collections.defaultdict(lambda : NULL, | ||||
|         bgcolor          = COLOR, | ||||
|         framecolor       = COLOR, | ||||
|         ) | ||||
|      | ||||
| class ParSkip(object): | ||||
|     def __init__(self, parskip): | ||||
|         self.height = parskip | ||||
|          | ||||
|     def __str__(self): | ||||
|         return 'Parskip: '+str(self.height) | ||||
| 
 | ||||
| 
 | ||||
| class TextBlock(object): | ||||
|      | ||||
|     class HeightExceeded(Exception): | ||||
|         pass | ||||
|      | ||||
|     has_content = property(fget=lambda self: self.peek_index < len(self.lines)-1) | ||||
|     XML_ENTITIES = dict(zip(Tag.XML_SPECIAL_CHARS_TO_ENTITIES.values(), Tag.XML_SPECIAL_CHARS_TO_ENTITIES.keys()))  | ||||
|     XML_ENTITIES["quot"] = '"' | ||||
|      | ||||
|     def __init__(self, tb, font_loader, respect_max_y, text_width, logger,  | ||||
|                  opts, ruby_tags, link_activated): | ||||
|         self.block_id = tb.id | ||||
|         self.bs, self.ts = BlockStyle(tb.style, font_loader.dpi), \ | ||||
|                             TextStyle(tb.textstyle, font_loader, ruby_tags) | ||||
|         self.bs.update(tb.attrs) | ||||
|         self.ts.update(tb.attrs) | ||||
|         self.lines = collections.deque() | ||||
|         self.line_length = min(self.bs.blockwidth, text_width) | ||||
|         self.line_length -= 2*self.bs.sidemargin | ||||
|         self.line_offset = self.bs.sidemargin | ||||
|         self.first_line = True | ||||
|         self.current_style = self.ts.copy() | ||||
|         self.current_line = None | ||||
|         self.font_loader, self.logger, self.opts = font_loader, logger, opts | ||||
|         self.in_link = False | ||||
|         self.link_activated = link_activated | ||||
|         self.max_y = self.bs.blockheight if (respect_max_y or self.bs.blockrule.lower() in ('vert-fixed', 'block-fixed')) else sys.maxint | ||||
|         self.height = 0 | ||||
|         self.peek_index = -1 | ||||
|          | ||||
|         try: | ||||
|             self.populate(tb.content) | ||||
|             self.end_line() | ||||
|         except TextBlock.HeightExceeded, err: | ||||
|             logger.warning('TextBlock height exceeded, skipping line:\n%s'%(err,)) | ||||
|          | ||||
|     def peek(self): | ||||
|         return self.lines[self.peek_index+1] | ||||
|      | ||||
|     def commit(self): | ||||
|         self.peek_index += 1 | ||||
|      | ||||
|     def reset(self): | ||||
|         self.peek_index = -1 | ||||
|          | ||||
|     def create_link(self, refobj): | ||||
|         if self.current_line is None: | ||||
|             self.create_line() | ||||
|         self.current_line.start_link(refobj, self.link_activated) | ||||
|         self.link_activated(refobj, on_creation=True) | ||||
|      | ||||
|     def end_link(self): | ||||
|         if self.current_line is not None: | ||||
|             self.current_line.end_link() | ||||
|          | ||||
|      | ||||
|     def populate(self, tb): | ||||
|         self.create_line() | ||||
|         open_containers = collections.deque() | ||||
|         self.in_para = False | ||||
|         for i in tb.content: | ||||
|             if isinstance(i, basestring): | ||||
|                 self.process_text(i) | ||||
|             elif i is None: | ||||
|                 if len(open_containers) > 0:  | ||||
|                     for a, b in open_containers.pop(): | ||||
|                         if callable(a): | ||||
|                             a(*b) | ||||
|                         else: | ||||
|                             setattr(self, a, b) | ||||
|             elif i.name == 'P': | ||||
|                 open_containers.append((('in_para', False),)) | ||||
|                 self.in_para = True         | ||||
|             elif i.name == 'CR': | ||||
|                 if self.in_para:                     | ||||
|                     self.end_line() | ||||
|                     self.create_line() | ||||
|                 else: | ||||
|                     self.end_line() | ||||
|                     delta = self.current_style.parskip | ||||
|                     if isinstance(self.lines[-1], ParSkip): | ||||
|                         delta += self.current_style.baselineskip | ||||
|                     self.lines.append(ParSkip(delta)) | ||||
|                     self.first_line = True | ||||
|             elif i.name == 'Span': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                 self.current_style.update(i.attrs)                 | ||||
|             elif i.name == 'CharButton': | ||||
|                 open_containers.append(((self.end_link, []),)) | ||||
|                 self.create_link(i.attrs['refobj'])                 | ||||
|             elif i.name == 'Italic': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                 self.current_style.update(fontstyle=QFont.StyleItalic) | ||||
|             elif i.name == 'Plot': | ||||
|                 plot = Plot(i, self.font_loader.dpi) | ||||
|                 if self.current_line is None: | ||||
|                     self.create_line() | ||||
|                 if not self.current_line.can_add_plot(plot): | ||||
|                     self.end_line() | ||||
|                     self.create_line() | ||||
|                 self.current_line.add_plot(plot) | ||||
|             elif i.name == 'Sup': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|             elif i.name == 'Sub': | ||||
|                 open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|             elif i.name == 'EmpLine': | ||||
|                 if i.attrs: | ||||
|                     open_containers.append((('current_style', self.current_style.copy()),)) | ||||
|                     self.current_style.update(i.attrs) | ||||
|             else: | ||||
|                 self.logger.warning('Unhandled TextTag %s'%(i.name,)) | ||||
|                 if not i.self_closing: | ||||
|                     open_containers.append([]) | ||||
|                  | ||||
|     def end_line(self): | ||||
|         if self.current_line is not None: | ||||
|             self.height += self.current_line.finalize(self.current_style.baselineskip, | ||||
|                                                       self.current_style.linespace, | ||||
|                                                       self.opts.visual_debug) | ||||
|             if self.height > self.max_y+10: | ||||
|                 raise TextBlock.HeightExceeded(str(self.current_line)) | ||||
|             self.lines.append(self.current_line)             | ||||
|             self.current_line = None | ||||
|      | ||||
|     def create_line(self): | ||||
|         line_length = self.line_length | ||||
|         line_offset = self.line_offset | ||||
|         if self.first_line: | ||||
|             line_length -= self.current_style.parindent | ||||
|             line_offset += self.current_style.parindent | ||||
|         self.current_line = Line(line_length, line_offset,  | ||||
|                                  self.current_style.linespace,  | ||||
|                                  self.current_style.align,  | ||||
|                                  self.opts.hyphenate, self.block_id) | ||||
|         self.first_line = False | ||||
|      | ||||
|     def process_text(self, raw): | ||||
|         for ent, rep in TextBlock.XML_ENTITIES.items(): | ||||
|             raw = raw.replace(u'&%s;'%ent, rep) | ||||
|         while len(raw) > 0: | ||||
|             if self.current_line is None: | ||||
|                 self.create_line() | ||||
|             pos, line_filled = self.current_line.populate(raw, self.current_style) | ||||
|             raw = raw[pos:] | ||||
|             if line_filled: | ||||
|                 self.end_line() | ||||
|              | ||||
|      | ||||
|     def __iter__(self): | ||||
|         for line in self.lines: yield line | ||||
|          | ||||
|     def __str__(self): | ||||
|         s = '' | ||||
|         for line in self: | ||||
|             s += str(line) + '\n' | ||||
|         return s | ||||
| 
 | ||||
| class Link(QGraphicsRectItem): | ||||
|     inactive_brush = QBrush(QColor(0x00, 0x00, 0x00, 0x09)) | ||||
|     active_brush   = QBrush(QColor(0x00, 0x00, 0x00, 0x59)) | ||||
|      | ||||
|     def __init__(self, parent, start, stop, refobj, slot): | ||||
|         QGraphicsRectItem.__init__(self, start, 0, stop-start, parent.height, parent) | ||||
|         self.refobj = refobj | ||||
|         self.slot = slot | ||||
|         self.setBrush(self.__class__.inactive_brush) | ||||
|         self.setPen(QPen(Qt.NoPen)) | ||||
|         self.setCursor(Qt.PointingHandCursor) | ||||
|         self.setAcceptsHoverEvents(True) | ||||
|          | ||||
|     def hoverEnterEvent(self, event): | ||||
|         self.setBrush(self.__class__.active_brush) | ||||
|          | ||||
|     def hoverLeaveEvent(self, event): | ||||
|         self.setBrush(self.__class__.inactive_brush) | ||||
|          | ||||
|     def mousePressEvent(self, event): | ||||
|         self.hoverLeaveEvent(None) | ||||
|         self.slot(self.refobj) | ||||
| 
 | ||||
| class Line(QGraphicsItem): | ||||
|     whitespace = re.compile(r'\s+') | ||||
|      | ||||
|     def __init__(self, line_length, offset, linespace, align, hyphenate, block_id): | ||||
|         QGraphicsItem.__init__(self) | ||||
|          | ||||
|         self.line_length, self.offset, self.line_space = line_length, offset, linespace | ||||
|         self.align, self.hyphenate, self.block_id = align, hyphenate, block_id | ||||
|          | ||||
|         self.tokens = collections.deque() | ||||
|         self.current_width = 0 | ||||
|         self.length_in_space = 0 | ||||
|         self.height, self.descent = 0, 0 | ||||
|         self.links = collections.deque() | ||||
|         self.current_link = None | ||||
|          | ||||
|     def start_link(self, refobj, slot): | ||||
|         self.current_link = [self.current_width, sys.maxint, refobj, slot] | ||||
|          | ||||
|     def end_link(self): | ||||
|         if self.current_link is not None: | ||||
|             self.current_link[1] = self.current_width | ||||
|             self.links.append(self.current_link) | ||||
|             self.current_link = None | ||||
|          | ||||
|     def can_add_plot(self, plot): | ||||
|         return self.line_length - self.current_width >= plot.width | ||||
|      | ||||
|     def add_plot(self, plot): | ||||
|         self.tokens.append(plot) | ||||
|         self.current_width += plot.width | ||||
|         self.height = max(self.height, plot.height) | ||||
|      | ||||
|     def populate(self, phrase, ts, process_space=True): | ||||
|         phrase_pos = 0 | ||||
|         processed = False | ||||
|         matches = self.__class__.whitespace.finditer(phrase) | ||||
|         font = QFont(ts.font) | ||||
|         fm = QFontMetrics(font) | ||||
|         single_space_width = fm.width(' ') | ||||
|         height, descent = fm.height(), fm.descent() | ||||
|         for match in matches: | ||||
|             processed = True | ||||
|             left, right = match.span() | ||||
|             if not process_space: | ||||
|                 right = left | ||||
|             space_width = single_space_width * (right-left) | ||||
|             word = phrase[phrase_pos:left] | ||||
|             width = fm.width(word) | ||||
|             if self.current_width + width < self.line_length: | ||||
|                 self.commit(word, width, height, descent, ts, font) | ||||
|                 if space_width > 0  and self.current_width + space_width < self.line_length: | ||||
|                     self.add_space(space_width) | ||||
|                 phrase_pos = right | ||||
|                 continue | ||||
|              | ||||
|             # Word doesn't fit on line | ||||
|             if self.hyphenate and len(word) > 3: | ||||
|                 tokens = hyphenate_word(word) | ||||
|                 for i in range(len(tokens)-2, -1, -1): | ||||
|                     word = ''.join(tokens[0:i+1])+'-' | ||||
|                     width = fm.width(word) | ||||
|                     if self.current_width + width < self.line_length: | ||||
|                         self.commit(word, width, height, descent, ts, font) | ||||
|                         return phrase_pos + len(word)-1, True | ||||
|             if self.current_width < 5: # Force hyphenation as word is longer than line | ||||
|                 for i in range(len(word)-5, 0, -5): | ||||
|                     part = word[:i] + '-' | ||||
|                     width = fm.width(part) | ||||
|                     if self.current_width + width < self.line_length: | ||||
|                         self.commit(part, width, height, descent, ts, font) | ||||
|                         return phrase_pos + len(part)-1, True | ||||
|             # Failed to add word. | ||||
|             return phrase_pos, True | ||||
|                          | ||||
|         if not processed: | ||||
|             return self.populate(phrase+' ', ts, False) | ||||
|              | ||||
|         return phrase_pos, False | ||||
|      | ||||
|     def commit(self, word, width, height, descent, ts, font): | ||||
|         self.tokens.append(Word(word, width, height, ts, font)) | ||||
|         self.current_width += width | ||||
|         self.height = max(self.height, height) | ||||
|         self.descent = max(self.descent, descent) | ||||
|          | ||||
|     def add_space(self, min_width): | ||||
|         self.tokens.append(min_width) | ||||
|         self.current_width += min_width | ||||
|         self.length_in_space += min_width | ||||
|      | ||||
|     def justify(self): | ||||
|         delta = self.line_length - self.current_width | ||||
|         if self.length_in_space > 0: | ||||
|             frac = 1 + float(delta)/self.length_in_space | ||||
|             for i in range(len(self.tokens)): | ||||
|                 if isinstance(self.tokens[i], (int, float)): | ||||
|                     self.tokens[i] *= frac | ||||
|             self.current_width = self.line_length | ||||
|      | ||||
|     def finalize(self, baselineskip, linespace, vdebug): | ||||
|         if self.current_width >= 0.85 * self.line_length: | ||||
|             self.justify() | ||||
|              | ||||
|         self.width = float(self.current_width) | ||||
|         if self.height == 0: | ||||
|             self.height = baselineskip | ||||
|         self.height += linespace | ||||
|         self.height = float(self.height) | ||||
|          | ||||
|         self.vdebug = vdebug | ||||
|          | ||||
|         if self.current_link is not None: | ||||
|             self.end_link() | ||||
|         for link in self.links: | ||||
|             Link(self, *link) | ||||
|              | ||||
|          | ||||
|         return self.height | ||||
|      | ||||
|     def boundingRect(self): | ||||
|         return QRectF(0, 0, self.width, self.height) | ||||
|      | ||||
|     def paint(self, painter, option, widget): | ||||
|         x, y = 0, 0+self.height-self.descent | ||||
|         if self.vdebug: | ||||
|             painter.save() | ||||
|             painter.setPen(QPen(Qt.yellow, 1, Qt.DotLine)) | ||||
|             painter.drawRect(self.boundingRect()) | ||||
|             painter.restore() | ||||
|         painter.save() | ||||
|         for tok in self.tokens: | ||||
|             if isinstance(tok, (int, float)): | ||||
|                 x += tok | ||||
|             elif isinstance(tok, Word): | ||||
|                 painter.setFont(tok.font) | ||||
|                 p = painter.pen() | ||||
|                 painter.setPen(QPen(tok.text_color)) | ||||
|                 painter.drawText(x, y, tok.string) | ||||
|                 painter.setPen(p) | ||||
|                 x += tok.width | ||||
|             else: | ||||
|                 painter.drawPixmap(x, 0, tok.pixmap()) | ||||
|                 x += tok.width | ||||
|         painter.restore() | ||||
|          | ||||
|     def getx(self, textwidth): | ||||
|         if self.align == 'head': | ||||
|             return self.offset | ||||
|         if self.align == 'foot': | ||||
|             return textwidth - self.width | ||||
|         if self.align == 'center':         | ||||
|             return (textwidth-self.width)/2. | ||||
|          | ||||
|     def __unicode__(self): | ||||
|         s = u'' | ||||
|         for tok in self.tokens: | ||||
|             if isinstance(tok, (int, float)): | ||||
|                 s += ' :%.1f: '%(tok,) | ||||
|             elif isinstance(tok, Word): | ||||
|                 s += qstring_to_unicode(tok.string) | ||||
|         return s  | ||||
|      | ||||
|     def __str__(self): | ||||
|         return unicode(self).encode('utf-8') | ||||
|          | ||||
| 
 | ||||
| class Word(object): | ||||
|      | ||||
|     def __init__(self, string, width, height, ts, font): | ||||
|         self.string, self.width, self.height = QString(string), width, height | ||||
|         self.font = font | ||||
|         self.text_color = ts.textcolor | ||||
|          | ||||
| def main(args=sys.argv): | ||||
|     return 0 | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     sys.exit(main()) | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user