/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "qtextureglyphcache_p.h" #include "private/qfontengine_p.h" #include "private/qnumeric_p.h" QT_BEGIN_NAMESPACE // #define CACHE_DEBUG // out-of-line to avoid vtable duplication, breaking e.g. RTTI QTextureGlyphCache::~QTextureGlyphCache() { } int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const { // Test 12 different subpixel positions since it factors into 3*4 so it gives // the coverage we need. const int NumSubpixelPositions = 12; QImage images[NumSubpixelPositions]; int numImages = 0; for (int i = 0; i < NumSubpixelPositions; ++i) { QImage img = textureMapForGlyph(glyph, QFixed::fromReal(i / 12.0)); if (numImages == 0) { QPainterPath path; QFixedPoint point; m_current_fontengine->addGlyphsToPath(&glyph, &point, 1, &path, QTextItem::RenderFlags()); // Glyph is space, return 0 to indicate that we need to keep trying if (path.isEmpty()) break; images[numImages++] = std::move(img); } else { bool found = false; for (int j = 0; j < numImages; ++j) { if (images[j] == img) { found = true; break; } } if (!found) images[numImages++] = std::move(img); } } return numImages; } bool QTextureGlyphCache::populate(QFontEngine *fontEngine, int numGlyphs, const glyph_t *glyphs, const QFixedPoint *positions) { #ifdef CACHE_DEBUG printf("Populating with %d glyphs\n", numGlyphs); qDebug() << " -> current transformation: " << m_transform; #endif m_current_fontengine = fontEngine; const int padding = glyphPadding(); const int paddingDoubled = padding * 2; bool supportsSubPixelPositions = fontEngine->supportsSubPixelPositions(); if (fontEngine->m_subPixelPositionCount == 0) { if (!supportsSubPixelPositions) { fontEngine->m_subPixelPositionCount = 1; } else { int i = 0; while (fontEngine->m_subPixelPositionCount == 0 && i < numGlyphs) fontEngine->m_subPixelPositionCount = calculateSubPixelPositionCount(glyphs[i++]); } } if (m_cx == 0 && m_cy == 0) { m_cx = padding; m_cy = padding; } QHash listItemCoordinates; int rowHeight = 0; // check each glyph for its metrics and get the required rowHeight. for (int i=0; i < numGlyphs; ++i) { const glyph_t glyph = glyphs[i]; QFixed subPixelPosition; if (supportsSubPixelPositions) { QFixed x = positions != 0 ? positions[i].x : QFixed(); subPixelPosition = fontEngine->subPixelPositionForX(x); } if (coords.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition))) continue; if (listItemCoordinates.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition))) continue; glyph_metrics_t metrics = fontEngine->alphaMapBoundingBox(glyph, subPixelPosition, m_transform, m_format); #ifdef CACHE_DEBUG printf("(%4x): w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n", glyph, metrics.width.toReal(), metrics.height.toReal(), metrics.xoff.toReal(), metrics.yoff.toReal(), metrics.x.toReal(), metrics.y.toReal()); #endif GlyphAndSubPixelPosition key(glyph, subPixelPosition); int glyph_width = metrics.width.ceil().toInt(); int glyph_height = metrics.height.ceil().toInt(); if (glyph_height == 0 || glyph_width == 0) { // Avoid multiple calls to boundingBox() for non-printable characters Coord c = { 0, 0, 0, 0, 0, 0 }; coords.insert(key, c); continue; } // align to 8-bit boundary if (m_format == QFontEngine::Format_Mono) glyph_width = (glyph_width+7)&~7; Coord c = { 0, 0, // will be filled in later glyph_width, glyph_height, // texture coords metrics.x.truncate(), -metrics.y.truncate() }; // baseline for horizontal scripts listItemCoordinates.insert(key, c); rowHeight = qMax(rowHeight, glyph_height); } if (listItemCoordinates.isEmpty()) return true; rowHeight += paddingDoubled; if (m_w == 0) { if (fontEngine->maxCharWidth() <= QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH) m_w = QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH; else m_w = qNextPowerOfTwo(qCeil(fontEngine->maxCharWidth()) - 1); } // now actually use the coords and paint the wanted glyps into cache. QHash::iterator iter = listItemCoordinates.begin(); int requiredWidth = m_w; while (iter != listItemCoordinates.end()) { Coord c = iter.value(); m_currentRowHeight = qMax(m_currentRowHeight, c.h); if (m_cx + c.w + padding > requiredWidth) { int new_width = requiredWidth*2; while (new_width < m_cx + c.w + padding) new_width *= 2; if (new_width <= maxTextureWidth()) { requiredWidth = new_width; } else { // no room on the current line, start new glyph strip m_cx = padding; m_cy += m_currentRowHeight + paddingDoubled; m_currentRowHeight = c.h; // New row } } if (maxTextureHeight() > 0 && m_cy + c.h + padding > maxTextureHeight()) { // We can't make a cache of the required size, so we bail out return false; } c.x = m_cx; c.y = m_cy; coords.insert(iter.key(), c); m_pendingGlyphs.insert(iter.key(), c); m_cx += c.w + paddingDoubled; ++iter; } return true; } void QTextureGlyphCache::fillInPendingGlyphs() { if (!hasPendingGlyphs()) return; int requiredHeight = m_h; int requiredWidth = m_w; // Use a minimum size to avoid a lot of initial reallocations { QHash::iterator iter = m_pendingGlyphs.begin(); while (iter != m_pendingGlyphs.end()) { Coord c = iter.value(); requiredHeight = qMax(requiredHeight, c.y + c.h); requiredWidth = qMax(requiredWidth, c.x + c.w); ++iter; } } if (isNull() || requiredHeight > m_h || requiredWidth > m_w) { if (isNull()) createCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1)); else resizeCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1)); } beginFillTexture(); { QHash::iterator iter = m_pendingGlyphs.begin(); while (iter != m_pendingGlyphs.end()) { GlyphAndSubPixelPosition key = iter.key(); fillTexture(iter.value(), key.glyph, key.subPixelPosition); ++iter; } } endFillTexture(); m_pendingGlyphs.clear(); } QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, QFixed subPixelPosition) const { switch (m_format) { case QFontEngine::Format_A32: return m_current_fontengine->alphaRGBMapForGlyph(g, subPixelPosition, m_transform); case QFontEngine::Format_ARGB: return m_current_fontengine->bitmapForGlyph(g, subPixelPosition, m_transform); default: return m_current_fontengine->alphaMapForGlyph(g, subPixelPosition, m_transform); } } /************************************************************************ * QImageTextureGlyphCache */ // out-of-line to avoid vtable duplication, breaking e.g. RTTI QImageTextureGlyphCache::~QImageTextureGlyphCache() { } void QImageTextureGlyphCache::resizeTextureData(int width, int height) { m_image = m_image.copy(0, 0, width, height); // Regions not part of the copy are initialized to 0, and that is just what // we need. } void QImageTextureGlyphCache::createTextureData(int width, int height) { switch (m_format) { case QFontEngine::Format_Mono: m_image = QImage(width, height, QImage::Format_Mono); break; case QFontEngine::Format_A8: m_image = QImage(width, height, QImage::Format_Alpha8); break; case QFontEngine::Format_A32: m_image = QImage(width, height, QImage::Format_RGB32); break; case QFontEngine::Format_ARGB: m_image = QImage(width, height, QImage::Format_ARGB32_Premultiplied); break; default: Q_UNREACHABLE(); } // Regions not touched by the glyphs must be initialized to 0. (such // locations may in fact be sampled with styled (shifted) text materials) // When resizing, the QImage copy() does this implicitly but the initial // contents must be zeroed out explicitly here. m_image.fill(0); } void QImageTextureGlyphCache::fillTexture(const Coord &c, glyph_t g, QFixed subPixelPosition) { QImage mask = textureMapForGlyph(g, subPixelPosition); #ifdef CACHE_DEBUG 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()); if (mask.width() > c.w || mask.height() > c.h) { printf(" ERROR; mask is bigger than reserved space! %dx%d instead of %dx%d\n", mask.width(), mask.height(), c.w,c.h); return; } #endif Q_ASSERT(mask.width() <= c.w && mask.height() <= c.h); if (m_format == QFontEngine::Format_A32 || m_format == QFontEngine::Format_ARGB) { QImage ref(m_image.bits() + (c.x * 4 + c.y * m_image.bytesPerLine()), qMin(mask.width(), c.w), qMin(mask.height(), c.h), m_image.bytesPerLine(), m_image.format()); QPainter p(&ref); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(0, 0, c.w, c.h, QColor(0,0,0,0)); // TODO optimize this p.drawImage(0, 0, mask); p.end(); } else if (m_format == QFontEngine::Format_Mono) { if (mask.depth() > 1) { // TODO optimize this mask = mask.alphaChannel(); mask.invertPixels(); mask = mask.convertToFormat(QImage::Format_Mono, Qt::ThresholdDither); } int mw = qMin(mask.width(), c.w); int mh = qMin(mask.height(), c.h); uchar *d = m_image.bits(); int dbpl = m_image.bytesPerLine(); for (int y = 0; y < c.h; ++y) { uchar *dest = d + (c.y + y) *dbpl + c.x/8; if (y < mh) { const uchar *src = mask.constScanLine(y); for (int x = 0; x < c.w/8; ++x) { if (x < (mw+7)/8) dest[x] = src[x]; else dest[x] = 0; } } else { for (int x = 0; x < c.w/8; ++x) dest[x] = 0; } } } else { // A8 int mw = qMin(mask.width(), c.w); int mh = qMin(mask.height(), c.h); uchar *d = m_image.bits(); int dbpl = m_image.bytesPerLine(); if (mask.depth() == 1) { for (int y = 0; y < c.h; ++y) { uchar *dest = d + (c.y + y) *dbpl + c.x; if (y < mh) { const uchar *src = mask.constScanLine(y); for (int x = 0; x < c.w; ++x) { if (x < mw) dest[x] = (src[x >> 3] & (1 << (7 - (x & 7)))) > 0 ? 255 : 0; } } } } else if (mask.depth() == 8) { for (int y = 0; y < c.h; ++y) { uchar *dest = d + (c.y + y) *dbpl + c.x; if (y < mh) { const uchar *src = mask.constScanLine(y); for (int x = 0; x < c.w; ++x) { if (x < mw) dest[x] = src[x]; } } } } } #ifdef CACHE_DEBUG // QPainter p(&m_image); // p.drawLine( int margin = m_current_fontengine ? m_current_fontengine->glyphMargin(m_format) : 0; QPoint base(c.x + margin, c.y + margin + c.baseLineY-1); if (m_image.rect().contains(base)) m_image.setPixel(base, 255); m_image.save(QString::fromLatin1("cache-%1.png").arg(qint64(this))); #endif } QT_END_NAMESPACE