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 
48 QT_BEGIN_NAMESPACE
49 
50 // #define CACHE_DEBUG
51 
52 // out-of-line to avoid vtable duplication, breaking e.g. RTTI
~QTextureGlyphCache()53 QTextureGlyphCache::~QTextureGlyphCache()
54 {
55 }
56 
calculateSubPixelPositionCount(glyph_t glyph) const57 int 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 
populate(QFontEngine * fontEngine,int numGlyphs,const glyph_t * glyphs,const QFixedPoint * positions)95 bool 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 
fillInPendingGlyphs()227 void 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 
textureMapForGlyph(glyph_t g,QFixed subPixelPosition) const266 QImage 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
~QImageTextureGlyphCache()283 QImageTextureGlyphCache::~QImageTextureGlyphCache()
284 {
285 }
286 
resizeTextureData(int width,int height)287 void 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 
createTextureData(int width,int height)294 void 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 
fillTexture(const Coord & c,glyph_t g,QFixed subPixelPosition)320 void 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         int 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         int 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 
415 QT_END_NAMESPACE
416