QLitehtml: Make container_qpainter usable without litehtml internals

We use it elsewhere to "paint" html to a printer (for generating a PDF).
It is much easier if we don't need to pull litehtml internals into that.

Change-Id: I447fa5442f02a6b5e84524f82089513f0c569939
Reviewed-by: Cristian Adam <cristian.adam@qt.io>
This commit is contained in:
Eike Ziller
2020-09-30 14:00:56 +02:00
parent 3433b8c24f
commit da8db0f24d
7 changed files with 434 additions and 333 deletions

View File

@@ -35,7 +35,7 @@ add_qtc_library(qlitehtml
PROPERTIES
POSITION_INDEPENDENT_CODE ON
SOURCES
container_qpainter.cpp container_qpainter.h
container_qpainter.cpp container_qpainter.h container_qpainter_p.h
qlitehtml_global.h
qlitehtmlwidget.cpp qlitehtmlwidget.h
)

View File

@@ -24,6 +24,7 @@
****************************************************************************/
#include "container_qpainter.h"
#include "container_qpainter_p.h"
#include <QClipboard>
#include <QCursor>
@@ -440,16 +441,18 @@ QRect Selection::boundingRect() const
return rect;
}
DocumentContainer::DocumentContainer() = default;
DocumentContainer::DocumentContainer()
: d(new DocumentContainerPrivate)
{}
DocumentContainer::~DocumentContainer() = default;
litehtml::uint_ptr DocumentContainer::create_font(const litehtml::tchar_t *faceName,
int size,
int weight,
litehtml::font_style italic,
unsigned int decoration,
litehtml::font_metrics *fm)
litehtml::uint_ptr DocumentContainerPrivate::create_font(const litehtml::tchar_t *faceName,
int size,
int weight,
litehtml::font_style italic,
unsigned int decoration,
litehtml::font_metrics *fm)
{
const QStringList splitNames = QString::fromUtf8(faceName).split(',', Qt::SkipEmptyParts);
QStringList familyNames;
@@ -515,23 +518,23 @@ litehtml::uint_ptr DocumentContainer::create_font(const litehtml::tchar_t *faceN
return reinterpret_cast<litehtml::uint_ptr>(font);
}
void DocumentContainer::delete_font(litehtml::uint_ptr hFont)
void DocumentContainerPrivate::delete_font(litehtml::uint_ptr hFont)
{
auto font = reinterpret_cast<Font *>(hFont);
delete font;
}
int DocumentContainer::text_width(const litehtml::tchar_t *text, litehtml::uint_ptr hFont)
int DocumentContainerPrivate::text_width(const litehtml::tchar_t *text, litehtml::uint_ptr hFont)
{
const QFontMetrics fm(toQFont(hFont));
return fm.horizontalAdvance(QString::fromUtf8(text));
}
void DocumentContainer::draw_text(litehtml::uint_ptr hdc,
const litehtml::tchar_t *text,
litehtml::uint_ptr hFont,
litehtml::web_color color,
const litehtml::position &pos)
void DocumentContainerPrivate::draw_text(litehtml::uint_ptr hdc,
const litehtml::tchar_t *text,
litehtml::uint_ptr hFont,
litehtml::web_color color,
const litehtml::position &pos)
{
auto painter = toQPainter(hdc);
painter->setFont(toQFont(hFont));
@@ -539,23 +542,24 @@ void DocumentContainer::draw_text(litehtml::uint_ptr hdc,
painter->drawText(toQRect(pos), 0, QString::fromUtf8(text));
}
int DocumentContainer::pt_to_px(int pt)
int DocumentContainerPrivate::pt_to_px(int pt)
{
// magic factor of 11/12 to account for differences to webengine/webkit
return m_paintDevice->physicalDpiY() * pt * 11 / m_paintDevice->logicalDpiY() / 12;
}
int DocumentContainer::get_default_font_size() const
int DocumentContainerPrivate::get_default_font_size() const
{
return m_defaultFont.pointSize();
}
const litehtml::tchar_t *DocumentContainer::get_default_font_name() const
const litehtml::tchar_t *DocumentContainerPrivate::get_default_font_name() const
{
return m_defaultFontFamilyName.constData();
}
void DocumentContainer::draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker)
void DocumentContainerPrivate::draw_list_marker(litehtml::uint_ptr hdc,
const litehtml::list_marker &marker)
{
auto painter = toQPainter(hdc);
if (marker.image.empty()) {
@@ -586,9 +590,9 @@ void DocumentContainer::draw_list_marker(litehtml::uint_ptr hdc, const litehtml:
}
}
void DocumentContainer::load_image(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
bool redraw_on_ready)
void DocumentContainerPrivate::load_image(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
bool redraw_on_ready)
{
const auto qtSrc = QString::fromUtf8(src);
const auto qtBaseUrl = QString::fromUtf8(baseurl);
@@ -604,9 +608,9 @@ void DocumentContainer::load_image(const litehtml::tchar_t *src,
m_pixmaps.insert(url, pixmap);
}
void DocumentContainer::get_image_size(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
litehtml::size &sz)
void DocumentContainerPrivate::get_image_size(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
litehtml::size &sz)
{
const auto qtSrc = QString::fromUtf8(src);
const auto qtBaseUrl = QString::fromUtf8(baseurl);
@@ -619,7 +623,7 @@ void DocumentContainer::get_image_size(const litehtml::tchar_t *src,
sz.height = pm.height();
}
void DocumentContainer::drawSelection(QPainter *painter, const QRect &clip) const
void DocumentContainerPrivate::drawSelection(QPainter *painter, const QRect &clip) const
{
painter->save();
painter->setClipRect(clip, Qt::IntersectClip);
@@ -639,7 +643,7 @@ static QString tagName(const litehtml::element::ptr &e)
return current ? QString::fromUtf8(current->get_tagName()) : QString();
}
void DocumentContainer::buildIndex()
void DocumentContainerPrivate::buildIndex()
{
m_index.elementToIndex.clear();
m_index.indexToElement.clear();
@@ -666,7 +670,8 @@ void DocumentContainer::buildIndex()
}
}
void DocumentContainer::draw_background(litehtml::uint_ptr hdc, const litehtml::background_paint &bg)
void DocumentContainerPrivate::draw_background(litehtml::uint_ptr hdc,
const litehtml::background_paint &bg)
{
auto painter = toQPainter(hdc);
if (bg.is_root) {
@@ -749,10 +754,10 @@ void DocumentContainer::draw_background(litehtml::uint_ptr hdc, const litehtml::
painter->restore();
}
void DocumentContainer::draw_borders(litehtml::uint_ptr hdc,
const litehtml::borders &borders,
const litehtml::position &draw_pos,
bool root)
void DocumentContainerPrivate::draw_borders(litehtml::uint_ptr hdc,
const litehtml::borders &borders,
const litehtml::position &draw_pos,
bool root)
{
Q_UNUSED(root)
// TODO: special border styles
@@ -815,18 +820,18 @@ void DocumentContainer::draw_borders(litehtml::uint_ptr hdc,
}
}
void DocumentContainer::set_caption(const litehtml::tchar_t *caption)
void DocumentContainerPrivate::set_caption(const litehtml::tchar_t *caption)
{
m_caption = QString::fromUtf8(caption);
}
void DocumentContainer::set_base_url(const litehtml::tchar_t *base_url)
void DocumentContainerPrivate::set_base_url(const litehtml::tchar_t *base_url)
{
m_baseUrl = QString::fromUtf8(base_url);
}
void DocumentContainer::link(const std::shared_ptr<litehtml::document> &doc,
const litehtml::element::ptr &el)
void DocumentContainerPrivate::link(const std::shared_ptr<litehtml::document> &doc,
const litehtml::element::ptr &el)
{
// TODO
qDebug(log) << "link";
@@ -834,20 +839,20 @@ void DocumentContainer::link(const std::shared_ptr<litehtml::document> &doc,
Q_UNUSED(el)
}
void DocumentContainer::on_anchor_click(const litehtml::tchar_t *url,
const litehtml::element::ptr &el)
void DocumentContainerPrivate::on_anchor_click(const litehtml::tchar_t *url,
const litehtml::element::ptr &el)
{
Q_UNUSED(el)
if (!m_blockLinks)
m_linkCallback(resolveUrl(QString::fromUtf8(url), m_baseUrl));
}
void DocumentContainer::set_cursor(const litehtml::tchar_t *cursor)
void DocumentContainerPrivate::set_cursor(const litehtml::tchar_t *cursor)
{
m_cursorCallback(toQCursor(QString::fromUtf8(cursor)));
}
void DocumentContainer::transform_text(litehtml::tstring &text, litehtml::text_transform tt)
void DocumentContainerPrivate::transform_text(litehtml::tstring &text, litehtml::text_transform tt)
{
// TODO
qDebug(log) << "transform_text";
@@ -855,9 +860,9 @@ void DocumentContainer::transform_text(litehtml::tstring &text, litehtml::text_t
Q_UNUSED(tt)
}
void DocumentContainer::import_css(litehtml::tstring &text,
const litehtml::tstring &url,
litehtml::tstring &baseurl)
void DocumentContainerPrivate::import_css(litehtml::tstring &text,
const litehtml::tstring &url,
litehtml::tstring &baseurl)
{
const QUrl actualUrl = resolveUrl(QString::fromStdString(url), QString::fromStdString(baseurl));
const QString urlString = actualUrl.toString(QUrl::None);
@@ -866,10 +871,10 @@ void DocumentContainer::import_css(litehtml::tstring &text,
text = QString::fromUtf8(m_dataCallback(actualUrl)).toStdString();
}
void DocumentContainer::set_clip(const litehtml::position &pos,
const litehtml::border_radiuses &bdr_radius,
bool valid_x,
bool valid_y)
void DocumentContainerPrivate::set_clip(const litehtml::position &pos,
const litehtml::border_radiuses &bdr_radius,
bool valid_x,
bool valid_y)
{
// TODO
qDebug(log) << "set_clip";
@@ -879,18 +884,18 @@ void DocumentContainer::set_clip(const litehtml::position &pos,
Q_UNUSED(valid_y)
}
void DocumentContainer::del_clip()
void DocumentContainerPrivate::del_clip()
{
// TODO
qDebug(log) << "del_clip";
}
void DocumentContainer::get_client_rect(litehtml::position &client) const
void DocumentContainerPrivate::get_client_rect(litehtml::position &client) const
{
client = {m_clientRect.x(), m_clientRect.y(), m_clientRect.width(), m_clientRect.height()};
}
std::shared_ptr<litehtml::element> DocumentContainer::create_element(
std::shared_ptr<litehtml::element> DocumentContainerPrivate::create_element(
const litehtml::tchar_t *tag_name,
const litehtml::string_map &attributes,
const std::shared_ptr<litehtml::document> &doc)
@@ -902,14 +907,14 @@ std::shared_ptr<litehtml::element> DocumentContainer::create_element(
return {};
}
void DocumentContainer::get_media_features(litehtml::media_features &media) const
void DocumentContainerPrivate::get_media_features(litehtml::media_features &media) const
{
media.type = litehtml::media_type_screen;
// TODO
qDebug(log) << "get_media_features";
}
void DocumentContainer::get_language(litehtml::tstring &language, litehtml::tstring &culture) const
void DocumentContainerPrivate::get_language(litehtml::tstring &language, litehtml::tstring &culture) const
{
// TODO
qDebug(log) << "get_language";
@@ -919,67 +924,91 @@ void DocumentContainer::get_language(litehtml::tstring &language, litehtml::tstr
void DocumentContainer::setPaintDevice(QPaintDevice *paintDevice)
{
m_paintDevice = paintDevice;
d->m_paintDevice = paintDevice;
}
void DocumentContainer::setScrollPosition(const QPoint &pos)
{
m_scrollPosition = pos;
d->m_scrollPosition = pos;
}
void DocumentContainer::setDocument(const QByteArray &data, litehtml::context *context)
void DocumentContainer::setDocument(const QByteArray &data, DocumentContainerContext *context)
{
m_pixmaps.clear();
m_selection = {};
m_document = litehtml::document::createFromUTF8(data.constData(), this, context);
buildIndex();
d->m_pixmaps.clear();
d->m_selection = {};
d->m_document = litehtml::document::createFromUTF8(data.constData(), d.get(), &context->d->context);
d->buildIndex();
}
litehtml::document::ptr DocumentContainer::document() const
bool DocumentContainer::hasDocument() const
{
return m_document;
return d->m_document.get();
}
void DocumentContainer::setBaseUrl(const QString &url)
{
d->set_base_url(url.toUtf8().constData());
}
void DocumentContainer::render(int width, int height)
{
m_clientRect = {0, 0, width, height};
if (!m_document)
d->m_clientRect = {0, 0, width, height};
if (!d->m_document)
return;
m_document->render(width);
m_selection.update();
d->m_document->render(width);
d->m_selection.update();
}
void DocumentContainer::draw(QPainter *painter, const QRect &clip)
{
drawSelection(painter, clip);
const QPoint pos = -m_scrollPosition;
d->drawSelection(painter, clip);
const QPoint pos = -d->m_scrollPosition;
const litehtml::position clipRect = {clip.x(), clip.y(), clip.width(), clip.height()};
document()->draw(reinterpret_cast<litehtml::uint_ptr>(painter), pos.x(), pos.y(), &clipRect);
d->m_document->draw(reinterpret_cast<litehtml::uint_ptr>(painter), pos.x(), pos.y(), &clipRect);
}
int DocumentContainer::documentWidth() const
{
return d->m_document->width();
}
int DocumentContainer::documentHeight() const
{
return d->m_document->height();
}
int DocumentContainer::anchorY(const QString &anchorName) const
{
litehtml::element::ptr element = d->m_document->root()->select_one(
QString("#%1").arg(anchorName).toStdString());
if (!element) {
element = d->m_document->root()->select_one(QString("[name=%1]").arg(anchorName).toStdString());
}
if (element)
return element->get_placement().y;
return -1;
}
QVector<QRect> DocumentContainer::mousePressEvent(const QPoint &documentPos,
const QPoint &viewportPos,
Qt::MouseButton button)
{
if (!m_document || button != Qt::LeftButton)
if (!d->m_document || button != Qt::LeftButton)
return {};
QVector<QRect> redrawRects;
// selection
if (m_selection.isValid())
redrawRects.append(m_selection.boundingRect());
m_selection = {};
m_selection.selectionStartDocumentPos = documentPos;
m_selection.startElem = deepest_child_at_point(m_document,
documentPos,
viewportPos,
m_selection.mode);
if (d->m_selection.isValid())
redrawRects.append(d->m_selection.boundingRect());
d->m_selection = {};
d->m_selection.selectionStartDocumentPos = documentPos;
d->m_selection.startElem = deepest_child_at_point(d->m_document,
documentPos,
viewportPos,
d->m_selection.mode);
// post to litehtml
litehtml::position::vector redrawBoxes;
if (m_document->on_lbutton_down(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y(),
redrawBoxes)) {
if (d->m_document->on_lbutton_down(
documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) {
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
}
@@ -989,34 +1018,30 @@ QVector<QRect> DocumentContainer::mousePressEvent(const QPoint &documentPos,
QVector<QRect> DocumentContainer::mouseMoveEvent(const QPoint &documentPos,
const QPoint &viewportPos)
{
if (!m_document)
if (!d->m_document)
return {};
QVector<QRect> redrawRects;
// selection
if (m_selection.isSelecting
|| (!m_selection.selectionStartDocumentPos.isNull()
&& (m_selection.selectionStartDocumentPos - documentPos).manhattanLength()
>= kDragDistance
&& m_selection.startElem.element)) {
const Selection::Element element = deepest_child_at_point(m_document,
if (d->m_selection.isSelecting
|| (!d->m_selection.selectionStartDocumentPos.isNull()
&& (d->m_selection.selectionStartDocumentPos - documentPos).manhattanLength() >= kDragDistance
&& d->m_selection.startElem.element)) {
const Selection::Element element = deepest_child_at_point(d->m_document,
documentPos,
viewportPos,
m_selection.mode);
d->m_selection.mode);
if (element.element) {
redrawRects.append(
m_selection.boundingRect() /*.adjusted(-1, -1, +1, +1)*/); // redraw old selection area
m_selection.endElem = element;
m_selection.update();
redrawRects.append(m_selection.boundingRect());
d->m_selection.boundingRect() /*.adjusted(-1, -1, +1, +1)*/); // redraw old selection area
d->m_selection.endElem = element;
d->m_selection.update();
redrawRects.append(d->m_selection.boundingRect());
}
m_selection.isSelecting = true;
d->m_selection.isSelecting = true;
}
litehtml::position::vector redrawBoxes;
if (m_document->on_mouse_over(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y(),
redrawBoxes)) {
if (d->m_document->on_mouse_over(
documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) {
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
}
@@ -1027,26 +1052,23 @@ QVector<QRect> DocumentContainer::mouseReleaseEvent(const QPoint &documentPos,
const QPoint &viewportPos,
Qt::MouseButton button)
{
if (!m_document || button != Qt::LeftButton)
if (!d->m_document || button != Qt::LeftButton)
return {};
QVector<QRect> redrawRects;
// selection
m_selection.isSelecting = false;
m_selection.selectionStartDocumentPos = {};
if (m_selection.isValid())
m_blockLinks = true;
d->m_selection.isSelecting = false;
d->m_selection.selectionStartDocumentPos = {};
if (d->m_selection.isValid())
d->m_blockLinks = true;
else
m_selection = {};
d->m_selection = {};
litehtml::position::vector redrawBoxes;
if (m_document->on_lbutton_up(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y(),
redrawBoxes)) {
if (d->m_document->on_lbutton_up(
documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) {
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
}
m_blockLinks = false;
d->m_blockLinks = false;
return redrawRects;
}
@@ -1054,36 +1076,36 @@ QVector<QRect> DocumentContainer::mouseDoubleClickEvent(const QPoint &documentPo
const QPoint &viewportPos,
Qt::MouseButton button)
{
if (!m_document || button != Qt::LeftButton)
if (!d->m_document || button != Qt::LeftButton)
return {};
QVector<QRect> redrawRects;
m_selection = {};
m_selection.mode = Selection::Mode::Word;
const Selection::Element element = deepest_child_at_point(m_document,
d->m_selection = {};
d->m_selection.mode = Selection::Mode::Word;
const Selection::Element element = deepest_child_at_point(d->m_document,
documentPos,
viewportPos,
m_selection.mode);
d->m_selection.mode);
if (element.element) {
m_selection.startElem = element;
m_selection.endElem = m_selection.startElem;
m_selection.isSelecting = true;
m_selection.update();
if (m_selection.isValid())
redrawRects.append(m_selection.boundingRect());
d->m_selection.startElem = element;
d->m_selection.endElem = d->m_selection.startElem;
d->m_selection.isSelecting = true;
d->m_selection.update();
if (d->m_selection.isValid())
redrawRects.append(d->m_selection.boundingRect());
} else {
if (m_selection.isValid())
redrawRects.append(m_selection.boundingRect());
m_selection = {};
if (d->m_selection.isValid())
redrawRects.append(d->m_selection.boundingRect());
d->m_selection = {};
}
return redrawRects;
}
QVector<QRect> DocumentContainer::leaveEvent()
{
if (!m_document)
if (!d->m_document)
return {};
litehtml::position::vector redrawBoxes;
if (m_document->on_mouse_leave(redrawBoxes)) {
if (d->m_document->on_mouse_leave(redrawBoxes)) {
QVector<QRect> redrawRects;
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
@@ -1094,26 +1116,24 @@ QVector<QRect> DocumentContainer::leaveEvent()
QUrl DocumentContainer::linkAt(const QPoint &documentPos, const QPoint &viewportPos)
{
if (!m_document)
if (!d->m_document)
return {};
const litehtml::element::ptr element = m_document->root()->get_element_by_point(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y());
const litehtml::element::ptr element = d->m_document->root()->get_element_by_point(
documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y());
const char *href = element->get_attr("href");
if (href)
return resolveUrl(QString::fromUtf8(href), m_baseUrl);
return d->resolveUrl(QString::fromUtf8(href), d->m_baseUrl);
return {};
}
QString DocumentContainer::caption() const
{
return m_caption;
return d->m_caption;
}
QString DocumentContainer::selectedText() const
{
return m_selection.text;
return d->m_selection.text;
}
void DocumentContainer::findText(const QString &text,
@@ -1130,16 +1150,16 @@ void DocumentContainer::findText(const QString &text,
oldSelection->clear();
if (newSelection)
newSelection->clear();
if (!m_document)
if (!d->m_document)
return;
const bool backward = flags & QTextDocument::FindBackward;
int startIndex = backward ? -1 : 0;
if (m_selection.startElem.element && m_selection.endElem.element) { // selection
if (d->m_selection.startElem.element && d->m_selection.endElem.element) { // selection
// poor-man's incremental search starts at beginning of selection,
// non-incremental at end (forward search) or beginning (backward search)
Selection::Element start;
Selection::Element end;
std::tie(start, end) = getStartAndEnd(m_selection.startElem, m_selection.endElem);
std::tie(start, end) = getStartAndEnd(d->m_selection.startElem, d->m_selection.endElem);
Selection::Element searchStart;
if (incremental || backward) {
if (start.index < 0) // fully selected
@@ -1152,8 +1172,8 @@ void DocumentContainer::findText(const QString &text,
else
searchStart = end;
}
const auto findInIndex = m_index.elementToIndex.find(searchStart.element);
if (findInIndex == std::end(m_index.elementToIndex)) {
const auto findInIndex = d->m_index.elementToIndex.find(searchStart.element);
if (findInIndex == std::end(d->m_index.elementToIndex)) {
qWarning() << "internal error: cannot find litehmtl element in index";
return;
}
@@ -1179,30 +1199,30 @@ void DocumentContainer::findText(const QString &text,
: QRegularExpression::CaseInsensitiveOption;
const QRegularExpression expression(term, patternOptions);
int foundIndex = backward ? m_index.text.lastIndexOf(expression, startIndex)
: m_index.text.indexOf(expression, startIndex);
int foundIndex = backward ? d->m_index.text.lastIndexOf(expression, startIndex)
: d->m_index.text.indexOf(expression, startIndex);
if (foundIndex < 0) { // wrap
foundIndex = backward ? m_index.text.lastIndexOf(expression)
: m_index.text.indexOf(expression);
foundIndex = backward ? d->m_index.text.lastIndexOf(expression)
: d->m_index.text.indexOf(expression);
if (wrapped && foundIndex >= 0)
*wrapped = true;
}
if (foundIndex >= 0) {
const Index::Entry startEntry = m_index.findElement(foundIndex);
const Index::Entry endEntry = m_index.findElement(foundIndex + text.size());
const Index::Entry startEntry = d->m_index.findElement(foundIndex);
const Index::Entry endEntry = d->m_index.findElement(foundIndex + text.size());
if (!startEntry.second || !endEntry.second) {
qWarning() << "internal error: search ended up with nullptr elements";
return;
}
if (oldSelection)
*oldSelection = m_selection.selection;
m_selection = {};
m_selection.startElem = fillXPos({startEntry.second, foundIndex - startEntry.first, -1});
m_selection.endElem = fillXPos(
*oldSelection = d->m_selection.selection;
d->m_selection = {};
d->m_selection.startElem = fillXPos({startEntry.second, foundIndex - startEntry.first, -1});
d->m_selection.endElem = fillXPos(
{endEntry.second, int(foundIndex + text.size() - endEntry.first), -1});
m_selection.update();
d->m_selection.update();
if (newSelection)
*newSelection = m_selection.selection;
*newSelection = d->m_selection.selection;
if (success)
*success = true;
return;
@@ -1212,36 +1232,68 @@ void DocumentContainer::findText(const QString &text,
void DocumentContainer::setDefaultFont(const QFont &font)
{
m_defaultFont = font;
m_defaultFontFamilyName = m_defaultFont.family().toUtf8();
d->m_defaultFont = font;
d->m_defaultFontFamilyName = d->m_defaultFont.family().toUtf8();
}
QFont DocumentContainer::defaultFont() const
{
return m_defaultFont;
return d->m_defaultFont;
}
void DocumentContainer::setDataCallback(const DocumentContainer::DataCallback &callback)
{
m_dataCallback = callback;
d->m_dataCallback = callback;
}
void DocumentContainer::setCursorCallback(const DocumentContainer::CursorCallback &callback)
{
m_cursorCallback = callback;
d->m_cursorCallback = callback;
}
void DocumentContainer::setLinkCallback(const DocumentContainer::LinkCallback &callback)
{
m_linkCallback = callback;
d->m_linkCallback = callback;
}
void DocumentContainer::setPaletteCallback(const DocumentContainer::PaletteCallback &callback)
{
m_paletteCallback = callback;
d->m_paletteCallback = callback;
}
QPixmap DocumentContainer::getPixmap(const QString &imageUrl, const QString &baseUrl)
static litehtml::element::ptr elementForY(int y, const litehtml::document::ptr &document)
{
if (!document)
return {};
const std::function<litehtml::element::ptr(int, litehtml::element::ptr)> recursion =
[&recursion](int y, const litehtml::element::ptr &element) {
litehtml::element::ptr result;
const int subY = y - element->get_position().y;
if (subY <= 0)
return element;
for (int i = 0; i < int(element->get_children_count()); ++i) {
const litehtml::element::ptr child = element->get_child(i);
result = recursion(subY, child);
if (result)
return result;
}
return result;
};
return recursion(y, document->root());
}
int DocumentContainer::withFixedElementPosition(int y, const std::function<void()> &action)
{
const litehtml::element::ptr element = elementForY(y, d->m_document);
action();
if (element)
return element->get_placement().y;
return -1;
}
QPixmap DocumentContainerPrivate::getPixmap(const QString &imageUrl, const QString &baseUrl)
{
const QString actualBaseurl = baseUrl.isEmpty() ? m_baseUrl : baseUrl;
const QUrl url = resolveUrl(imageUrl, baseUrl);
@@ -1252,25 +1304,25 @@ QPixmap DocumentContainer::getPixmap(const QString &imageUrl, const QString &bas
return m_pixmaps.value(url);
}
QString DocumentContainer::serifFont() const
QString DocumentContainerPrivate::serifFont() const
{
// TODO make configurable
return {"Times New Roman"};
}
QString DocumentContainer::sansSerifFont() const
QString DocumentContainerPrivate::sansSerifFont() const
{
// TODO make configurable
return {"Arial"};
}
QString DocumentContainer::monospaceFont() const
QString DocumentContainerPrivate::monospaceFont() const
{
// TODO make configurable
return {"Courier"};
}
QUrl DocumentContainer::resolveUrl(const QString &url, const QString &baseUrl) const
QUrl DocumentContainerPrivate::resolveUrl(const QString &url, const QString &baseUrl) const
{
const QUrl qurl(url);
if (qurl.isRelative() && !qurl.path(QUrl::FullyEncoded).isEmpty()) {
@@ -1294,3 +1346,14 @@ Index::Entry Index::findElement(int index) const
return {-1, {}};
return *(upper - 1);
}
DocumentContainerContext::DocumentContainerContext()
: d(new DocumentContainerContextPrivate)
{}
DocumentContainerContext::~DocumentContainerContext() = default;
void DocumentContainerContext::setMasterStyleSheet(const QString &css)
{
d->context.load_master_stylesheet(css.toUtf8().constData());
}

View File

@@ -25,125 +25,56 @@
#pragma once
#include <litehtml.h>
#include "qlitehtml_global.h"
#include <QFont>
#include <QHash>
#include <QByteArray>
#include <QPaintDevice>
#include <QPalette>
#include <QPixmap>
#include <QPainter>
#include <QPoint>
#include <QRect>
#include <QString>
#include <QTextDocument>
#include <QUrl>
#include <QVector>
#include <functional>
#include <unordered_map>
#include <memory>
class Selection
class DocumentContainerPrivate;
class DocumentContainerContextPrivate;
class QLITEHTML_EXPORT DocumentContainerContext
{
public:
struct Element
{
litehtml::element::ptr element;
int index = -1;
int x = -1;
};
DocumentContainerContext();
~DocumentContainerContext();
enum class Mode { Free, Word };
void setMasterStyleSheet(const QString &css);
bool isValid() const;
private:
std::unique_ptr<DocumentContainerContextPrivate> d;
void update();
QRect boundingRect() const;
Element startElem;
Element endElem;
QVector<QRect> selection;
QString text;
QPoint selectionStartDocumentPos;
Mode mode = Mode::Free;
bool isSelecting = false;
friend class DocumentContainer;
friend class DocumentContainerPrivate;
};
struct Index
{
QString text;
// only contains leaf elements
std::unordered_map<litehtml::element::ptr, int> elementToIndex;
using Entry = std::pair<int, litehtml::element::ptr>;
std::vector<Entry> indexToElement;
Entry findElement(int index) const;
};
class DocumentContainer : public litehtml::document_container
class QLITEHTML_EXPORT DocumentContainer
{
public:
DocumentContainer();
virtual ~DocumentContainer();
public: // document_container API
litehtml::uint_ptr create_font(const litehtml::tchar_t *faceName,
int size,
int weight,
litehtml::font_style italic,
unsigned int decoration,
litehtml::font_metrics *fm) override;
void delete_font(litehtml::uint_ptr hFont) override;
int text_width(const litehtml::tchar_t *text, litehtml::uint_ptr hFont) override;
void draw_text(litehtml::uint_ptr hdc,
const litehtml::tchar_t *text,
litehtml::uint_ptr hFont,
litehtml::web_color color,
const litehtml::position &pos) override;
int pt_to_px(int pt) override;
int get_default_font_size() const override;
const litehtml::tchar_t *get_default_font_name() const override;
void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) override;
void load_image(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
bool redraw_on_ready) override;
void get_image_size(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
litehtml::size &sz) override;
void draw_background(litehtml::uint_ptr hdc, const litehtml::background_paint &bg) override;
void draw_borders(litehtml::uint_ptr hdc,
const litehtml::borders &borders,
const litehtml::position &draw_pos,
bool root) override;
void set_caption(const litehtml::tchar_t *caption) override;
void set_base_url(const litehtml::tchar_t *base_url) override;
void link(const std::shared_ptr<litehtml::document> &doc,
const litehtml::element::ptr &el) override;
void on_anchor_click(const litehtml::tchar_t *url, const litehtml::element::ptr &el) override;
void set_cursor(const litehtml::tchar_t *cursor) override;
void transform_text(litehtml::tstring &text, litehtml::text_transform tt) override;
void import_css(litehtml::tstring &text,
const litehtml::tstring &url,
litehtml::tstring &baseurl) override;
void set_clip(const litehtml::position &pos,
const litehtml::border_radiuses &bdr_radius,
bool valid_x,
bool valid_y) override;
void del_clip() override;
void get_client_rect(litehtml::position &client) const override;
std::shared_ptr<litehtml::element> create_element(
const litehtml::tchar_t *tag_name,
const litehtml::string_map &attributes,
const std::shared_ptr<litehtml::document> &doc) override;
void get_media_features(litehtml::media_features &media) const override;
void get_language(litehtml::tstring &language, litehtml::tstring &culture) const override;
public: // outside API
void setPaintDevice(QPaintDevice *paintDevice);
void setDocument(const QByteArray &data, litehtml::context *context);
litehtml::document::ptr document() const;
void setDocument(const QByteArray &data, DocumentContainerContext *context);
bool hasDocument() const;
void setBaseUrl(const QString &url);
void setScrollPosition(const QPoint &pos);
void render(int width, int height);
void draw(QPainter *painter, const QRect &clip);
int documentWidth() const;
int documentHeight() const;
int anchorY(const QString &anchorName) const;
// these return areas to redraw in document space
QVector<QRect> mousePressEvent(const QPoint &documentPos,
@@ -186,29 +117,8 @@ public: // outside API
using PaletteCallback = std::function<QPalette()>;
void setPaletteCallback(const PaletteCallback &callback);
private:
QPixmap getPixmap(const QString &imageUrl, const QString &baseUrl);
QString serifFont() const;
QString sansSerifFont() const;
QString monospaceFont() const;
QUrl resolveUrl(const QString &url, const QString &baseUrl) const;
void drawSelection(QPainter *painter, const QRect &clip) const;
void buildIndex();
int withFixedElementPosition(int y, const std::function<void()> &action);
QPaintDevice *m_paintDevice = nullptr;
litehtml::document::ptr m_document;
Index m_index;
QString m_baseUrl;
QRect m_clientRect;
QPoint m_scrollPosition;
QString m_caption;
QFont m_defaultFont = QFont(sansSerifFont(), 16);
QByteArray m_defaultFontFamilyName = m_defaultFont.family().toUtf8();
QHash<QUrl, QPixmap> m_pixmaps;
Selection m_selection;
DataCallback m_dataCallback;
CursorCallback m_cursorCallback;
LinkCallback m_linkCallback;
PaletteCallback m_paletteCallback;
bool m_blockLinks = false;
private:
std::unique_ptr<DocumentContainerPrivate> d;
};

View File

@@ -0,0 +1,163 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of QLiteHtml.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "container_qpainter.h"
#include <litehtml.h>
#include <QPaintDevice>
#include <QPixmap>
#include <QPoint>
#include <QRect>
#include <QString>
#include <QVector>
#include <unordered_map>
class Selection
{
public:
struct Element
{
litehtml::element::ptr element;
int index = -1;
int x = -1;
};
enum class Mode { Free, Word };
bool isValid() const;
void update();
QRect boundingRect() const;
Element startElem;
Element endElem;
QVector<QRect> selection;
QString text;
QPoint selectionStartDocumentPos;
Mode mode = Mode::Free;
bool isSelecting = false;
};
struct Index
{
QString text;
// only contains leaf elements
std::unordered_map<litehtml::element::ptr, int> elementToIndex;
using Entry = std::pair<int, litehtml::element::ptr>;
std::vector<Entry> indexToElement;
Entry findElement(int index) const;
};
class DocumentContainerPrivate : public litehtml::document_container
{
public: // document_container API
litehtml::uint_ptr create_font(const litehtml::tchar_t *faceName,
int size,
int weight,
litehtml::font_style italic,
unsigned int decoration,
litehtml::font_metrics *fm) override;
void delete_font(litehtml::uint_ptr hFont) override;
int text_width(const litehtml::tchar_t *text, litehtml::uint_ptr hFont) override;
void draw_text(litehtml::uint_ptr hdc,
const litehtml::tchar_t *text,
litehtml::uint_ptr hFont,
litehtml::web_color color,
const litehtml::position &pos) override;
int pt_to_px(int pt) override;
int get_default_font_size() const override;
const litehtml::tchar_t *get_default_font_name() const override;
void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) override;
void load_image(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
bool redraw_on_ready) override;
void get_image_size(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
litehtml::size &sz) override;
void draw_background(litehtml::uint_ptr hdc, const litehtml::background_paint &bg) override;
void draw_borders(litehtml::uint_ptr hdc,
const litehtml::borders &borders,
const litehtml::position &draw_pos,
bool root) override;
void set_caption(const litehtml::tchar_t *caption) override;
void set_base_url(const litehtml::tchar_t *base_url) override;
void link(const std::shared_ptr<litehtml::document> &doc,
const litehtml::element::ptr &el) override;
void on_anchor_click(const litehtml::tchar_t *url, const litehtml::element::ptr &el) override;
void set_cursor(const litehtml::tchar_t *cursor) override;
void transform_text(litehtml::tstring &text, litehtml::text_transform tt) override;
void import_css(litehtml::tstring &text,
const litehtml::tstring &url,
litehtml::tstring &baseurl) override;
void set_clip(const litehtml::position &pos,
const litehtml::border_radiuses &bdr_radius,
bool valid_x,
bool valid_y) override;
void del_clip() override;
void get_client_rect(litehtml::position &client) const override;
std::shared_ptr<litehtml::element> create_element(const litehtml::tchar_t *tag_name,
const litehtml::string_map &attributes,
const std::shared_ptr<litehtml::document> &doc) override;
void get_media_features(litehtml::media_features &media) const override;
void get_language(litehtml::tstring &language, litehtml::tstring &culture) const override;
QPixmap getPixmap(const QString &imageUrl, const QString &baseUrl);
QString serifFont() const;
QString sansSerifFont() const;
QString monospaceFont() const;
QUrl resolveUrl(const QString &url, const QString &baseUrl) const;
void drawSelection(QPainter *painter, const QRect &clip) const;
void buildIndex();
QPaintDevice *m_paintDevice = nullptr;
litehtml::document::ptr m_document;
Index m_index;
QString m_baseUrl;
QRect m_clientRect;
QPoint m_scrollPosition;
QString m_caption;
QFont m_defaultFont = QFont(sansSerifFont(), 16);
QByteArray m_defaultFontFamilyName = m_defaultFont.family().toUtf8();
QHash<QUrl, QPixmap> m_pixmaps;
Selection m_selection;
DocumentContainer::DataCallback m_dataCallback;
DocumentContainer::CursorCallback m_cursorCallback;
DocumentContainer::LinkCallback m_linkCallback;
DocumentContainer::PaletteCallback m_paletteCallback;
bool m_blockLinks = false;
};
class DocumentContainerContextPrivate
{
public:
litehtml::context context;
};

View File

@@ -147,6 +147,7 @@ exists($$PWD/litehtml/CMakeLists.txt) {
HEADERS += \
$$PWD/container_qpainter.h \
$$PWD/container_qpainter_p.h \
$$PWD/qlitehtmlwidget.h
SOURCES += \

View File

@@ -41,6 +41,7 @@ Product {
files: [
"container_qpainter.cpp",
"container_qpainter.h",
"container_qpainter_p.h",
"qlitehtmlwidget.cpp",
"qlitehtmlwidget.h",
]

View File

@@ -34,8 +34,6 @@
#include <QStyle>
#include <QTimer>
#include <litehtml.h>
const int kScrollBarStep = 40;
// TODO copied from litehtml/include/master.css
@@ -370,7 +368,7 @@ display: block;
class QLiteHtmlWidgetPrivate
{
public:
litehtml::context context;
DocumentContainerContext context;
QUrl url;
DocumentContainer documentContainer;
qreal zoomFactor = 1;
@@ -397,7 +395,7 @@ QLiteHtmlWidget::QLiteHtmlWidget(QWidget *parent)
});
// TODO adapt mastercss to palette (default text & background color)
d->context.load_master_stylesheet(mastercss);
d->context.setMasterStyleSheet(mastercss);
}
QLiteHtmlWidget::~QLiteHtmlWidget()
@@ -413,7 +411,7 @@ void QLiteHtmlWidget::setUrl(const QUrl &url)
const QString urlString = urlWithoutAnchor.toString(QUrl::None);
const int lastSlash = urlString.lastIndexOf('/');
const QString baseUrl = lastSlash >= 0 ? urlString.left(lastSlash) : urlString;
d->documentContainer.set_base_url(baseUrl.toUtf8().constData());
d->documentContainer.setBaseUrl(baseUrl);
}
QUrl QLiteHtmlWidget::url() const
@@ -489,23 +487,16 @@ QFont QLiteHtmlWidget::defaultFont() const
void QLiteHtmlWidget::scrollToAnchor(const QString &name)
{
if (!d->documentContainer.document())
if (!d->documentContainer.hasDocument())
return;
horizontalScrollBar()->setValue(0);
if (name.isEmpty()) {
verticalScrollBar()->setValue(0);
return;
}
litehtml::element::ptr element = d->documentContainer.document()->root()->select_one(
QString("#%1").arg(name).toStdString());
if (!element) {
element = d->documentContainer.document()->root()->select_one(
QString("[name=%1]").arg(name).toStdString());
}
if (element) {
const int y = element->get_placement().y;
const int y = d->documentContainer.anchorY(name);
if (y >= 0)
verticalScrollBar()->setValue(std::min(y, verticalScrollBar()->maximum()));
}
}
void QLiteHtmlWidget::setResourceHandler(const QLiteHtmlWidget::ResourceHandler &handler)
@@ -520,7 +511,7 @@ QString QLiteHtmlWidget::selectedText() const
void QLiteHtmlWidget::paintEvent(QPaintEvent *event)
{
if (!d->documentContainer.document())
if (!d->documentContainer.hasDocument())
return;
d->documentContainer.setScrollPosition(scrollPosition());
QPainter p(viewport());
@@ -530,29 +521,6 @@ void QLiteHtmlWidget::paintEvent(QPaintEvent *event)
d->documentContainer.draw(&p, toVirtual(event->rect()));
}
static litehtml::element::ptr elementForY(int y, const litehtml::document::ptr &document)
{
if (!document)
return {};
const std::function<litehtml::element::ptr(int, litehtml::element::ptr)> recursion =
[&recursion](int y, const litehtml::element::ptr &element) {
litehtml::element::ptr result;
const int subY = y - element->get_position().y;
if (subY <= 0)
return element;
for (int i = 0; i < int(element->get_children_count()); ++i) {
const litehtml::element::ptr child = element->get_child(i);
result = recursion(subY, child);
if (result)
return result;
}
return result;
};
return recursion(y, document->root());
}
void QLiteHtmlWidget::resizeEvent(QResizeEvent *event)
{
withFixedTextPosition([this, event] {
@@ -620,17 +588,14 @@ void QLiteHtmlWidget::withFixedTextPosition(const std::function<void()> &action)
QPoint viewportPos;
QPoint pos;
htmlPos({}, &viewportPos, &pos); // top-left
const litehtml::element::ptr element = elementForY(pos.y(), d->documentContainer.document());
action();
if (element) {
verticalScrollBar()->setValue(
std::min(element->get_placement().y, verticalScrollBar()->maximum()));
}
const int y = d->documentContainer.withFixedElementPosition(pos.y(), action);
if (y >= 0)
verticalScrollBar()->setValue(std::min(y, verticalScrollBar()->maximum()));
}
void QLiteHtmlWidget::render()
{
if (!d->documentContainer.document())
if (!d->documentContainer.hasDocument())
return;
const int fullWidth = width() / d->zoomFactor;
const QSize vViewportSize = toVirtual(viewport()->size());
@@ -639,12 +604,10 @@ void QLiteHtmlWidget::render()
d->documentContainer.render(w, vViewportSize.height());
// scroll bars reflect virtual/scaled size of html document
horizontalScrollBar()->setPageStep(vViewportSize.width());
horizontalScrollBar()->setRange(0, std::max(0, d->documentContainer.document()->width() - w));
horizontalScrollBar()->setRange(0, std::max(0, d->documentContainer.documentWidth() - w));
verticalScrollBar()->setPageStep(vViewportSize.height());
verticalScrollBar()->setRange(0,
std::max(0,
d->documentContainer.document()->height()
- vViewportSize.height()));
verticalScrollBar()
->setRange(0, std::max(0, d->documentContainer.documentHeight() - vViewportSize.height()));
viewport()->update();
}