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
qtextimagehandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
6
7#include <qbuffer.h>
8#include <qguiapplication.h>
9#include <qtextformat.h>
10#include <qpainter.h>
11#include <qdebug.h>
12#include <qicon.h>
13#include <qimagereader.h>
14#include <qpixmapcache.h>
15#include <qthread.h>
16#include <qpa/qplatformintegration.h>
17#include <private/qguiapplication_p.h>
18#include <private/qhexstring_p.h>
19#include <private/qtextengine_p.h>
20#include <limits>
21
23
24using namespace Qt::StringLiterals;
25
26static inline QUrl findAtNxFileOrResource(const QString &baseFileName,
27 qreal targetDevicePixelRatio,
28 qreal *sourceDevicePixelRatio,
29 QString *name)
30{
31 // qt_findAtNxFile expects a file name that can be tested with QFile::exists.
32 // so if the format.name() is a file:/ or qrc:/ URL, then we need to strip away the schema.
33 QString localFile;
34 const QUrl url(baseFileName);
35 bool hasFileScheme = false;
36 bool isResource = false;
37 if (url.isLocalFile()) {
38 localFile = url.toLocalFile();
39 hasFileScheme = true;
40 } else if (baseFileName.startsWith("qrc:/"_L1)) {
41 // QFile::exists() can only handle ":/file.txt"
42 localFile = baseFileName.sliced(3);
43 isResource = true;
44 } else {
45 localFile = baseFileName;
46 isResource = baseFileName.startsWith(":/"_L1);
47 }
48 *name = qt_findAtNxFile(localFile, targetDevicePixelRatio, sourceDevicePixelRatio);
49
50 if (hasFileScheme)
51 return QUrl::fromLocalFile(*name);
52 if (isResource)
53 return QUrl("qrc"_L1 + *name);
54 return QUrl(*name);
55}
56
57static QImage getImage(QTextDocument *doc, const QTextImageFormat &format,
58 const qreal devicePixelRatio = 1.0, const QSizeF &size = {})
59{
60 qreal sourcePixelRatio = 1.0;
61 QString name;
62 const QUrl url = findAtNxFileOrResource(format.name(), devicePixelRatio, &sourcePixelRatio, &name);
63 const QVariant data = doc->resource(QTextDocument::ImageResource, url);
64
65 // directly provided as QPixmap/QImage or already cached
66 if (data.userType() == QMetaType::QPixmap || data.userType() == QMetaType::QImage)
67 return data.value<QImage>();
68
69 // some image formats are scalable (e.g. svg) so we should not store a single
70 // QImage as QTextDocument::ImageResource to avoid scaling artefacts later on.
71 // But otoh we don't want to load them on every usage. Therefore cache the result
72 // in QPixmapCache
73 const bool canUsePixmapCache = (QThread::isMainThread()
74 || QGuiApplicationPrivate::platformIntegration()->hasCapability(
75 QPlatformIntegration::ThreadedPixmaps));
76 const auto buildCacheKey = [](const QString &name, const QSizeF &size, qreal dpr) -> QString {
77 return QLatin1String("qt_textimagehandler_") % name
78 % HexString<int>(size.width())
79 % HexString<int>(size.height())
80 % HexString<qint16>(qRound(dpr * 1000));
81 };
82 const QString cacheKey = canUsePixmapCache
83 ? buildCacheKey(name, size * devicePixelRatio, sourcePixelRatio)
84 : QString();
85
86 if (canUsePixmapCache) {
87 QPixmap pm;
88 if (QPixmapCache::find(cacheKey, &pm))
89 return pm.toImage();
90 }
91
92 const auto readImage = [&](auto &&content) {
93 QImageReader imgReader(content);
94 if (imgReader.canRead()) {
95 const bool supportsScaledSize = imgReader.supportsOption(QImageIOHandler::ScaledSize);
96 if (size.isValid() && supportsScaledSize)
97 imgReader.setScaledSize((size * devicePixelRatio).toSize());
98 QImage result = imgReader.read();
99 result.setDevicePixelRatio(sourcePixelRatio);
100 if (!supportsScaledSize)
101 doc->addResource(QTextDocument::ImageResource, url, result);
102 else if (canUsePixmapCache)
103 QPixmapCache::insert(cacheKey, QPixmap::fromImage(result));
104 return result;
105 }
106 return QImage();
107 };
108
109 QImage result;
110 if (data.metaType() == QMetaType::fromType<QByteArray>()) {
111 QByteArray ba(data.toByteArray());
112 QBuffer buf(&ba);
113 result = readImage(&buf);
114 }
115 if (result.isNull())
116 result = readImage(name);
117
118 if (result.isNull())
119 result = QImage(":/qt-project.org/styles/commonstyle/images/file-16.png"_L1);
120 return result;
121}
122
123static QSize getSize(QTextDocument *doc, const QTextImageFormat &format)
124{
125 const bool hasWidth = format.hasProperty(QTextFormat::ImageWidth);
126 int width = qRound(format.width());
127 const bool hasHeight = format.hasProperty(QTextFormat::ImageHeight);
128 const int height = qRound(format.height());
129
130 const bool hasMaxWidth = format.hasProperty(QTextFormat::ImageMaxWidth);
131 const auto maxWidth = format.maximumWidth();
132
133 int effectiveMaxWidth = std::numeric_limits<int>::max();
134 if (hasMaxWidth) {
135 if (maxWidth.type() == QTextLength::PercentageLength)
136 effectiveMaxWidth = (doc->pageSize().width() - 2 * doc->documentMargin()) * maxWidth.value(100) / 100;
137 else
138 effectiveMaxWidth = maxWidth.rawValue();
139
140 width = qMin(effectiveMaxWidth, width);
141 }
142
143 QImage source;
144 QSize size(width, height);
145 if (!hasWidth || !hasHeight) {
146 source = getImage(doc, format);
147 QSizeF sourceSize = source.deviceIndependentSize();
148
149 if (sourceSize.width() > effectiveMaxWidth) {
150 // image is bigger than effectiveMaxWidth, scale it down
151 sourceSize.setHeight(effectiveMaxWidth * (sourceSize.height() / qreal(sourceSize.width())));
152 sourceSize.setWidth(effectiveMaxWidth);
153 }
154
155 if (!hasWidth) {
156 if (!hasHeight)
157 size.setWidth(sourceSize.width());
158 else
159 size.setWidth(qMin(effectiveMaxWidth, qRound(height * (sourceSize.width() / qreal(sourceSize.height())))));
160 }
161 if (!hasHeight) {
162 if (!hasWidth)
163 size.setHeight(sourceSize.height());
164 else
165 size.setHeight(qRound(width * (sourceSize.height() / qreal(sourceSize.width()))));
166 }
167 }
168
169 const QPaintDevice *pdev = doc->documentLayout()->paintDevice();
170 if (pdev)
171 size *= qreal(pdev->logicalDpiY()) / qreal(qt_defaultDpi());
172
173 return size;
174}
175
176QTextImageHandler::QTextImageHandler(QObject *parent)
177 : QObject(parent)
178{
179}
180
181QSizeF QTextImageHandler::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format)
182{
183 Q_UNUSED(posInDocument);
184 const QTextImageFormat imageFormat = format.toImageFormat();
185
186 return getSize(doc, imageFormat);
187}
188
189QImage QTextImageHandler::image(QTextDocument *doc, const QTextImageFormat &imageFormat)
190{
191 Q_ASSERT(doc != nullptr);
192
193 return getImage(doc, imageFormat);
194}
195
196void QTextImageHandler::drawObject(QPainter *p, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format)
197{
198 Q_UNUSED(posInDocument);
199 const QTextImageFormat imageFormat = format.toImageFormat();
200
201 const QImage image = getImage(doc, imageFormat, p->device()->devicePixelRatio(), rect.size());
202 p->drawImage(rect, image, image.rect());
203}
204
205QT_END_NAMESPACE
206
207#include "moc_qtextimagehandler_p.cpp"
\inmodule QtCore \reentrant
Definition qbuffer.h:17
Combined button and popup list for selecting options.
static QUrl findAtNxFileOrResource(const QString &baseFileName, qreal targetDevicePixelRatio, qreal *sourceDevicePixelRatio, QString *name)
static QSize getSize(QTextDocument *doc, const QTextImageFormat &format)
static QImage getImage(QTextDocument *doc, const QTextImageFormat &format, const qreal devicePixelRatio=1.0, const QSizeF &size={})