247 lines
6.5 KiB
C++
247 lines
6.5 KiB
C++
#include "pagedtextitem.h"
|
|
|
|
#include <QPainter>
|
|
#include <QAbstractTextDocumentLayout>
|
|
#include <QTextBlock>
|
|
#include <QTextLayout>
|
|
#include <QFont>
|
|
#include <QPointF>
|
|
#include <QRectF>
|
|
|
|
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);
|
|
}
|