/src/qtbase/src/gui/painting/qtextureglyphcache.cpp
Line | Count | Source |
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 | | // Qt-Security score:significant reason:default |
4 | | |
5 | | #include <qmath.h> |
6 | | |
7 | | #include "qtextureglyphcache_p.h" |
8 | | #include "private/qfontengine_p.h" |
9 | | #include "private/qnumeric_p.h" |
10 | | |
11 | | #include <QtGui/qpainterpath.h> |
12 | | |
13 | | QT_BEGIN_NAMESPACE |
14 | | |
15 | | // #define CACHE_DEBUG |
16 | | |
17 | | // out-of-line to avoid vtable duplication, breaking e.g. RTTI |
18 | | QTextureGlyphCache::~QTextureGlyphCache() |
19 | 0 | { |
20 | 0 | } |
21 | | |
22 | | int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const |
23 | 0 | { |
24 | | // Test 12 different subpixel positions since it factors into 3*4 so it gives |
25 | | // the coverage we need. |
26 | |
|
27 | 0 | const int NumSubpixelPositions = 12; |
28 | |
|
29 | 0 | QImage images[NumSubpixelPositions]; |
30 | 0 | int numImages = 0; |
31 | 0 | for (int i = 0; i < NumSubpixelPositions; ++i) { |
32 | 0 | QImage img = textureMapForGlyph(glyph, QFixedPoint(QFixed::fromReal(i / 12.0), 0)); |
33 | |
|
34 | 0 | if (numImages == 0) { |
35 | 0 | QPainterPath path; |
36 | 0 | QFixedPoint point; |
37 | 0 | m_current_fontengine->addGlyphsToPath(&glyph, &point, 1, &path, QTextItem::RenderFlags()); |
38 | | |
39 | | // Glyph is space, return 0 to indicate that we need to keep trying |
40 | 0 | if (path.isEmpty()) |
41 | 0 | break; |
42 | | |
43 | 0 | images[numImages++] = std::move(img); |
44 | 0 | } else { |
45 | 0 | bool found = false; |
46 | 0 | for (int j = 0; j < numImages; ++j) { |
47 | 0 | if (images[j] == img) { |
48 | 0 | found = true; |
49 | 0 | break; |
50 | 0 | } |
51 | 0 | } |
52 | 0 | if (!found) |
53 | 0 | images[numImages++] = std::move(img); |
54 | 0 | } |
55 | 0 | } |
56 | |
|
57 | 0 | return numImages; |
58 | 0 | } |
59 | | |
60 | | bool QTextureGlyphCache::populate(QFontEngine *fontEngine, |
61 | | qsizetype numGlyphs, |
62 | | const glyph_t *glyphs, |
63 | | const QFixedPoint *positions, |
64 | | QPainter::RenderHints renderHints, |
65 | | bool includeGlyphCacheScale) |
66 | 0 | { |
67 | | #ifdef CACHE_DEBUG |
68 | | printf("Populating with %lld glyphs\n", static_cast<long long>(numGlyphs)); |
69 | | qDebug() << " -> current transformation: " << m_transform; |
70 | | #endif |
71 | |
|
72 | 0 | m_current_fontengine = fontEngine; |
73 | 0 | const int padding = glyphPadding(); |
74 | 0 | const int paddingDoubled = padding * 2; |
75 | |
|
76 | 0 | bool supportsSubPixelPositions = fontEngine->supportsSubPixelPositions(); |
77 | 0 | bool verticalSubPixelPositions = fontEngine->supportsVerticalSubPixelPositions() |
78 | 0 | && (renderHints & QPainter::VerticalSubpixelPositioning) != 0; |
79 | 0 | if (fontEngine->m_subPixelPositionCount == 0) { |
80 | 0 | if (!supportsSubPixelPositions) { |
81 | 0 | fontEngine->m_subPixelPositionCount = 1; |
82 | 0 | } else { |
83 | 0 | qsizetype i = 0; |
84 | 0 | while (fontEngine->m_subPixelPositionCount == 0 && i < numGlyphs) |
85 | 0 | fontEngine->m_subPixelPositionCount = calculateSubPixelPositionCount(glyphs[i++]); |
86 | 0 | } |
87 | 0 | } |
88 | |
|
89 | 0 | if (m_cx == 0 && m_cy == 0) { |
90 | 0 | m_cx = padding; |
91 | 0 | m_cy = padding; |
92 | 0 | } |
93 | |
|
94 | 0 | qreal glyphCacheScaleX = transform().m11(); |
95 | 0 | qreal glyphCacheScaleY = transform().m22(); |
96 | |
|
97 | 0 | QHash<GlyphAndSubPixelPosition, Coord> listItemCoordinates; |
98 | 0 | int rowHeight = 0; |
99 | | |
100 | | // check each glyph for its metrics and get the required rowHeight. |
101 | 0 | for (qsizetype i = 0; i < numGlyphs; ++i) { |
102 | 0 | const glyph_t glyph = glyphs[i]; |
103 | |
|
104 | 0 | QFixedPoint subPixelPosition; |
105 | 0 | if (supportsSubPixelPositions) { |
106 | 0 | QFixedPoint pos = positions != nullptr ? positions[i] : QFixedPoint(); |
107 | 0 | if (includeGlyphCacheScale) { |
108 | 0 | pos = QFixedPoint(QFixed::fromReal(pos.x.toReal() * glyphCacheScaleX), |
109 | 0 | QFixed::fromReal(pos.y.toReal() * glyphCacheScaleY)); |
110 | 0 | } |
111 | 0 | subPixelPosition = fontEngine->subPixelPositionFor(pos); |
112 | 0 | if (!verticalSubPixelPositions) |
113 | 0 | subPixelPosition.y = 0; |
114 | 0 | } |
115 | |
|
116 | 0 | if (coords.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition))) |
117 | 0 | continue; |
118 | 0 | if (listItemCoordinates.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition))) |
119 | 0 | continue; |
120 | | |
121 | 0 | glyph_metrics_t metrics = fontEngine->alphaMapBoundingBox(glyph, subPixelPosition, m_transform, m_format); |
122 | |
|
123 | | #ifdef CACHE_DEBUG |
124 | | printf("(%4x): w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n", |
125 | | glyph, |
126 | | metrics.width.toReal(), |
127 | | metrics.height.toReal(), |
128 | | metrics.xoff.toReal(), |
129 | | metrics.yoff.toReal(), |
130 | | metrics.x.toReal(), |
131 | | metrics.y.toReal()); |
132 | | #endif |
133 | 0 | GlyphAndSubPixelPosition key(glyph, subPixelPosition); |
134 | 0 | int glyph_width = metrics.width.ceil().toInt(); |
135 | 0 | int glyph_height = metrics.height.ceil().toInt(); |
136 | 0 | if (glyph_height == 0 || glyph_width == 0) { |
137 | | // Avoid multiple calls to boundingBox() for non-printable characters |
138 | 0 | Coord c = { 0, 0, 0, 0, 0, 0 }; |
139 | 0 | coords.insert(key, c); |
140 | 0 | continue; |
141 | 0 | } |
142 | | // align to 8-bit boundary |
143 | 0 | if (m_format == QFontEngine::Format_Mono) |
144 | 0 | glyph_width = (glyph_width+7)&~7; |
145 | |
|
146 | 0 | Coord c = { 0, 0, // will be filled in later |
147 | 0 | glyph_width, |
148 | 0 | glyph_height, // texture coords |
149 | 0 | metrics.x.truncate(), |
150 | 0 | -metrics.y.truncate() }; // baseline for horizontal scripts |
151 | |
|
152 | 0 | listItemCoordinates.insert(key, c); |
153 | 0 | rowHeight = qMax(rowHeight, glyph_height); |
154 | 0 | } |
155 | 0 | if (listItemCoordinates.isEmpty()) |
156 | 0 | return true; |
157 | | |
158 | 0 | rowHeight += paddingDoubled; |
159 | |
|
160 | 0 | if (m_w == 0) { |
161 | 0 | if (fontEngine->maxCharWidth() <= QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH) |
162 | 0 | m_w = QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH; |
163 | 0 | else |
164 | 0 | m_w = qNextPowerOfTwo(qCeil(fontEngine->maxCharWidth()) - 1); |
165 | 0 | } |
166 | | |
167 | | // now actually use the coords and paint the wanted glyps into cache. |
168 | 0 | QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = listItemCoordinates.begin(); |
169 | 0 | int requiredWidth = m_w; |
170 | 0 | while (iter != listItemCoordinates.end()) { |
171 | 0 | Coord c = iter.value(); |
172 | |
|
173 | 0 | m_currentRowHeight = qMax(m_currentRowHeight, c.h); |
174 | |
|
175 | 0 | if (m_cx + c.w + padding > requiredWidth) { |
176 | 0 | int new_width = requiredWidth*2; |
177 | 0 | while (new_width < m_cx + c.w + padding) |
178 | 0 | new_width *= 2; |
179 | 0 | if (new_width <= maxTextureWidth()) { |
180 | 0 | requiredWidth = new_width; |
181 | 0 | } else { |
182 | | // no room on the current line, start new glyph strip |
183 | 0 | m_cx = padding; |
184 | 0 | m_cy += m_currentRowHeight + paddingDoubled; |
185 | 0 | m_currentRowHeight = c.h; // New row |
186 | 0 | } |
187 | 0 | } |
188 | |
|
189 | 0 | if (maxTextureHeight() > 0 && m_cy + c.h + padding > maxTextureHeight()) { |
190 | | // We can't make a cache of the required size, so we bail out |
191 | 0 | return false; |
192 | 0 | } |
193 | | |
194 | 0 | c.x = m_cx; |
195 | 0 | c.y = m_cy; |
196 | |
|
197 | 0 | coords.insert(iter.key(), c); |
198 | 0 | m_pendingGlyphs.insert(iter.key(), c); |
199 | |
|
200 | 0 | m_cx += c.w + paddingDoubled; |
201 | 0 | ++iter; |
202 | 0 | } |
203 | 0 | return true; |
204 | |
|
205 | 0 | } |
206 | | |
207 | | void QTextureGlyphCache::fillInPendingGlyphs() |
208 | 0 | { |
209 | 0 | if (!hasPendingGlyphs()) |
210 | 0 | return; |
211 | | |
212 | 0 | int requiredHeight = m_h; |
213 | 0 | int requiredWidth = m_w; // Use a minimum size to avoid a lot of initial reallocations |
214 | 0 | const int padding = glyphPadding(); |
215 | 0 | { |
216 | 0 | QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin(); |
217 | 0 | while (iter != m_pendingGlyphs.end()) { |
218 | 0 | Coord c = iter.value(); |
219 | 0 | requiredHeight = qMax(requiredHeight, c.y + c.h + padding); |
220 | 0 | requiredWidth = qMax(requiredWidth, c.x + c.w + padding); |
221 | 0 | ++iter; |
222 | 0 | } |
223 | 0 | } |
224 | |
|
225 | 0 | if (isNull() || requiredHeight > m_h || requiredWidth > m_w) { |
226 | 0 | if (isNull()) |
227 | 0 | createCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1)); |
228 | 0 | else |
229 | 0 | resizeCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1)); |
230 | 0 | } |
231 | |
|
232 | 0 | beginFillTexture(); |
233 | 0 | { |
234 | 0 | QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin(); |
235 | 0 | while (iter != m_pendingGlyphs.end()) { |
236 | 0 | GlyphAndSubPixelPosition key = iter.key(); |
237 | 0 | fillTexture(iter.value(), key.glyph, key.subPixelPosition); |
238 | |
|
239 | 0 | ++iter; |
240 | 0 | } |
241 | 0 | } |
242 | 0 | endFillTexture(); |
243 | |
|
244 | 0 | m_pendingGlyphs.clear(); |
245 | 0 | } |
246 | | |
247 | | QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) const |
248 | 0 | { |
249 | 0 | switch (m_format) { |
250 | 0 | case QFontEngine::Format_A32: |
251 | 0 | return m_current_fontengine->alphaRGBMapForGlyph(g, subPixelPosition, m_transform); |
252 | 0 | case QFontEngine::Format_ARGB: |
253 | 0 | return m_current_fontengine->bitmapForGlyph(g, subPixelPosition, m_transform, color()); |
254 | 0 | default: |
255 | 0 | return m_current_fontengine->alphaMapForGlyph(g, subPixelPosition, m_transform); |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | | /************************************************************************ |
260 | | * QImageTextureGlyphCache |
261 | | */ |
262 | | |
263 | | // out-of-line to avoid vtable duplication, breaking e.g. RTTI |
264 | | QImageTextureGlyphCache::~QImageTextureGlyphCache() |
265 | 0 | { |
266 | 0 | } |
267 | | |
268 | | void QImageTextureGlyphCache::resizeTextureData(int width, int height) |
269 | 0 | { |
270 | 0 | m_image = m_image.copy(0, 0, width, height); |
271 | | // Regions not part of the copy are initialized to 0, and that is just what |
272 | | // we need. |
273 | 0 | } |
274 | | |
275 | | void QImageTextureGlyphCache::createTextureData(int width, int height) |
276 | 0 | { |
277 | 0 | switch (m_format) { |
278 | 0 | case QFontEngine::Format_Mono: |
279 | 0 | m_image = QImage(width, height, QImage::Format_Mono); |
280 | 0 | break; |
281 | 0 | case QFontEngine::Format_A8: |
282 | 0 | m_image = QImage(width, height, QImage::Format_Alpha8); |
283 | 0 | break; |
284 | 0 | case QFontEngine::Format_A32: |
285 | 0 | m_image = QImage(width, height, QImage::Format_RGB32); |
286 | 0 | break; |
287 | 0 | case QFontEngine::Format_ARGB: |
288 | 0 | m_image = QImage(width, height, QImage::Format_ARGB32_Premultiplied); |
289 | 0 | break; |
290 | 0 | default: |
291 | 0 | Q_UNREACHABLE(); |
292 | 0 | } |
293 | | |
294 | | // Regions not touched by the glyphs must be initialized to 0. (such |
295 | | // locations may in fact be sampled with styled (shifted) text materials) |
296 | | // When resizing, the QImage copy() does this implicitly but the initial |
297 | | // contents must be zeroed out explicitly here. |
298 | 0 | m_image.fill(0); |
299 | 0 | } |
300 | | |
301 | | void QImageTextureGlyphCache::fillTexture(const Coord &c, |
302 | | glyph_t g, |
303 | | const QFixedPoint &subPixelPosition) |
304 | 0 | { |
305 | 0 | QImage mask = textureMapForGlyph(g, subPixelPosition); |
306 | 0 | if (mask.isNull()) |
307 | 0 | return; |
308 | | |
309 | | #ifdef CACHE_DEBUG |
310 | | printf("fillTexture of %dx%d at %d,%d in the cache of %dx%d\n", c.w, c.h, c.x, c.y, m_image.width(), m_image.height()); |
311 | | if (mask.width() > c.w || mask.height() > c.h) { |
312 | | printf(" ERROR; mask is bigger than reserved space! %dx%d instead of %dx%d\n", mask.width(), mask.height(), c.w,c.h); |
313 | | return; |
314 | | } |
315 | | #endif |
316 | 0 | if (m_format == QFontEngine::Format_A32 |
317 | 0 | || m_format == QFontEngine::Format_ARGB) { |
318 | 0 | QImage ref(m_image.bits() + (c.x * 4 + c.y * m_image.bytesPerLine()), |
319 | 0 | qMin(mask.width(), c.w), qMin(mask.height(), c.h), m_image.bytesPerLine(), |
320 | 0 | m_image.format()); |
321 | 0 | QPainter p(&ref); |
322 | 0 | p.setCompositionMode(QPainter::CompositionMode_Source); |
323 | 0 | p.fillRect(0, 0, c.w, c.h, QColor(0,0,0,0)); // TODO optimize this |
324 | 0 | p.drawImage(0, 0, mask); |
325 | 0 | p.end(); |
326 | 0 | } else if (m_format == QFontEngine::Format_Mono) { |
327 | 0 | if (mask.depth() > 1) { |
328 | | // TODO optimize this |
329 | 0 | mask.convertTo(QImage::Format_Alpha8); |
330 | 0 | mask.reinterpretAsFormat(QImage::Format_Grayscale8); |
331 | 0 | mask.invertPixels(); |
332 | 0 | mask.convertTo(QImage::Format_Mono, Qt::ThresholdDither); |
333 | 0 | } |
334 | |
|
335 | 0 | int mw = qMin(mask.width(), c.w); |
336 | 0 | int mh = qMin(mask.height(), c.h); |
337 | 0 | uchar *d = m_image.bits(); |
338 | 0 | qsizetype dbpl = m_image.bytesPerLine(); |
339 | |
|
340 | 0 | for (int y = 0; y < c.h; ++y) { |
341 | 0 | uchar *dest = d + (c.y + y) *dbpl + c.x/8; |
342 | |
|
343 | 0 | if (y < mh) { |
344 | 0 | const uchar *src = mask.constScanLine(y); |
345 | 0 | for (int x = 0; x < c.w/8; ++x) { |
346 | 0 | if (x < (mw+7)/8) |
347 | 0 | dest[x] = src[x]; |
348 | 0 | else |
349 | 0 | dest[x] = 0; |
350 | 0 | } |
351 | 0 | } else { |
352 | 0 | for (int x = 0; x < c.w/8; ++x) |
353 | 0 | dest[x] = 0; |
354 | 0 | } |
355 | 0 | } |
356 | 0 | } else { // A8 |
357 | 0 | int mw = qMin(mask.width(), c.w); |
358 | 0 | int mh = qMin(mask.height(), c.h); |
359 | 0 | uchar *d = m_image.bits(); |
360 | 0 | qsizetype dbpl = m_image.bytesPerLine(); |
361 | |
|
362 | 0 | if (mask.depth() == 1) { |
363 | 0 | for (int y = 0; y < c.h; ++y) { |
364 | 0 | uchar *dest = d + (c.y + y) *dbpl + c.x; |
365 | 0 | if (y < mh) { |
366 | 0 | const uchar *src = mask.constScanLine(y); |
367 | 0 | for (int x = 0; x < c.w; ++x) { |
368 | 0 | if (x < mw) |
369 | 0 | dest[x] = (src[x >> 3] & (1 << (7 - (x & 7)))) > 0 ? 255 : 0; |
370 | 0 | } |
371 | 0 | } |
372 | 0 | } |
373 | 0 | } else if (mask.depth() == 8) { |
374 | 0 | for (int y = 0; y < c.h; ++y) { |
375 | 0 | uchar *dest = d + (c.y + y) *dbpl + c.x; |
376 | 0 | if (y < mh) { |
377 | 0 | const uchar *src = mask.constScanLine(y); |
378 | 0 | for (int x = 0; x < c.w; ++x) { |
379 | 0 | if (x < mw) |
380 | 0 | dest[x] = src[x]; |
381 | 0 | } |
382 | 0 | } |
383 | 0 | } |
384 | 0 | } |
385 | 0 | } |
386 | |
|
387 | | #ifdef CACHE_DEBUG |
388 | | // QPainter p(&m_image); |
389 | | // p.drawLine( |
390 | | int margin = m_current_fontengine ? m_current_fontengine->glyphMargin(m_format) : 0; |
391 | | QPoint base(c.x + margin, c.y + margin + c.baseLineY-1); |
392 | | if (m_image.rect().contains(base)) |
393 | | m_image.setPixel(base, 255); |
394 | | m_image.save(QString::fromLatin1("cache-%1.png").arg(qint64(this))); |
395 | | #endif |
396 | 0 | } |
397 | | |
398 | | QT_END_NAMESPACE |