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