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

source code of qtbase/src/gui/painting/qtextureglyphcache.cpp