#include "pagedtextitem.h" #include #include #include #include #include #include #include PagedTextItem::PagedTextItem(QQuickItem *parent) : QQuickPaintedItem(parent) , m_fontPixelSize(16) , m_page(0) , m_pendingPosition(-1) , m_layoutPageHeight(1) { // nessun margine interno: i margini li impone la geometria dell'item in QML m_doc.setDocumentMargin(0); applyFont(); } void PagedTextItem::applyFont() { QFont f = m_doc.defaultFont(); if (!m_fontFamily.isEmpty()) f.setFamily(m_fontFamily); f.setPixelSize(qMax(4, m_fontPixelSize)); m_doc.setDefaultFont(f); } void PagedTextItem::setHtml(const QString &html) { if (m_html == html) return; m_html = html; m_page = 0; m_doc.setHtml(m_html); if (!m_baseUrl.isEmpty()) m_doc.setBaseUrl(QUrl(m_baseUrl)); relayout(false); emit htmlChanged(); } void PagedTextItem::setBaseUrl(const QString &url) { if (m_baseUrl == url) return; m_baseUrl = url; m_doc.setBaseUrl(QUrl(url)); emit baseUrlChanged(); update(); } void PagedTextItem::setFontFamily(const QString &family) { if (m_fontFamily == family) return; // posizione letta PRIMA di toccare il font: il layout e' ancora coerente const int keep = positionAtPage(m_page); m_fontFamily = family; applyFont(); relayoutKeeping(keep); emit fontFamilyChanged(); } void PagedTextItem::setFontPixelSize(int px) { px = qMax(4, px); if (m_fontPixelSize == px) return; // posizione letta PRIMA del reflow (vedi setFontFamily) const int keep = positionAtPage(m_page); m_fontPixelSize = px; applyFont(); // reflow: mantiene la posizione di lettura per carattere relayoutKeeping(keep); emit fontPixelSizeChanged(); } void PagedTextItem::setTextColor(const QColor &color) { if (m_textColor == color) return; m_textColor = color; emit textColorChanged(); update(); } void PagedTextItem::setPage(int page) { const int last = pageCount() - 1; page = qBound(0, page, qMax(0, last)); if (m_page == page) return; m_page = page; emit pageChanged(); emit positionChanged(); update(); } void PagedTextItem::nextPage() { setPage(m_page + 1); } void PagedTextItem::previousPage() { setPage(m_page - 1); } void PagedTextItem::goToPosition(int pos) { if (width() <= 0 || height() <= 0) { // geometria non ancora risolta: applica al primo layout m_pendingPosition = pos; return; } setPage(pageForPosition(pos)); } int PagedTextItem::pageCount() const { const int n = m_doc.pageCount(); return n > 0 ? n : 1; } int PagedTextItem::characterCount() const { return m_doc.characterCount(); } int PagedTextItem::position() const { return positionAtPage(m_page); } qreal PagedTextItem::pageHeight() const { // altezza dell'ULTIMO layout: se la geometria e' appena cambiata ma il // documento non e' ancora stato ri-impaginato, le letture restano coerenti return m_layoutPageHeight > 0 ? m_layoutPageHeight : qMax(qreal(1), height()); } int PagedTextItem::positionAtPage(int page) const { if (m_doc.isEmpty() || width() <= 0 || height() <= 0) return 0; QAbstractTextDocumentLayout *layout = m_doc.documentLayout(); if (!layout) return 0; const qreal ph = pageHeight(); // punto appena dentro la pagina: hitTest dà il carattere piu' vicino const QPointF probe(qMin(qreal(4), width() / 2), page * ph + 1); const int pos = layout->hitTest(probe, Qt::FuzzyHit); if (pos < 0) return 0; return qBound(0, pos, qMax(0, m_doc.characterCount() - 1)); } int PagedTextItem::pageForPosition(int pos) const { if (m_doc.isEmpty() || width() <= 0 || height() <= 0) return 0; const int len = m_doc.characterCount(); const int at = qBound(0, pos, qMax(0, len - 1)); // findBlock invece di QTextCursor: metodo const (niente cast sul documento) const QTextBlock block = m_doc.findBlock(at); QRectF rect; if (QAbstractTextDocumentLayout *layout = m_doc.documentLayout()) rect = layout->blockBoundingRect(block); qreal y = rect.top(); if (block.layout()) { // y della riga che contiene il carattere, relativa al blocco: cosi' // anche i paragrafi lunghi (spezzati su piu' pagine) sono precisi const int inBlock = qBound(0, at - block.position(), qMax(0, block.length() - 1)); const QTextLine line = block.layout()->lineForTextPosition(inBlock); if (line.isValid()) y += line.y(); } return qMax(0, int(y / pageHeight())); } void PagedTextItem::clampPage() { const int last = pageCount() - 1; m_page = qBound(0, m_page, qMax(0, last)); } void PagedTextItem::relayoutKeeping(int pos) { if (width() <= 0 || height() <= 0) return; m_doc.setPageSize(QSizeF(width(), height())); m_layoutPageHeight = qMax(qreal(1), height()); if (m_pendingPosition >= 0) { // posizione chiesta prima del layout (ripristino all'apertura) m_page = pageForPosition(m_pendingPosition); m_pendingPosition = -1; } else if (pos >= 0) { m_page = pageForPosition(pos); } clampPage(); emit layoutChanged(); emit positionChanged(); update(); } void PagedTextItem::relayout(bool keepPosition) { relayoutKeeping(keepPosition ? positionAtPage(m_page) : -1); } void PagedTextItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry); if (newGeometry.size() != oldGeometry.size()) relayout(true); } void PagedTextItem::renderPage(QPainter *painter) const { if (!painter || width() <= 0 || height() <= 0 || m_doc.isEmpty()) return; const qreal ph = pageHeight(); painter->save(); painter->setRenderHint(QPainter::TextAntialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true); painter->setClipRect(QRectF(0, 0, width(), ph)); painter->translate(0, -m_page * ph); QAbstractTextDocumentLayout::PaintContext ctx; // il clip del contesto e' in coordinate documento ctx.clip = QRectF(0, m_page * ph, width(), ph); if (m_textColor.isValid()) ctx.palette.setColor(QPalette::Text, m_textColor); m_doc.documentLayout()->draw(painter, ctx); painter->restore(); } void PagedTextItem::paint(QPainter *painter) { renderPage(painter); }