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