Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
container_qpainter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
6
7#if QT_CONFIG(clipboard)
8#include <QClipboard>
9#endif
10#include <QCursor>
11#include <QDebug>
12#include <QDir>
13#include <QFont>
14#include <QFontDatabase>
15#include <QFontMetrics>
16#include <QGuiApplication>
17#include <QLoggingCategory>
18#include <QPainter>
19#include <QPalette>
20#include <QRegularExpression>
21#include <QScreen>
22#include <QTextLayout>
23#include <QUrl>
24
25#include <algorithm>
26
27const int kDragDistance = 5;
28
29using Font = QFont;
30using Context = QPainter;
31
32#ifdef Q_STATIC_LOGGING_CATEGORY
33Q_STATIC_LOGGING_CATEGORY(log, "qlitehtml", QtCriticalMsg)
34#else
35static Q_LOGGING_CATEGORY(log, "qlitehtml", QtCriticalMsg)
36#endif
37
38static QFont toQFont(litehtml::uint_ptr hFont)
39{
40 return *reinterpret_cast<Font *>(hFont);
41}
42
43static QPainter *toQPainter(litehtml::uint_ptr hdc)
44{
45 return reinterpret_cast<Context *>(hdc);
46}
47
48static QRect toQRect(litehtml::position position)
49{
50 return {position.x, position.y, position.width, position.height};
51}
52
53static bool isVisible(const litehtml::element::ptr &element)
54{
55 // TODO render_item::is_visible() would also take m_skip into account, so this might be wrong
56 return element->css().get_display() != litehtml::display_none
57 && element->css().get_visibility() == litehtml::visibility_visible;
58}
59
60static litehtml::elements_list path(const litehtml::element::ptr &element)
61{
62 litehtml::elements_list result;
63 litehtml::element::ptr current = element;
64 while (current) {
65 result.push_back(current);
66 current = current->parent();
67 }
68 std::reverse(std::begin(result), std::end(result));
69 return result;
70}
71
72// <parent, first_different_child_a, first_different_child_b>
74getCommonParent(const litehtml::elements_list &a, const litehtml::elements_list &b)
75{
76 litehtml::element::ptr parent;
77 auto ait = a.cbegin();
78 auto bit = b.cbegin();
79 while (ait != a.cend() && bit != b.cend()) {
80 if (*ait != *bit)
81 return {parent, *ait, *bit};
82 parent = *ait;
83 ++ait;
84 ++bit;
85 }
86 return {parent,
87 (ait != a.cend() ? *ait : litehtml::element::ptr()),
88 (bit != b.cend() ? *bit : litehtml::element::ptr())};
89}
90
92 const Selection::Element &b)
93{
94 if (a.element == b.element) {
95 if (a.index <= b.index)
96 return {a, b};
97 return {b, a};
98 }
99 const litehtml::elements_list aPath = path(a.element);
100 const litehtml::elements_list bPath = path(b.element);
101 litehtml::element::ptr commonParent;
102 litehtml::element::ptr aBranch;
103 litehtml::element::ptr bBranch;
104 std::tie(commonParent, aBranch, bBranch) = getCommonParent(aPath, bPath);
105 if (!commonParent) {
106 qWarning() << "internal error: litehtml elements do not have common parent";
107 return {a, b};
108 }
109 if (commonParent == a.element)
110 return {a, a}; // 'a' already contains 'b'
111 if (commonParent == b.element)
112 return {b, b};
113 // find out if a or b is first in the child sub-trees of commonParent
114 for (const litehtml::element::ptr &child : commonParent->children()) {
115 if (child == aBranch)
116 return {a, b};
117 if (child == bBranch)
118 return {b, a};
119 }
120 qWarning() << "internal error: failed to find out order of litehtml elements";
121 return {a, b};
122}
123
124// 1) stops right away if element == stop, otherwise stops whenever stop element is encountered
125// 2) moves down the first children from element until there is none anymore
126static litehtml::element::ptr firstLeaf(const litehtml::element::ptr &element,
127 const litehtml::element::ptr &stop)
128{
129 if (element == stop)
130 return element;
131 litehtml::element::ptr current = element;
132 while (current != stop && !current->children().empty())
133 current = current->children().front();
134 return current;
135}
136
137// 1) stops right away if element == stop, otherwise stops whenever stop element is encountered
138// 2) starts at next sibling (up the hierarchy chain) if possible, otherwise root
139// 3) returns first leaf of the element found in 2
140static litehtml::element::ptr nextLeaf(const litehtml::element::ptr &element,
141 const litehtml::element::ptr &stop)
142{
143 if (element == stop)
144 return element;
145 litehtml::element::ptr current = element;
146 if (!current->is_root()) {
147 // find next sibling
148 const litehtml::element::ptr parent = current->parent();
149 const litehtml::elements_list &children = parent->children();
150 auto childIt = std::find_if(children.cbegin(),
151 children.cend(),
152 [&current](const litehtml::element::ptr &e) {
153 return e == current;
154 });
155 if (childIt == children.cend()) {
156 qWarning() << "internal error: filed to find litehtml child element in parent";
157 return stop;
158 }
159 ++childIt;
160 if (childIt == children.cend()) // no sibling, move up
161 return nextLeaf(parent, stop);
162 current = *childIt;
163 }
164 return firstLeaf(current, stop);
165}
166
167static Selection::Element selectionDetails(const litehtml::element::ptr &element,
168 const QString &text,
169 const QPoint &pos)
170{
171 // shortcut, which _might_ not really be correct
172 if (!element->children().empty())
173 return {element, -1, -1}; // everything selected
174 const QFont &font = toQFont(element->css().get_font());
175 const QFontMetrics fm(font);
176 int previous = 0;
177 for (int i = 0; i < text.size(); ++i) {
178 const int width = fm.size(0, text.left(i + 1)).width();
179 if ((width + previous) / 2 >= pos.x())
180 return {element, i, previous};
181 previous = width;
182 }
183 return {element, int(text.size()), previous};
184}
185
186// Returns whether the intended child was found and stop.
187// Does a depth-first iteration over elements that "pos" is inside, and executes
188// \a action with them. If \a action returns \c true, the iteration is stopped.
189static bool deepest_child_at_point(const litehtml::element::ptr &element,
190 const QPoint &pos,
191 const QPoint &viewportPos,
192 const std::function<bool(const litehtml::element::ptr &)> &action,
193 int level = 0)
194{
195 // TODO are there elements for which we should take viewportPos into account instead?
196 // E.g. fixed position elements?
197 if (!element)
198 return false /*continue iterating*/;
199 const QRect placement = toQRect(element->get_placement());
200 // Do not continue down elements that do not cover the position. Exceptions:
201 // - elements with 0 size (includes anchors and other weird elements)
202 // - html and body, which for some reason have size == viewport size
203 if (!placement.size().isEmpty() && element->tag() != litehtml::_html_
204 && element->tag() != litehtml::_body_ && !placement.contains(pos)) {
205 return false /*continue iterating*/;
206 }
207 // qDebug() << qPrintable(QString(level * 2, ' ')) << element->dump_get_name() << placement << pos;
208
209 const litehtml::elements_list &children = element->children();
210 for (auto it = children.cbegin(); it != children.cend(); ++it) {
211 if (deepest_child_at_point(*it, pos, viewportPos, action, level + 1))
212 return true;
213 }
214 if (placement.contains(pos))
215 return action(element);
216 return false /*continue iterating*/;
217}
218
219static Selection::Element selection_element_at_point(const litehtml::element::ptr &element,
220 const QPoint &pos,
221 const QPoint &viewportPos,
222 Selection::Mode mode)
223{
224 Selection::Element result;
225 deepest_child_at_point(element,
226 pos,
227 viewportPos,
228 [mode, &result, &pos](const litehtml::element::ptr &element) {
229 const QRect placement = toQRect(element->get_placement());
230 std::string text;
231 element->get_text(text);
232 if (!text.empty()) {
233 result = mode == Selection::Mode::Free
234 ? selectionDetails(element,
235 QString::fromStdString(text),
236 pos - placement.topLeft())
237 : Selection::Element({element, -1, -1});
238 return true;
239 }
240 return false; /*continue*/
241 });
242 return result;
243}
244
245// CSS: 400 == normal, 700 == bold.
246// Qt5: 50 == normal, 75 == bold
247// Qt6: == CSS
248static QFont::Weight cssWeightToQtWeight(int cssWeight)
249{
250#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
251 return QFont::Weight(cssWeight);
252#else
253 if (cssWeight <= 400)
254 return QFont::Weight(cssWeight * 50 / 400);
255 if (cssWeight >= 700)
256 return QFont::Weight(75 + (cssWeight - 700) * 25 / 300);
257 return QFont::Weight(50 + (cssWeight - 400) * 25 / 300);
258#endif
259}
260
261static QFont::Style toQFontStyle(litehtml::font_style style)
262{
263 switch (style) {
264 case litehtml::font_style_normal:
265 return QFont::StyleNormal;
266 case litehtml::font_style_italic:
267 return QFont::StyleItalic;
268 }
269 // should not happen
270 qWarning(log) << "Unknown litehtml font style:" << style;
271 return QFont::StyleNormal;
272}
273
274static QColor toQColor(const litehtml::web_color &color)
275{
276 return {color.red, color.green, color.blue, color.alpha};
277}
278
279static Qt::PenStyle borderPenStyle(litehtml::border_style style)
280{
281 switch (style) {
282 case litehtml::border_style_dotted:
283 return Qt::DotLine;
284 case litehtml::border_style_dashed:
285 return Qt::DashLine;
286 case litehtml::border_style_solid:
287 return Qt::SolidLine;
288 default:
289 qWarning(log) << "Unsupported border style:" << style;
290 }
291 return Qt::SolidLine;
292}
293
294static QPen borderPen(const litehtml::border &border)
295{
296 return {toQColor(border.color), qreal(border.width), borderPenStyle(border.style)};
297}
298
299static QCursor toQCursor(const QString &c)
300{
301 if (c == "alias")
302 return {Qt::PointingHandCursor}; // ???
303 if (c == "all-scroll")
304 return {Qt::SizeAllCursor};
305 if (c == "auto")
306 return {Qt::ArrowCursor}; // ???
307 if (c == "cell")
308 return {Qt::UpArrowCursor};
309 if (c == "context-menu")
310 return {Qt::ArrowCursor}; // ???
311 if (c == "col-resize")
312 return {Qt::SplitHCursor};
313 if (c == "copy")
314 return {Qt::DragCopyCursor};
315 if (c == "crosshair")
316 return {Qt::CrossCursor};
317 if (c == "default")
318 return {Qt::ArrowCursor};
319 if (c == "e-resize")
320 return {Qt::SizeHorCursor}; // ???
321 if (c == "ew-resize")
322 return {Qt::SizeHorCursor};
323 if (c == "grab")
324 return {Qt::OpenHandCursor};
325 if (c == "grabbing")
326 return {Qt::ClosedHandCursor};
327 if (c == "help")
328 return {Qt::WhatsThisCursor};
329 if (c == "move")
330 return {Qt::SizeAllCursor};
331 if (c == "n-resize")
332 return {Qt::SizeVerCursor}; // ???
333 if (c == "ne-resize")
334 return {Qt::SizeBDiagCursor}; // ???
335 if (c == "nesw-resize")
336 return {Qt::SizeBDiagCursor};
337 if (c == "ns-resize")
338 return {Qt::SizeVerCursor};
339 if (c == "nw-resize")
340 return {Qt::SizeFDiagCursor}; // ???
341 if (c == "nwse-resize")
342 return {Qt::SizeFDiagCursor};
343 if (c == "no-drop")
344 return {Qt::ForbiddenCursor};
345 if (c == "none")
346 return {Qt::BlankCursor};
347 if (c == "not-allowed")
348 return {Qt::ForbiddenCursor};
349 if (c == "pointer")
350 return {Qt::PointingHandCursor};
351 if (c == "progress")
352 return {Qt::BusyCursor};
353 if (c == "row-resize")
354 return {Qt::SplitVCursor};
355 if (c == "s-resize")
356 return {Qt::SizeVerCursor}; // ???
357 if (c == "se-resize")
358 return {Qt::SizeFDiagCursor}; // ???
359 if (c == "sw-resize")
360 return {Qt::SizeBDiagCursor}; // ???
361 if (c == "text")
362 return {Qt::IBeamCursor};
363 if (c == "url")
364 return {Qt::ArrowCursor}; // ???
365 if (c == "w-resize")
366 return {Qt::SizeHorCursor}; // ???
367 if (c == "wait")
368 return {Qt::BusyCursor};
369 if (c == "zoom-in")
370 return {Qt::ArrowCursor}; // ???
371 qWarning(log) << QString("unknown cursor property \"%1\"").arg(c).toUtf8().constData();
372 return {Qt::ArrowCursor};
373}
374
375bool Selection::isValid() const
376{
377 return !selection.isEmpty();
378}
379
381{
382 const auto addElement = [this](const Selection::Element &element,
383 const Selection::Element &end = {}) {
384 std::string elemText;
385 element.element->get_text(elemText);
386 const QString textStr = QString::fromStdString(elemText);
387 if (!textStr.isEmpty()) {
388 QRect rect = toQRect(element.element->get_placement()).adjusted(-1, -1, 1, 1);
389 if (element.index < 0) { // fully selected
390 text += textStr;
391 } else if (end.element) { // select from element "to end"
392 if (element.element == end.element) {
393 // end.index is guaranteed to be >= element.index by caller, same for x
394 text += textStr.mid(element.index, end.index - element.index);
395 const int left = rect.left();
396 rect.setLeft(left + element.x);
397 rect.setRight(left + end.x);
398 } else {
399 text += textStr.mid(element.index);
400 rect.setLeft(rect.left() + element.x);
401 }
402 } else { // select from start of element
403 text += textStr.left(element.index);
404 rect.setRight(rect.left() + element.x);
405 }
406 selection.append(rect);
407 }
408 };
409
410 if (startElem.element && endElem.element) {
411 // Edge cases:
412 // start and end elements could be reversed or children of each other
413 Selection::Element start;
414 Selection::Element end;
415 std::tie(start, end) = getStartAndEnd(startElem, endElem);
416
417 selection.clear();
418 text.clear();
419
420 // Treats start element as a leaf even if it isn't, because it already contains all its
421 // children
422 addElement(start, end);
423 if (start.element != end.element) {
424 litehtml::element::ptr current = start.element;
425 do {
426 current = nextLeaf(current, end.element);
427 if (current == end.element)
428 addElement(end);
429 else
430 addElement({current, -1, -1});
431 } while (current != end.element);
432 }
433 } else {
434 selection = {};
435 text.clear();
436 }
437#if QT_CONFIG(clipboard)
438 QClipboard *cb = QGuiApplication::clipboard();
439 if (cb->supportsSelection())
440 cb->setText(text, QClipboard::Selection);
441#endif
442}
443
445{
446 QRect rect;
447 for (const QRect &r : selection)
448 rect = rect.united(r);
449 return rect;
450}
451
452DocumentContainer::DocumentContainer()
453 : d(new DocumentContainerPrivate)
454{}
455
456DocumentContainer::~DocumentContainer() = default;
457
459 int size,
460 int weight,
461 litehtml::font_style italic,
462 unsigned int decoration,
463 litehtml::font_metrics *fm)
464{
465 const QStringList splitNames = QString::fromUtf8(faceName).split(',', Qt::SkipEmptyParts);
466 QStringList familyNames;
467 std::transform(splitNames.cbegin(),
468 splitNames.cend(),
469 std::back_inserter(familyNames),
470 [this](const QString &s) {
471 // clean whitespace and quotes
472 QString name = s.trimmed();
473 if (name.startsWith('\"'))
474 name = name.mid(1);
475 if (name.endsWith('\"'))
476 name.chop(1);
477 const QString lowerName = name.toLower();
478 if (lowerName == "serif")
479 return serifFont();
480 if (lowerName == "sans-serif")
481 return sansSerifFont();
482 if (lowerName == "monospace")
483 return monospaceFont();
484 return name;
485 });
486 auto font = new QFont();
487 font->setFamilies(familyNames);
488 font->setPixelSize(size);
489 font->setWeight(cssWeightToQtWeight(weight));
490 font->setStyle(toQFontStyle(italic));
491 font->setStyleStrategy(m_antialias ? QFont::PreferAntialias : QFont::NoAntialias);
492 if (decoration == litehtml::font_decoration_underline)
493 font->setUnderline(true);
494 if (decoration == litehtml::font_decoration_overline)
495 font->setOverline(true);
496 if (decoration == litehtml::font_decoration_linethrough)
497 font->setStrikeOut(true);
498 if (fm) {
499 const QFontMetrics metrics(*font);
500 fm->height = metrics.height();
501 fm->ascent = metrics.ascent();
502 fm->descent = metrics.descent();
503 fm->x_height = metrics.xHeight();
504 fm->draw_spaces = true;
505 }
506 return reinterpret_cast<litehtml::uint_ptr>(font);
507}
508
509void DocumentContainerPrivate::delete_font(litehtml::uint_ptr hFont)
510{
511 auto font = reinterpret_cast<Font *>(hFont);
512 delete font;
513}
514
515int DocumentContainerPrivate::text_width(const char *text, litehtml::uint_ptr hFont)
516{
517 const QFontMetrics fm(toQFont(hFont));
518 return fm.horizontalAdvance(QString::fromUtf8(text));
519}
520
521void DocumentContainerPrivate::draw_text(litehtml::uint_ptr hdc,
522 const char *text,
523 litehtml::uint_ptr hFont,
524 litehtml::web_color color,
525 const litehtml::position &pos)
526{
527 auto painter = toQPainter(hdc);
528 painter->setFont(toQFont(hFont));
529 painter->setPen(toQColor(color));
530 painter->drawText(toQRect(pos), 0, QString::fromUtf8(text));
531}
532
534{
535 // magic factor of 11/12 to account for differences to webengine/webkit
536 return m_paintDevice->physicalDpiY() * pt * 11 / m_paintDevice->logicalDpiY() / 12;
537}
538
540{
541 return m_defaultFont.pointSize();
542}
543
545{
546 return m_defaultFontFamilyName.constData();
547}
548
549void DocumentContainerPrivate::draw_list_marker(litehtml::uint_ptr hdc,
550 const litehtml::list_marker &marker)
551{
552 auto painter = toQPainter(hdc);
553 if (marker.image.empty()) {
554 if (marker.marker_type == litehtml::list_style_type_square) {
555 painter->setPen(Qt::NoPen);
556 painter->setBrush(toQColor(marker.color));
557 painter->drawRect(toQRect(marker.pos));
558 } else if (marker.marker_type == litehtml::list_style_type_disc) {
559 painter->setPen(Qt::NoPen);
560 painter->setBrush(toQColor(marker.color));
561 painter->drawEllipse(toQRect(marker.pos));
562 } else if (marker.marker_type == litehtml::list_style_type_circle) {
563 painter->setPen(toQColor(marker.color));
564 painter->setBrush(Qt::NoBrush);
565 painter->drawEllipse(toQRect(marker.pos));
566 } else {
567 // TODO we do not get information about index and font for e.g. decimal / roman
568 // at least draw a bullet
569 painter->setPen(Qt::NoPen);
570 painter->setBrush(toQColor(marker.color));
571 painter->drawEllipse(toQRect(marker.pos));
572 qWarning(log) << "list marker of type" << marker.marker_type << "not supported";
573 }
574 } else {
575 const QPixmap pixmap = getPixmap(QString::fromStdString(marker.image),
576 QString::fromStdString(marker.baseurl));
577 painter->drawPixmap(toQRect(marker.pos), pixmap);
578 }
579}
580
581void DocumentContainerPrivate::load_image(const char *src, const char *baseurl, bool redraw_on_ready)
582{
583 const auto qtSrc = QString::fromUtf8(src);
584 const auto qtBaseUrl = QString::fromUtf8(baseurl);
585 Q_UNUSED(redraw_on_ready)
586 qDebug(log) << "load_image:" << QString("src = \"%1\";").arg(qtSrc).toUtf8().constData()
587 << QString("base = \"%1\"").arg(qtBaseUrl).toUtf8().constData();
588 const QUrl url = resolveUrl(qtSrc, qtBaseUrl);
589 if (m_pixmaps.contains(url))
590 return;
591
592 QPixmap pixmap;
593 pixmap.loadFromData(m_dataCallback(url));
594 m_pixmaps.insert(url, pixmap);
595}
596
598 const char *baseurl,
599 litehtml::size &sz)
600{
601 const auto qtSrc = QString::fromUtf8(src);
602 const auto qtBaseUrl = QString::fromUtf8(baseurl);
603 if (qtSrc.isEmpty()) // for some reason that happens
604 return;
605 qDebug(log) << "get_image_size:" << QString("src = \"%1\";").arg(qtSrc).toUtf8().constData()
606 << QString("base = \"%1\"").arg(qtBaseUrl).toUtf8().constData();
607 const QPixmap pm = getPixmap(qtSrc, qtBaseUrl);
608 sz.width = pm.width();
609 sz.height = pm.height();
610}
611
612void DocumentContainerPrivate::drawSelection(QPainter *painter, const QRect &clip) const
613{
614 painter->save();
615 painter->setClipRect(clip, Qt::IntersectClip);
616 for (const QRect &r : m_selection.selection) {
617 const QRect clientRect = r.translated(-m_scrollPosition);
618 const QPalette palette = m_paletteCallback();
619 painter->fillRect(clientRect, palette.brush(QPalette::Highlight));
620 }
621 painter->restore();
622}
623
624static QString tagName(const litehtml::element::ptr &e)
625{
626 litehtml::element::ptr current = e;
627 while (current && std::strlen(current->get_tagName()) == 0)
628 current = current->parent();
629 return current ? QString::fromUtf8(current->get_tagName()) : QString();
630}
631
633{
634 m_index.elementToIndex.clear();
635 m_index.indexToElement.clear();
636 m_index.text.clear();
637
638 int index = 0;
639 bool inBody = false;
640 litehtml::element::ptr current = firstLeaf(m_document->root(), nullptr);
641 while (current != m_document->root()) {
642 m_index.elementToIndex.insert({current, index});
643 if (!inBody)
644 inBody = tagName(current).toLower() == "body";
645 if (inBody && isVisible(current)) {
646 std::string text;
647 current->get_text(text);
648 if (!text.empty()) {
649 m_index.indexToElement.push_back({index, current});
650 const QString str = QString::fromStdString(text);
651 m_index.text += str;
652 index += str.size();
653 }
654 }
655 current = nextLeaf(current, m_document->root());
656 }
657}
658
660{
661 const QString oldText = m_selection.text;
662 m_selection.update();
663 if (!m_clipboardCallback)
664 return;
665
666 const QString newText = m_selection.text;
667 if (oldText.isEmpty() && !newText.isEmpty())
668 m_clipboardCallback(true);
669 else if (!oldText.isEmpty() && newText.isEmpty())
670 m_clipboardCallback(false);
671}
672
674{
675 const QString oldText = m_selection.text;
676 m_selection = {};
677 if (!m_clipboardCallback)
678 return;
679
680 if (!oldText.isEmpty())
681 m_clipboardCallback(false);
682}
683
684void DocumentContainerPrivate::draw_background(litehtml::uint_ptr hdc,
685 const std::vector<litehtml::background_paint> &bgs)
686{
687 auto painter = toQPainter(hdc);
688 painter->save();
689 for (const litehtml::background_paint &bg : bgs) {
690 if (bg.is_root) {
691 // TODO ?
692 break;
693 }
694 painter->setClipRect(toQRect(bg.clip_box));
695 const QRegion horizontalMiddle(QRect(bg.border_box.x,
696 bg.border_box.y + bg.border_radius.top_left_y,
697 bg.border_box.width,
698 bg.border_box.height - bg.border_radius.top_left_y
699 - bg.border_radius.bottom_left_y));
700 const QRegion horizontalTop(
701 QRect(bg.border_box.x + bg.border_radius.top_left_x,
702 bg.border_box.y,
703 bg.border_box.width - bg.border_radius.top_left_x - bg.border_radius.top_right_x,
704 bg.border_radius.top_left_y));
705 const QRegion horizontalBottom(QRect(bg.border_box.x + bg.border_radius.bottom_left_x,
706 bg.border_box.bottom() - bg.border_radius.bottom_left_y,
707 bg.border_box.width - bg.border_radius.bottom_left_x
708 - bg.border_radius.bottom_right_x,
709 bg.border_radius.bottom_left_y));
710 const QRegion topLeft(QRect(bg.border_box.left(),
711 bg.border_box.top(),
712 2 * bg.border_radius.top_left_x,
713 2 * bg.border_radius.top_left_y),
714 QRegion::Ellipse);
715 const QRegion topRight(QRect(bg.border_box.right() - 2 * bg.border_radius.top_right_x,
716 bg.border_box.top(),
717 2 * bg.border_radius.top_right_x,
718 2 * bg.border_radius.top_right_y),
719 QRegion::Ellipse);
720 const QRegion bottomLeft(QRect(bg.border_box.left(),
721 bg.border_box.bottom() - 2 * bg.border_radius.bottom_left_y,
722 2 * bg.border_radius.bottom_left_x,
723 2 * bg.border_radius.bottom_left_y),
724 QRegion::Ellipse);
725 const QRegion bottomRight(QRect(bg.border_box.right() - 2 * bg.border_radius.bottom_right_x,
726 bg.border_box.bottom() - 2 * bg.border_radius.bottom_right_y,
727 2 * bg.border_radius.bottom_right_x,
728 2 * bg.border_radius.bottom_right_y),
729 QRegion::Ellipse);
730 const QRegion clipRegion = horizontalMiddle.united(horizontalTop)
731 .united(horizontalBottom)
732 .united(topLeft)
733 .united(topRight)
734 .united(bottomLeft)
735 .united(bottomRight);
736 painter->setClipRegion(clipRegion, Qt::IntersectClip);
737 painter->setPen(Qt::NoPen);
738 painter->setBrush(toQColor(bg.color));
739 painter->drawRect(bg.border_box.x,
740 bg.border_box.y,
741 bg.border_box.width,
742 bg.border_box.height);
743 drawSelection(painter, toQRect(bg.border_box));
744 if (!bg.image.empty()) {
745 const QPixmap pixmap = getPixmap(QString::fromStdString(bg.image),
746 QString::fromStdString(bg.baseurl));
747 if (bg.repeat == litehtml::background_repeat_no_repeat) {
748 painter->drawPixmap(QRect(bg.position_x,
749 bg.position_y,
750 bg.image_size.width,
751 bg.image_size.height),
752 pixmap);
753 } else if (bg.repeat == litehtml::background_repeat_repeat_x) {
754 if (bg.image_size.width > 0) {
755 int x = bg.border_box.left();
756 while (x <= bg.border_box.right()) {
757 painter->drawPixmap(QRect(x,
758 bg.border_box.top(),
759 bg.image_size.width,
760 bg.image_size.height),
761 pixmap);
762 x += bg.image_size.width;
763 }
764 }
765 } else {
766 qWarning(log) << "unsupported background repeat" << bg.repeat;
767 }
768 }
769 }
770 painter->restore();
771}
772
773void DocumentContainerPrivate::draw_borders(litehtml::uint_ptr hdc,
774 const litehtml::borders &borders,
775 const litehtml::position &draw_pos,
776 bool root)
777{
778 Q_UNUSED(root)
779 // TODO: special border styles
780 auto painter = toQPainter(hdc);
781 if (borders.top.style != litehtml::border_style_none
782 && borders.top.style != litehtml::border_style_hidden) {
783 painter->setPen(borderPen(borders.top));
784 painter->drawLine(draw_pos.left() + borders.radius.top_left_x,
785 draw_pos.top(),
786 draw_pos.right() - borders.radius.top_right_x,
787 draw_pos.top());
788 painter->drawArc(draw_pos.left(),
789 draw_pos.top(),
790 2 * borders.radius.top_left_x,
791 2 * borders.radius.top_left_y,
792 90 * 16,
793 90 * 16);
794 painter->drawArc(draw_pos.right() - 2 * borders.radius.top_right_x,
795 draw_pos.top(),
796 2 * borders.radius.top_right_x,
797 2 * borders.radius.top_right_y,
798 0,
799 90 * 16);
800 }
801 if (borders.bottom.style != litehtml::border_style_none
802 && borders.bottom.style != litehtml::border_style_hidden) {
803 painter->setPen(borderPen(borders.bottom));
804 painter->drawLine(draw_pos.left() + borders.radius.bottom_left_x,
805 draw_pos.bottom(),
806 draw_pos.right() - borders.radius.bottom_right_x,
807 draw_pos.bottom());
808 painter->drawArc(draw_pos.left(),
809 draw_pos.bottom() - 2 * borders.radius.bottom_left_y,
810 2 * borders.radius.bottom_left_x,
811 2 * borders.radius.bottom_left_y,
812 180 * 16,
813 90 * 16);
814 painter->drawArc(draw_pos.right() - 2 * borders.radius.bottom_right_x,
815 draw_pos.bottom() - 2 * borders.radius.bottom_right_y,
816 2 * borders.radius.bottom_right_x,
817 2 * borders.radius.bottom_right_y,
818 270 * 16,
819 90 * 16);
820 }
821 if (borders.left.style != litehtml::border_style_none
822 && borders.left.style != litehtml::border_style_hidden) {
823 painter->setPen(borderPen(borders.left));
824 painter->drawLine(draw_pos.left(),
825 draw_pos.top() + borders.radius.top_left_y,
826 draw_pos.left(),
827 draw_pos.bottom() - borders.radius.bottom_left_y);
828 }
829 if (borders.right.style != litehtml::border_style_none
830 && borders.right.style != litehtml::border_style_hidden) {
831 painter->setPen(borderPen(borders.right));
832 painter->drawLine(draw_pos.right(),
833 draw_pos.top() + borders.radius.top_right_y,
834 draw_pos.right(),
835 draw_pos.bottom() - borders.radius.bottom_right_y);
836 }
837}
838
839void DocumentContainerPrivate::set_caption(const char *caption)
840{
841 m_caption = QString::fromUtf8(caption);
842}
843
844void DocumentContainerPrivate::set_base_url(const char *base_url)
845{
846 m_baseUrl = QString::fromUtf8(base_url);
847}
848
849void DocumentContainerPrivate::link(const std::shared_ptr<litehtml::document> &doc,
850 const litehtml::element::ptr &el)
851{
852 // TODO
853 qDebug(log) << "link";
854 Q_UNUSED(doc)
855 Q_UNUSED(el)
856}
857
858void DocumentContainerPrivate::on_anchor_click(const char *url, const litehtml::element::ptr &el)
859{
860 Q_UNUSED(el)
861 if (!m_blockLinks)
862 m_linkCallback(resolveUrl(QString::fromUtf8(url), m_baseUrl));
863}
864
865void DocumentContainerPrivate::set_cursor(const char *cursor)
866{
867 m_cursorCallback(toQCursor(QString::fromUtf8(cursor)));
868}
869
870void DocumentContainerPrivate::transform_text(std::string &text, litehtml::text_transform tt)
871{
872 // TODO
873 qDebug(log) << "transform_text";
874 Q_UNUSED(text)
875 Q_UNUSED(tt)
876}
877
879 const std::string &url,
880 std::string &baseurl)
881{
882 const QUrl actualUrl = resolveUrl(QString::fromStdString(url), QString::fromStdString(baseurl));
883 const QString urlString = actualUrl.toString(QUrl::None);
884 const int lastSlash = urlString.lastIndexOf('/');
885 baseurl = urlString.left(lastSlash).toStdString();
886 text = QString::fromUtf8(m_dataCallback(actualUrl)).toStdString();
887}
888
889void DocumentContainerPrivate::set_clip(const litehtml::position &pos,
890 const litehtml::border_radiuses &bdr_radius)
891{
892 // TODO
893 qDebug(log) << "set_clip";
894 Q_UNUSED(pos)
895 Q_UNUSED(bdr_radius)
896}
897
899{
900 // TODO
901 qDebug(log) << "del_clip";
902}
903
904void DocumentContainerPrivate::get_client_rect(litehtml::position &client) const
905{
906 client = {m_clientRect.x(), m_clientRect.y(), m_clientRect.width(), m_clientRect.height()};
907}
908
910 const char *tag_name,
911 const litehtml::string_map &attributes,
912 const std::shared_ptr<litehtml::document> &doc)
913{
914 // TODO
915 qDebug(log) << "create_element" << QString::fromUtf8(tag_name);
916 Q_UNUSED(attributes)
917 Q_UNUSED(doc)
918 return {};
919}
920
921void DocumentContainerPrivate::get_media_features(litehtml::media_features &media) const
922{
923 media.type = litehtml::media_type_screen;
924 // TODO
925 qDebug(log) << "get_media_features";
926}
927
928void DocumentContainerPrivate::get_language(std::string &language, std::string &culture) const
929{
930 // TODO
931 qDebug(log) << "get_language";
932 Q_UNUSED(language)
933 Q_UNUSED(culture)
934}
935
936void DocumentContainer::setPaintDevice(QPaintDevice *paintDevice)
937{
938 d->m_paintDevice = paintDevice;
939}
940
941void DocumentContainer::setScrollPosition(const QPoint &pos)
942{
943 d->m_scrollPosition = pos;
944}
945
946void DocumentContainer::setDocument(const QByteArray &data, DocumentContainerContext *context)
947{
948 d->m_pixmaps.clear();
949 d->clearSelection();
950 d->m_document = litehtml::document::createFromString(data.constData(),
951 d.get(),
952 context->d->masterCss.toUtf8().constData());
953 d->buildIndex();
954}
955
956bool DocumentContainer::hasDocument() const
957{
958 return d->m_document.get();
959}
960
961void DocumentContainer::setBaseUrl(const QString &url)
962{
963 d->set_base_url(url.toUtf8().constData());
964}
965
966void DocumentContainer::render(int width, int height)
967{
968 d->m_clientRect = {0, 0, width, height};
969 if (!d->m_document)
970 return;
971 d->m_document->render(width);
972 d->updateSelection();
973}
974
975void DocumentContainer::draw(QPainter *painter, const QRect &clip)
976{
977 d->drawSelection(painter, clip);
978 const QPoint pos = -d->m_scrollPosition;
979 const litehtml::position clipRect = {clip.x(), clip.y(), clip.width(), clip.height()};
980 d->m_document->draw(reinterpret_cast<litehtml::uint_ptr>(painter), pos.x(), pos.y(), &clipRect);
981}
982
983int DocumentContainer::documentWidth() const
984{
985 return d->m_document->width();
986}
987
988int DocumentContainer::documentHeight() const
989{
990 return d->m_document->height();
991}
992
993int DocumentContainer::anchorY(const QString &anchorName) const
994{
995 litehtml::element::ptr element = d->m_document->root()->select_one(
996 QString("#%1").arg(anchorName).toStdString());
997 if (!element) {
998 element = d->m_document->root()->select_one(QString("[name=%1]").arg(anchorName).toStdString());
999 }
1000 if (!element)
1001 return -1;
1002 while (element) {
1003 if (element->get_placement().y > 0)
1004 return element->get_placement().y;
1005 element = element->parent();
1006 }
1007 return 0;
1008}
1009
1010QVector<QRect> DocumentContainer::mousePressEvent(const QPoint &documentPos,
1011 const QPoint &viewportPos,
1012 Qt::MouseButton button)
1013{
1014 if (!d->m_document || button != Qt::LeftButton)
1015 return {};
1016 QVector<QRect> redrawRects;
1017 // selection
1018 if (d->m_selection.isValid())
1019 redrawRects.append(d->m_selection.boundingRect());
1020 d->clearSelection();
1021 d->m_selection.selectionStartDocumentPos = documentPos;
1022 d->m_selection.startElem = selection_element_at_point(d->m_document->root(),
1023 documentPos,
1024 viewportPos,
1025 d->m_selection.mode);
1026 // post to litehtml
1027 litehtml::position::vector redrawBoxes;
1028 if (d->m_document->on_lbutton_down(
1029 documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) {
1030 for (const litehtml::position &box : redrawBoxes)
1031 redrawRects.append(toQRect(box));
1032 }
1033 return redrawRects;
1034}
1035
1036QVector<QRect> DocumentContainer::mouseMoveEvent(const QPoint &documentPos,
1037 const QPoint &viewportPos)
1038{
1039 if (!d->m_document)
1040 return {};
1041 QVector<QRect> redrawRects;
1042 // selection
1043 if (d->m_selection.isSelecting
1044 || (!d->m_selection.selectionStartDocumentPos.isNull()
1045 && (d->m_selection.selectionStartDocumentPos - documentPos).manhattanLength() >= kDragDistance
1046 && d->m_selection.startElem.element)) {
1047 const Selection::Element element = selection_element_at_point(d->m_document->root(),
1048 documentPos,
1049 viewportPos,
1050 d->m_selection.mode);
1051 if (element.element) {
1052 redrawRects.append(
1053 d->m_selection.boundingRect() /*.adjusted(-1, -1, +1, +1)*/); // redraw old selection area
1054 d->m_selection.endElem = element;
1055 d->updateSelection();
1056 redrawRects.append(d->m_selection.boundingRect());
1057 }
1058 d->m_selection.isSelecting = true;
1059 }
1060 litehtml::position::vector redrawBoxes;
1061 if (d->m_document->on_mouse_over(
1062 documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) {
1063 for (const litehtml::position &box : redrawBoxes)
1064 redrawRects.append(toQRect(box));
1065 }
1066 return redrawRects;
1067}
1068
1069QVector<QRect> DocumentContainer::mouseReleaseEvent(const QPoint &documentPos,
1070 const QPoint &viewportPos,
1071 Qt::MouseButton button)
1072{
1073 if (!d->m_document || button != Qt::LeftButton)
1074 return {};
1075 QVector<QRect> redrawRects;
1076 // selection
1077 d->m_selection.isSelecting = false;
1078 d->m_selection.selectionStartDocumentPos = {};
1079 if (d->m_selection.isValid())
1080 d->m_blockLinks = true;
1081 else
1082 d->clearSelection();
1083 litehtml::position::vector redrawBoxes;
1084 if (d->m_document->on_lbutton_up(
1085 documentPos.x(), documentPos.y(), viewportPos.x(), viewportPos.y(), redrawBoxes)) {
1086 for (const litehtml::position &box : redrawBoxes)
1087 redrawRects.append(toQRect(box));
1088 }
1089 d->m_blockLinks = false;
1090 return redrawRects;
1091}
1092
1093QVector<QRect> DocumentContainer::mouseDoubleClickEvent(const QPoint &documentPos,
1094 const QPoint &viewportPos,
1095 Qt::MouseButton button)
1096{
1097 if (!d->m_document || button != Qt::LeftButton)
1098 return {};
1099 QVector<QRect> redrawRects;
1100 d->clearSelection();
1101 d->m_selection.mode = Selection::Mode::Word;
1102 const Selection::Element element = selection_element_at_point(d->m_document->root(),
1103 documentPos,
1104 viewportPos,
1105 d->m_selection.mode);
1106 if (element.element) {
1107 d->m_selection.startElem = element;
1108 d->m_selection.endElem = d->m_selection.startElem;
1109 d->m_selection.isSelecting = true;
1110 d->updateSelection();
1111 if (d->m_selection.isValid())
1112 redrawRects.append(d->m_selection.boundingRect());
1113 } else {
1114 if (d->m_selection.isValid())
1115 redrawRects.append(d->m_selection.boundingRect());
1116 d->clearSelection();
1117 }
1118 return redrawRects;
1119}
1120
1121QVector<QRect> DocumentContainer::leaveEvent()
1122{
1123 if (!d->m_document)
1124 return {};
1125 litehtml::position::vector redrawBoxes;
1126 if (d->m_document->on_mouse_leave(redrawBoxes)) {
1127 QVector<QRect> redrawRects;
1128 for (const litehtml::position &box : redrawBoxes)
1129 redrawRects.append(toQRect(box));
1130 return redrawRects;
1131 }
1132 return {};
1133}
1134
1135QUrl DocumentContainer::linkAt(const QPoint &documentPos, const QPoint &viewportPos)
1136{
1137 if (!d->m_document)
1138 return {};
1139 const char *href = nullptr;
1140 deepest_child_at_point(d->m_document->root(),
1141 documentPos,
1142 viewportPos,
1143 [&href](const litehtml::element::ptr &e) {
1144 const litehtml::element::ptr parent = e->parent();
1145 if (parent && parent->tag() == litehtml::_a_) {
1146 href = parent->get_attr("href");
1147 if (href)
1148 return true;
1149 }
1150 return false; /*continue*/
1151 });
1152 if (href)
1153 return d->resolveUrl(QString::fromUtf8(href), d->m_baseUrl);
1154 return {};
1155}
1156
1157QString DocumentContainer::caption() const
1158{
1159 return d->m_caption;
1160}
1161
1162QString DocumentContainer::selectedText() const
1163{
1164 return d->m_selection.text;
1165}
1166
1167void DocumentContainer::findText(const QString &text,
1168 QTextDocument::FindFlags flags,
1169 bool incremental,
1170 bool *wrapped,
1171 bool *success,
1172 QVector<QRect> *oldSelection,
1173 QVector<QRect> *newSelection)
1174{
1175 if (success)
1176 *success = false;
1177 if (oldSelection)
1178 oldSelection->clear();
1179 if (newSelection)
1180 newSelection->clear();
1181 if (!d->m_document)
1182 return;
1183 const bool backward = flags & QTextDocument::FindBackward;
1184 int startIndex = backward ? -1 : 0;
1185 if (d->m_selection.startElem.element && d->m_selection.endElem.element) { // selection
1186 // poor-man's incremental search starts at beginning of selection,
1187 // non-incremental at end (forward search) or beginning (backward search)
1188 Selection::Element start;
1189 Selection::Element end;
1190 std::tie(start, end) = getStartAndEnd(d->m_selection.startElem, d->m_selection.endElem);
1191 Selection::Element searchStart;
1192 if (incremental || backward) {
1193 if (start.index < 0) // fully selected
1194 searchStart = {firstLeaf(start.element, nullptr), 0, -1};
1195 else
1196 searchStart = start;
1197 } else {
1198 if (end.index < 0) // fully selected
1199 searchStart = {nextLeaf(end.element, nullptr), 0, -1};
1200 else
1201 searchStart = end;
1202 }
1203 const auto findInIndex = d->m_index.elementToIndex.find(searchStart.element);
1204 if (findInIndex == std::end(d->m_index.elementToIndex)) {
1205 qWarning() << "internal error: cannot find litehmtl element in index";
1206 return;
1207 }
1208 startIndex = findInIndex->second + searchStart.index;
1209 if (backward)
1210 --startIndex;
1211 }
1212
1213 const auto fillXPos = [](const Selection::Element &e) {
1214 std::string ttext;
1215 e.element->get_text(ttext);
1216 const QString text = QString::fromStdString(ttext);
1217 const auto fontPtr = e.element->css().get_font();
1218 if (!fontPtr)
1219 return e;
1220 const QFont &font = toQFont(fontPtr);
1221 const QFontMetrics fm(font);
1222 return Selection::Element{e.element, e.index, fm.size(0, text.left(e.index)).width()};
1223 };
1224
1225 QString term = QRegularExpression::escape(text);
1226 if (flags & QTextDocument::FindWholeWords)
1227 term = QString("\\b%1\\b").arg(term);
1228 const QRegularExpression::PatternOptions patternOptions
1229 = (flags & QTextDocument::FindCaseSensitively) ? QRegularExpression::NoPatternOption
1230 : QRegularExpression::CaseInsensitiveOption;
1231 const QRegularExpression expression(term, patternOptions);
1232
1233 int foundIndex = backward ? d->m_index.text.lastIndexOf(expression, startIndex)
1234 : d->m_index.text.indexOf(expression, startIndex);
1235 if (foundIndex < 0) { // wrap
1236 foundIndex = backward ? d->m_index.text.lastIndexOf(expression)
1237 : d->m_index.text.indexOf(expression);
1238 if (wrapped && foundIndex >= 0)
1239 *wrapped = true;
1240 }
1241 if (foundIndex >= 0) {
1242 const Index::Entry startEntry = d->m_index.findElement(foundIndex);
1243 const Index::Entry endEntry = d->m_index.findElement(foundIndex + text.size());
1244 if (!startEntry.second || !endEntry.second) {
1245 qWarning() << "internal error: search ended up with nullptr elements";
1246 return;
1247 }
1248 if (oldSelection)
1249 *oldSelection = d->m_selection.selection;
1250 d->clearSelection();
1251 d->m_selection.startElem = fillXPos({startEntry.second, foundIndex - startEntry.first, -1});
1252 d->m_selection.endElem = fillXPos(
1253 {endEntry.second, int(foundIndex + text.size() - endEntry.first), -1});
1254 d->updateSelection();
1255 if (newSelection)
1256 *newSelection = d->m_selection.selection;
1257 if (success)
1258 *success = true;
1259 return;
1260 }
1261 return;
1262}
1263
1264void DocumentContainer::setDefaultFont(const QFont &font)
1265{
1266 d->m_defaultFont = font;
1267 d->m_defaultFontFamilyName = d->m_defaultFont.family().toUtf8();
1268 // Since font family name and size are read only once, when parsing html,
1269 // we need to trigger the reparse of this info.
1270 if (d->m_document && d->m_document->root()) {
1271 d->m_document->root()->refresh_styles();
1272 d->m_document->root()->compute_styles();
1273 }
1274}
1275
1276QFont DocumentContainer::defaultFont() const
1277{
1278 return d->m_defaultFont;
1279}
1280
1281void DocumentContainer::setAntialias(bool on)
1282{
1283 d->m_antialias = on;
1284}
1285
1286bool DocumentContainer::antialias() const
1287{
1288 return d->m_antialias;
1289}
1290
1291void DocumentContainer::setDataCallback(const DocumentContainer::DataCallback &callback)
1292{
1293 d->m_dataCallback = callback;
1294}
1295
1296void DocumentContainer::setCursorCallback(const DocumentContainer::CursorCallback &callback)
1297{
1298 d->m_cursorCallback = callback;
1299}
1300
1301void DocumentContainer::setLinkCallback(const DocumentContainer::LinkCallback &callback)
1302{
1303 d->m_linkCallback = callback;
1304}
1305
1306void DocumentContainer::setPaletteCallback(const DocumentContainer::PaletteCallback &callback)
1307{
1308 d->m_paletteCallback = callback;
1309}
1310
1311void DocumentContainer::setClipboardCallback(const DocumentContainer::ClipboardCallback &callback)
1312{
1313 d->m_clipboardCallback = callback;
1314}
1315
1316static litehtml::element::ptr elementForY(int y, const litehtml::element::ptr &element)
1317{
1318 if (!element)
1319 return {};
1320 if (element->get_placement().y >= y)
1321 return element;
1322 for (const litehtml::element::ptr &child : element->children()) {
1323 litehtml::element::ptr result = elementForY(y, child);
1324 if (result)
1325 return result;
1326 }
1327 return {};
1328}
1329
1330static litehtml::element::ptr elementForY(int y, const litehtml::document::ptr &document)
1331{
1332 if (!document)
1333 return {};
1334
1335 return elementForY(y, document->root());
1336}
1337
1338int DocumentContainer::withFixedElementPosition(int y, const std::function<void()> &action)
1339{
1340 const litehtml::element::ptr element = elementForY(y, d->m_document);
1341 action();
1342 if (element)
1343 return element->get_placement().y;
1344 return -1;
1345}
1346
1347QPixmap DocumentContainerPrivate::getPixmap(const QString &imageUrl, const QString &baseUrl)
1348{
1349 const QUrl url = resolveUrl(imageUrl, baseUrl);
1350 if (!m_pixmaps.contains(url)) {
1351 qWarning(log) << "draw_background: pixmap not loaded for" << url;
1352 return {};
1353 }
1354 return m_pixmaps.value(url);
1355}
1356
1358{
1359 // TODO make configurable
1360 return {"Times New Roman"};
1361}
1362
1364{
1365 // TODO make configurable
1366 return {"Arial"};
1367}
1368
1370{
1371 // TODO make configurable
1372 return {"Courier"};
1373}
1374
1375QUrl DocumentContainerPrivate::resolveUrl(const QString &url, const QString &baseUrl) const
1376{
1377 // several cases:
1378 // full url: "https://foo.bar/blah.css"
1379 // relative path: "foo/bar.css"
1380 // server relative path: "/foo/bar.css"
1381 // net path: "//foo.bar/blah.css"
1382 // fragment only: "#foo-fragment"
1383 const QUrl qurl = QUrl::fromEncoded(url.toUtf8());
1384 if (qurl.scheme().isEmpty()) {
1385 if (url.startsWith('#')) // leave alone if just a fragment
1386 return qurl;
1387 const QUrl pageBaseUrl = QUrl(baseUrl.isEmpty() ? m_baseUrl : baseUrl);
1388 if (url.startsWith("//")) // net path
1389 return QUrl(pageBaseUrl.scheme() + ":" + url);
1390 QUrl serverUrl = QUrl(pageBaseUrl);
1391 serverUrl.setPath("");
1392 const QString actualBaseUrl = url.startsWith('/')
1393 ? serverUrl.toString(QUrl::FullyEncoded)
1394 : pageBaseUrl.toString(QUrl::FullyEncoded);
1395 QUrl resolvedUrl(actualBaseUrl + '/' + url);
1396 resolvedUrl.setPath(resolvedUrl.path(QUrl::FullyEncoded | QUrl::NormalizePathSegments), QUrl::TolerantMode);
1397 return resolvedUrl;
1398 }
1399 return qurl;
1400}
1401
1402Index::Entry Index::findElement(int index) const
1403{
1404 const auto upper = std::upper_bound(std::begin(indexToElement),
1405 std::end(indexToElement),
1406 Entry{index, {}},
1407 [](const Entry &a, const Entry &b) {
1408 return a.first < b.first;
1409 });
1410 if (upper == std::begin(indexToElement)) // should not happen for index >= 0
1411 return {-1, {}};
1412 return *(upper - 1);
1413}
1414
1415DocumentContainerContext::DocumentContainerContext()
1416 : d(new DocumentContainerContextPrivate)
1417{}
1418
1419DocumentContainerContext::~DocumentContainerContext() = default;
1420
1421void DocumentContainerContext::setMasterStyleSheet(const QString &css)
1422{
1423 d->masterCss = css;
1424}
void get_image_size(const char *src, const char *baseurl, litehtml::size &sz) override
int pt_to_px(int pt) const override
void get_client_rect(litehtml::position &client) const override
const char * get_default_font_name() const override
void on_anchor_click(const char *url, const litehtml::element::ptr &el) override
void get_media_features(litehtml::media_features &media) const override
void draw_borders(litehtml::uint_ptr hdc, const litehtml::borders &borders, const litehtml::position &draw_pos, bool root) override
void link(const std::shared_ptr< litehtml::document > &doc, const litehtml::element::ptr &el) override
litehtml::uint_ptr create_font(const char *faceName, int size, int weight, litehtml::font_style italic, unsigned int decoration, litehtml::font_metrics *fm) override
void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) override
void set_caption(const char *caption) override
void get_language(std::string &language, std::string &culture) const override
void delete_font(litehtml::uint_ptr hFont) override
void import_css(std::string &text, const std::string &url, std::string &baseurl) override
void load_image(const char *src, const char *baseurl, bool redraw_on_ready) override
void transform_text(std::string &text, litehtml::text_transform tt) override
void draw_background(litehtml::uint_ptr hdc, const std::vector< litehtml::background_paint > &bgs) override
int get_default_font_size() const override
void set_clip(const litehtml::position &pos, const litehtml::border_radiuses &bdr_radius) override
int text_width(const char *text, litehtml::uint_ptr hFont) override
void set_cursor(const char *cursor) override
std::shared_ptr< litehtml::element > create_element(const char *tag_name, const litehtml::string_map &attributes, const std::shared_ptr< litehtml::document > &doc) override
void drawSelection(QPainter *painter, const QRect &clip) const
DocumentContainer::DataCallback m_dataCallback
QUrl resolveUrl(const QString &url, const QString &baseUrl) const
void set_base_url(const char *base_url) override
QPixmap getPixmap(const QString &imageUrl, const QString &baseUrl)
void draw_text(litehtml::uint_ptr hdc, const char *text, litehtml::uint_ptr hFont, litehtml::web_color color, const litehtml::position &pos) override
QRect boundingRect() const
bool isValid() const
static std::tuple< litehtml::element::ptr, litehtml::element::ptr, litehtml::element::ptr > getCommonParent(const litehtml::elements_list &a, const litehtml::elements_list &b)
static Qt::PenStyle borderPenStyle(litehtml::border_style style)
static QCursor toQCursor(const QString &c)
static Selection::Element selection_element_at_point(const litehtml::element::ptr &element, const QPoint &pos, const QPoint &viewportPos, Selection::Mode mode)
static Q_LOGGING_CATEGORY(log, "qlitehtml", QtCriticalMsg) static QFont toQFont(litehtml
static litehtml::element::ptr firstLeaf(const litehtml::element::ptr &element, const litehtml::element::ptr &stop)
static QFont::Style toQFontStyle(litehtml::font_style style)
static QRect toQRect(litehtml::position position)
static bool deepest_child_at_point(const litehtml::element::ptr &element, const QPoint &pos, const QPoint &viewportPos, const std::function< bool(const litehtml::element::ptr &)> &action, int level=0)
static QPen borderPen(const litehtml::border &border)
static std::pair< Selection::Element, Selection::Element > getStartAndEnd(const Selection::Element &a, const Selection::Element &b)
static litehtml::elements_list path(const litehtml::element::ptr &element)
static QPainter * toQPainter(litehtml::uint_ptr hdc)
static Selection::Element selectionDetails(const litehtml::element::ptr &element, const QString &text, const QPoint &pos)
static litehtml::element::ptr nextLeaf(const litehtml::element::ptr &element, const litehtml::element::ptr &stop)
static QColor toQColor(const litehtml::web_color &color)
static bool isVisible(const litehtml::element::ptr &element)
static litehtml::element::ptr elementForY(int y, const litehtml::element::ptr &element)
static QFont::Weight cssWeightToQtWeight(int cssWeight)
static QString tagName(const litehtml::element::ptr &e)
const int kDragDistance
Entry findElement(int index) const