def __init__(self, parent): QWidget.__init__(self, parent) self.layout = QTextLayout() self.layout.setFont(self.font()) self.layout.setCacheEnabled(True) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.last_layout_rect = None
class Message(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.layout = QTextLayout() self.layout.setFont(self.font()) self.layout.setCacheEnabled(True) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.last_layout_rect = None def setText(self, text): self.layout.setText(text) self.last_layout_rect = None self.update() def sizeHint(self): return QSize(10, 10) def do_layout(self): ly = self.layout ly.beginLayout() w = self.width() - 5 height = 0 leading = self.fontMetrics().leading() while True: line = ly.createLine() if not line.isValid(): break line.setLineWidth(w) height += leading line.setPosition(QPointF(5, height)) height += line.height() ly.endLayout() def paintEvent(self, ev): if self.last_layout_rect != self.rect(): self.do_layout() p = QPainter(self) br = self.layout.boundingRect() y = 0 if br.height() < self.height(): y = (self.height() - br.height()) / 2 self.layout.draw(p, QPointF(0, y))
def parse_text_formatting(text): pos = 0 tokens = [] for m in re.finditer(r'</?([a-zA-Z1-6]+)/?>', text): q = text[pos:m.start()] if q: tokens.append((False, q)) tokens.append((True, (m.group(1).lower(), '/' in m.group()[:2]))) pos = m.end() if tokens: if text[pos:]: tokens.append((False, text[pos:])) else: tokens = [(False, text)] ranges, open_ranges, text = [], [], [] offset = 0 for is_tag, tok in tokens: if is_tag: tag, closing = tok if closing: if open_ranges: r = open_ranges.pop() r[-1] = offset - r[-2] if r[-1] > 0: ranges.append(r) else: if tag in {'b', 'strong', 'i', 'em'}: open_ranges.append([tag, offset, -1]) else: offset += len(tok.replace('&', '&')) text.append(tok) text = ''.join(text) formats = [] for tag, start, length in chain(ranges, open_ranges): fmt = QTextCharFormat() if tag in {'b', 'strong'}: fmt.setFontWeight(QFont.Weight.Bold) elif tag in {'i', 'em'}: fmt.setFontItalic(True) else: continue if length == -1: length = len(text) - start if length > 0: r = QTextLayout.FormatRange() r.format = fmt r.start, r.length = start, length formats.append(r) return text, formats
def do_tag(block, words, lo, hi, pos, fmts): for word in words[lo:hi]: if word == '\n': if fmts: block.layout().setAdditionalFormats(fmts) pos, block, fmts = 0, block.next(), [] continue if tag in {'replace', 'insert', 'delete'}: fmt = getattr(self.left, '%s_format' % ('replacereplace' if tag == 'replace' else tag)) f = QTextLayout.FormatRange() f.start, f.length, f.format = pos, len(word), fmt fmts.append(f) pos += len(word) return block, pos, fmts
def parse_single_block(self, block): ud, is_new_ud = self.get_user_data(block) orig_state = ud.state pblock = block.previous() if pblock.isValid(): start_state = pblock.userData() if start_state is None: start_state = self.user_data_factory().state else: start_state = start_state.state.copy() else: start_state = self.user_data_factory().state ud.clear(state=start_state, doc_name=self.doc_name) # Ensure no stale user data lingers formats = [] for i, num, fmt in run_loop(ud, self.state_map, self.formats, str(block.text())): if fmt is not None: r = QTextLayout.FormatRange() r.start, r.length, r.format = i, num, fmt formats.append(r) force_next_highlight = is_new_ud or ud.state != orig_state return formats, force_next_highlight
def __init__(self, text='', width=0, font=None, img=None, max_height=100, align=Qt.AlignmentFlag.AlignCenter): self.layouts = [] self._position = Point(0, 0) self.leading = self.line_spacing = 0 if font is not None: fm = QFontMetrics(font, img) self.leading = fm.leading() self.line_spacing = fm.lineSpacing() for text in text.split('<br>') if text else (): text, formats = parse_text_formatting(sanitize(text)) l = QTextLayout(unescape_formatting(text), font, img) l.setAdditionalFormats(formats) to = QTextOption(align) to.setWrapMode(QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere) l.setTextOption(to) l.beginLayout() height = 0 while height + 3*self.leading < max_height: line = l.createLine() if not line.isValid(): break line.setLineWidth(width) height += self.leading line.setPosition(QPointF(0, height)) height += line.height() max_height -= height l.endLayout() if self.layouts: self.layouts.append(self.leading) else: self._position = Point(l.position().x(), l.position().y()) self.layouts.append(l) if self.layouts: self.layouts.append(self.leading)