diff -r 000000000000 -r 1918ee327afb src/gui/text/qfontengine.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/text/qfontengine.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,1711 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include "qbitmap.h" +#include "qpainter.h" +#include "qpainterpath.h" +#include "qvarlengtharray.h" +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static inline bool qtransform_equals_no_translate(const QTransform &a, const QTransform &b) +{ + if (a.type() <= QTransform::TxTranslate && b.type() <= QTransform::TxTranslate) { + return true; + } else { + // We always use paths for perspective text anyway, so no + // point in checking the full matrix... + Q_ASSERT(a.type() < QTransform::TxProject); + Q_ASSERT(b.type() < QTransform::TxProject); + + return a.m11() == b.m11() + && a.m12() == b.m12() + && a.m21() == b.m21() + && a.m22() == b.m22(); + } +} + +// Harfbuzz helper functions + +static HB_Bool hb_stringToGlyphs(HB_Font font, const HB_UChar16 *string, hb_uint32 length, HB_Glyph *glyphs, hb_uint32 *numGlyphs, HB_Bool rightToLeft) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QVarLengthGlyphLayoutArray qglyphs(*numGlyphs); + + QTextEngine::ShaperFlags shaperFlags(QTextEngine::GlyphIndicesOnly); + if (rightToLeft) + shaperFlags |= QTextEngine::RightToLeft; + + int nGlyphs = *numGlyphs; + bool result = fe->stringToCMap(reinterpret_cast(string), length, &qglyphs, &nGlyphs, shaperFlags); + *numGlyphs = nGlyphs; + if (!result) + return false; + + for (hb_uint32 i = 0; i < *numGlyphs; ++i) + glyphs[i] = qglyphs.glyphs[i]; + + return true; +} + +static void hb_getAdvances(HB_Font font, const HB_Glyph *glyphs, hb_uint32 numGlyphs, HB_Fixed *advances, int flags) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + + QVarLengthGlyphLayoutArray qglyphs(numGlyphs); + + for (hb_uint32 i = 0; i < numGlyphs; ++i) + qglyphs.glyphs[i] = glyphs[i]; + + fe->recalcAdvances(&qglyphs, flags & HB_ShaperFlag_UseDesignMetrics ? QFlags(QTextEngine::DesignMetrics) : QFlags(0)); + + for (hb_uint32 i = 0; i < numGlyphs; ++i) + advances[i] = qglyphs.advances_x[i].value(); +} + +static HB_Bool hb_canRender(HB_Font font, const HB_UChar16 *string, hb_uint32 length) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->canRender(reinterpret_cast(string), length); +} + +static void hb_getGlyphMetrics(HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + glyph_metrics_t m = fe->boundingBox(glyph); + metrics->x = m.x.value(); + metrics->y = m.y.value(); + metrics->width = m.width.value(); + metrics->height = m.height.value(); + metrics->xOffset = m.xoff.value(); + metrics->yOffset = m.yoff.value(); +} + +static HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric) +{ + if (metric == HB_FontAscent) { + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->ascent().value(); + } + return 0; +} + +HB_Error QFontEngine::getPointInOutline(HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + Q_UNUSED(glyph) + Q_UNUSED(flags) + Q_UNUSED(point) + Q_UNUSED(xpos) + Q_UNUSED(ypos) + Q_UNUSED(nPoints) + return HB_Err_Not_Covered; +} + +static HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints) +{ + QFontEngine *fe = (QFontEngine *)font->userData; + return fe->getPointInOutline(glyph, flags, point, xpos, ypos, nPoints); +} + +static const HB_FontClass hb_fontClass = { + hb_stringToGlyphs, hb_getAdvances, hb_canRender, hb_getPointInOutline, + hb_getGlyphMetrics, hb_getFontMetric +}; + +static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length) +{ + QFontEngine *fe = (QFontEngine *)font; + if (!fe->getSfntTableData(tableTag, buffer, length)) + return HB_Err_Invalid_Argument; + return HB_Err_Ok; +} + +// QFontEngine + +QFontEngine::QFontEngine() + : QObject() +{ + ref = 0; + cache_count = 0; + fsType = 0; + symbol = false; + memset(&hbFont, 0, sizeof(hbFont)); + hbFont.klass = &hb_fontClass; + hbFont.userData = this; + + hbFace = 0; + glyphFormat = -1; +} + +QFontEngine::~QFontEngine() +{ + for (GlyphPointerHash::const_iterator it = m_glyphPointerHash.constBegin(), + end = m_glyphPointerHash.constEnd(); it != end; ++it) { + for (QList::const_iterator it2 = it.value().constBegin(), + end2 = it.value().constEnd(); it2 != end2; ++it2) { + delete *it2; + } + } + m_glyphPointerHash.clear(); + for (GlyphIntHash::const_iterator it = m_glyphIntHash.constBegin(), + end = m_glyphIntHash.constEnd(); it != end; ++it) { + for (QList::const_iterator it2 = it.value().constBegin(), + end2 = it.value().constEnd(); it2 != end2; ++it2) { + delete *it2; + } + } + m_glyphIntHash.clear(); + qHBFreeFace(hbFace); +} + +QFixed QFontEngine::lineThickness() const +{ + // ad hoc algorithm + int score = fontDef.weight * fontDef.pixelSize; + int lw = score / 700; + + // looks better with thicker line for small pointsizes + if (lw < 2 && score >= 1050) lw = 2; + if (lw == 0) lw = 1; + + return lw; +} + +QFixed QFontEngine::underlinePosition() const +{ + return ((lineThickness() * 2) + 3) / 6; +} + +HB_Font QFontEngine::harfbuzzFont() const +{ + if (!hbFont.x_ppem) { + QFixed emSquare = emSquareSize(); + hbFont.x_ppem = fontDef.pixelSize; + hbFont.y_ppem = fontDef.pixelSize * fontDef.stretch / 100; + hbFont.x_scale = (QFixed(hbFont.x_ppem * (1 << 16)) / emSquare).value(); + hbFont.y_scale = (QFixed(hbFont.y_ppem * (1 << 16)) / emSquare).value(); + } + return &hbFont; +} + +HB_Face QFontEngine::harfbuzzFace() const +{ + if (!hbFace) { + hbFace = qHBNewFace(const_cast(this), hb_getSFntTable); + Q_CHECK_PTR(hbFace); + } + return hbFace; +} + +glyph_metrics_t QFontEngine::boundingBox(glyph_t glyph, const QTransform &matrix) +{ + glyph_metrics_t metrics = boundingBox(glyph); + + if (matrix.type() > QTransform::TxTranslate) { + return metrics.transformed(matrix); + } + return metrics; +} + +QFixed QFontEngine::xHeight() const +{ + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + QChar x((ushort)'x'); + stringToCMap(&x, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + + glyph_metrics_t bb = const_cast(this)->boundingBox(glyphs.glyphs[0]); + return bb.height; +} + +QFixed QFontEngine::averageCharWidth() const +{ + QGlyphLayoutArray<8> glyphs; + int nglyphs = 7; + QChar x((ushort)'x'); + stringToCMap(&x, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + + glyph_metrics_t bb = const_cast(this)->boundingBox(glyphs.glyphs[0]); + return bb.xoff; +} + + +void QFontEngine::getGlyphPositions(const QGlyphLayout &glyphs, const QTransform &matrix, QTextItem::RenderFlags flags, + QVarLengthArray &glyphs_out, QVarLengthArray &positions) +{ + QFixed xpos; + QFixed ypos; + + const bool transform = matrix.m11() != 1. + || matrix.m12() != 0. + || matrix.m21() != 0. + || matrix.m22() != 1.; + if (!transform) { + xpos = QFixed::fromReal(matrix.dx()); + ypos = QFixed::fromReal(matrix.dy()); + } + + int current = 0; + if (flags & QTextItem::RightToLeft) { + int i = glyphs.numGlyphs; + int totalKashidas = 0; + while(i--) { + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + totalKashidas += glyphs.justifications[i].nKashidas; + } + positions.resize(glyphs.numGlyphs+totalKashidas); + glyphs_out.resize(glyphs.numGlyphs+totalKashidas); + + i = 0; + while(i < glyphs.numGlyphs) { + if (glyphs.attributes[i].dontPrint) { + ++i; + continue; + } + xpos -= glyphs.advances_x[i]; + ypos -= glyphs.advances_y[i]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = glyphs.glyphs[i]; + ++current; + if (glyphs.justifications[i].nKashidas) { + QChar ch(0x640); // Kashida character + QGlyphLayoutArray<8> g; + int nglyphs = 7; + stringToCMap(&ch, 1, &g, &nglyphs, 0); + for (uint k = 0; k < glyphs.justifications[i].nKashidas; ++k) { + xpos -= g.advances_x[0]; + ypos -= g.advances_y[0]; + + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + if (transform) { + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + gpos_x = QFixed::fromReal(gpos.x()); + gpos_y = QFixed::fromReal(gpos.y()); + } + positions[current].x = gpos_x; + positions[current].y = gpos_y; + glyphs_out[current] = g.glyphs[0]; + ++current; + } + } else { + xpos -= QFixed::fromFixed(glyphs.justifications[i].space_18d6); + } + ++i; + } + } else { + positions.resize(glyphs.numGlyphs); + glyphs_out.resize(glyphs.numGlyphs); + int i = 0; + if (!transform) { + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + positions[current].x = xpos + glyphs.offsets[i].x; + positions[current].y = ypos + glyphs.offsets[i].y; + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + ++current; + } + ++i; + } + } else { + positions.resize(glyphs.numGlyphs); + glyphs_out.resize(glyphs.numGlyphs); + int i = 0; + while (i < glyphs.numGlyphs) { + if (!glyphs.attributes[i].dontPrint) { + QFixed gpos_x = xpos + glyphs.offsets[i].x; + QFixed gpos_y = ypos + glyphs.offsets[i].y; + QPointF gpos(gpos_x.toReal(), gpos_y.toReal()); + gpos = gpos * matrix; + positions[current].x = QFixed::fromReal(gpos.x()); + positions[current].y = QFixed::fromReal(gpos.y()); + glyphs_out[current] = glyphs.glyphs[i]; + xpos += glyphs.advances_x[i] + QFixed::fromFixed(glyphs.justifications[i].space_18d6); + ypos += glyphs.advances_y[i]; + ++current; + } + ++i; + } + } + } + positions.resize(current); + glyphs_out.resize(current); + Q_ASSERT(positions.size() == glyphs_out.size()); +} + + +glyph_metrics_t QFontEngine::tightBoundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + + QFixed ymax = 0; + QFixed xmax = 0; + for (int i = 0; i < glyphs.numGlyphs; i++) { + glyph_metrics_t bb = boundingBox(glyphs.glyphs[i]); + QFixed x = overall.xoff + glyphs.offsets[i].x + bb.x; + QFixed y = overall.yoff + glyphs.offsets[i].y + bb.y; + overall.x = qMin(overall.x, x); + overall.y = qMin(overall.y, y); + xmax = qMax(xmax, x + bb.width); + ymax = qMax(ymax, y + bb.height); + overall.xoff += bb.xoff; + overall.yoff += bb.yoff; + } + overall.height = qMax(overall.height, ymax - overall.y); + overall.width = xmax - overall.x; + + return overall; +} + + +void QFontEngine::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, + QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + addGlyphsToPath(positioned_glyphs.data(), positions.data(), positioned_glyphs.size(), path, flags); +} + +#define GRID(x, y) grid[(y)*(w+1) + (x)] +#define SET(x, y) (*(image_data + (y)*bpl + ((x) >> 3)) & (0x80 >> ((x) & 7))) + +enum { EdgeRight = 0x1, + EdgeDown = 0x2, + EdgeLeft = 0x4, + EdgeUp = 0x8 +}; + +static void collectSingleContour(qreal x0, qreal y0, uint *grid, int x, int y, int w, int h, QPainterPath *path) +{ + Q_UNUSED(h); + + path->moveTo(x + x0, y + y0); + while (GRID(x, y)) { + if (GRID(x, y) & EdgeRight) { + while (GRID(x, y) & EdgeRight) { + GRID(x, y) &= ~EdgeRight; + ++x; + } + Q_ASSERT(x <= w); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeDown) { + while (GRID(x, y) & EdgeDown) { + GRID(x, y) &= ~EdgeDown; + ++y; + } + Q_ASSERT(y <= h); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeLeft) { + while (GRID(x, y) & EdgeLeft) { + GRID(x, y) &= ~EdgeLeft; + --x; + } + Q_ASSERT(x >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + if (GRID(x, y) & EdgeUp) { + while (GRID(x, y) & EdgeUp) { + GRID(x, y) &= ~EdgeUp; + --y; + } + Q_ASSERT(y >= 0); + path->lineTo(x + x0, y + y0); + continue; + } + } + path->closeSubpath(); +} + +void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path) +{ + uint *grid = new uint[(w+1)*(h+1)]; + // set up edges + for (int y = 0; y <= h; ++y) { + for (int x = 0; x <= w; ++x) { + bool topLeft = (x == 0)|(y == 0) ? false : SET(x - 1, y - 1); + bool topRight = (x == w)|(y == 0) ? false : SET(x, y - 1); + bool bottomLeft = (x == 0)|(y == h) ? false : SET(x - 1, y); + bool bottomRight = (x == w)|(y == h) ? false : SET(x, y); + + GRID(x, y) = 0; + if ((!topRight) & bottomRight) + GRID(x, y) |= EdgeRight; + if ((!bottomRight) & bottomLeft) + GRID(x, y) |= EdgeDown; + if ((!bottomLeft) & topLeft) + GRID(x, y) |= EdgeLeft; + if ((!topLeft) & topRight) + GRID(x, y) |= EdgeUp; + } + } + + // collect edges + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + if (!GRID(x, y)) + continue; + // found start of a contour, follow it + collectSingleContour(x0, y0, grid, x, y, w, h, path); + } + } + delete [] grid; +} + +#undef GRID +#undef SET + + +void QFontEngine::addBitmapFontToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ +// TODO what to do with 'flags' ?? + Q_UNUSED(flags); + QFixed advanceX = QFixed::fromReal(x); + QFixed advanceY = QFixed::fromReal(y); + for (int i=0; i < glyphs.numGlyphs; ++i) { + glyph_metrics_t metrics = boundingBox(glyphs.glyphs[i]); + if (metrics.width.value() == 0 || metrics.height.value() == 0) { + advanceX += glyphs.advances_x[i]; + advanceY += glyphs.advances_y[i]; + continue; + } + const QImage alphaMask = alphaMapForGlyph(glyphs.glyphs[i]); + + const int w = alphaMask.width(); + const int h = alphaMask.height(); + const int srcBpl = alphaMask.bytesPerLine(); + QImage bitmap; + if (alphaMask.depth() == 1) { + bitmap = alphaMask; + } else { + bitmap = QImage(w, h, QImage::Format_Mono); + const uchar *imageData = alphaMask.bits(); + const int destBpl = bitmap.bytesPerLine(); + uchar *bitmapData = bitmap.bits(); + + for (int yi = 0; yi < h; ++yi) { + const uchar *src = imageData + yi*srcBpl; + uchar *dst = bitmapData + yi*destBpl; + for (int xi = 0; xi < w; ++xi) { + const int byte = xi / 8; + const int bit = xi % 8; + if (bit == 0) + dst[byte] = 0; + if (src[xi]) + dst[byte] |= 128 >> bit; + } + } + } + const uchar *bitmap_data = bitmap.bits(); + QFixedPoint offset = glyphs.offsets[i]; + advanceX += offset.x; + advanceY += offset.y; + qt_addBitmapToPath((advanceX + metrics.x).toReal(), (advanceY + metrics.y).toReal(), bitmap_data, bitmap.bytesPerLine(), w, h, path); + advanceX += glyphs.advances_x[i]; + advanceY += glyphs.advances_y[i]; + } +} + +void QFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + qreal x = positions[0].x.toReal(); + qreal y = positions[0].y.toReal(); + QVarLengthGlyphLayoutArray g(nGlyphs); + + for (int i = 0; i < nGlyphs; ++i) { + g.glyphs[i] = glyphs[i]; + if (i < nGlyphs - 1) { + g.advances_x[i] = positions[i+1].x - positions[i].x; + g.advances_y[i] = positions[i+1].y - positions[i].y; + } else { + g.advances_x[i] = QFixed::fromReal(maxCharWidth()); + g.advances_y[i] = 0; + } + } + + addBitmapFontToPath(x, y, g, path, flags); +} + +QImage QFontEngine::alphaMapForGlyph(glyph_t glyph, const QTransform &t) +{ + QImage i = alphaMapForGlyph(glyph); + if (t.type() > QTransform::TxTranslate) + i = i.transformed(t); + Q_ASSERT(i.depth() <= 8); // To verify that transformed didn't change the format... + return i; +} + +QImage QFontEngine::alphaRGBMapForGlyph(glyph_t glyph, int /* margin */, const QTransform &t) +{ + QImage alphaMask = alphaMapForGlyph(glyph, t); + QImage rgbMask(alphaMask.width(), alphaMask.height(), QImage::Format_RGB32); + + for (int y=0; y colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y(table.data()), &len)) + return QByteArray(); + return table; +} + +void QFontEngine::expireGlyphCache() +{ + if (m_glyphCacheQueue.count() > 10) { // hold only 10 caches in memory. + QFontEngineGlyphCache *old = m_glyphCacheQueue.takeFirst(); + // remove the value from either of our hashes + for (GlyphPointerHash::iterator i = m_glyphPointerHash.begin(); i != m_glyphPointerHash.end(); ++i) { + QList list = i.value(); + if (list.removeAll(old)) { + if (list.isEmpty()) + m_glyphPointerHash.remove(i.key()); + else + m_glyphPointerHash.insert(i.key(), list); + break; + } + } + for (GlyphIntHash::iterator i = m_glyphIntHash.begin(); i != m_glyphIntHash.end(); ++i) { + QList list = i.value(); + if (list.removeAll(old)) { + if (list.isEmpty()) + m_glyphIntHash.remove(i.key()); + else + m_glyphIntHash.insert(i.key(), list); + break; + } + } + delete old; + } +} + +void QFontEngine::setGlyphCache(void *key, QFontEngineGlyphCache *data) +{ + Q_ASSERT(data); + QList items = m_glyphPointerHash.value(key); + + for (QList::iterator it = items.begin(), end = items.end(); it != end; ++it) { + QFontEngineGlyphCache *c = *it; + if (qtransform_equals_no_translate(c->m_transform, data->m_transform)) { + if (c == data) + return; + items.removeAll(c); + delete c; + break; + } + } + items.append(data); + m_glyphPointerHash.insert(key, items); + + m_glyphCacheQueue.append(data); + expireGlyphCache(); +} + +void QFontEngine::setGlyphCache(QFontEngineGlyphCache::Type key, QFontEngineGlyphCache *data) +{ + Q_ASSERT(data); + QList items = m_glyphIntHash.value(key); + + for (QList::iterator it = items.begin(), end = items.end(); it != end; ++it) { + QFontEngineGlyphCache *c = *it; + if (qtransform_equals_no_translate(c->m_transform, data->m_transform)) { + if (c == data) + return; + items.removeAll(c); + delete c; + break; + } + } + items.append(data); + m_glyphIntHash.insert(key, items); + + m_glyphCacheQueue.append(data); + expireGlyphCache(); +} + +QFontEngineGlyphCache *QFontEngine::glyphCache(void *key, const QTransform &transform) const +{ + QList items = m_glyphPointerHash.value(key); + + for (QList::iterator it = items.begin(), end = items.end(); it != end; ++it) { + QFontEngineGlyphCache *c = *it; + if (qtransform_equals_no_translate(c->m_transform, transform)) { + m_glyphCacheQueue.removeAll(c); // last used, move it up + m_glyphCacheQueue.append(c); + return c; + } + } + return 0; +} + +QFontEngineGlyphCache *QFontEngine::glyphCache(QFontEngineGlyphCache::Type key, const QTransform &transform) const +{ + QList items = m_glyphIntHash.value(key); + + for (QList::iterator it = items.begin(), end = items.end(); it != end; ++it) { + QFontEngineGlyphCache *c = *it; + if (qtransform_equals_no_translate(c->m_transform, transform)) { + m_glyphCacheQueue.removeAll(c); // last used, move it up + m_glyphCacheQueue.append(c); + return c; + } + } + return 0; +} + +#if defined(Q_WS_WIN) || defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN) +static inline QFixed kerning(int left, int right, const QFontEngine::KernPair *pairs, int numPairs) +{ + uint left_right = (left << 16) + right; + + left = 0, right = numPairs - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + if(pairs[middle].left_right == left_right) + return pairs[middle].adjust; + + if (pairs[middle].left_right < left_right) + left = middle + 1; + else + right = middle - 1; + } + return 0; +} + +void QFontEngine::doKerning(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + int numPairs = kerning_pairs.size(); + if(!numPairs) + return; + + const KernPair *pairs = kerning_pairs.constData(); + + if(flags & QTextEngine::DesignMetrics) { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances_x[i] += kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs); + } else { + for(int i = 0; i < glyphs->numGlyphs - 1; ++i) + glyphs->advances_x[i] += qRound(kerning(glyphs->glyphs[i], glyphs->glyphs[i+1] , pairs, numPairs)); + } +} + +void QFontEngine::loadKerningPairs(QFixed scalingFactor) +{ + kerning_pairs.clear(); + + QByteArray tab = getSfntTable(MAKE_TAG('k', 'e', 'r', 'n')); + if (tab.isEmpty()) + return; + + const uchar *table = reinterpret_cast(tab.constData()); + + unsigned short version = qFromBigEndian(table); + if (version != 0) { +// qDebug("wrong version"); + return; + } + + unsigned short numTables = qFromBigEndian(table + 2); + { + int offset = 4; + for(int i = 0; i < numTables; ++i) { + if (offset + 6 > tab.size()) { +// qDebug("offset out of bounds"); + goto end; + } + const uchar *header = table + offset; + + ushort version = qFromBigEndian(header); + ushort length = qFromBigEndian(header+2); + ushort coverage = qFromBigEndian(header+4); +// qDebug("subtable: version=%d, coverage=%x",version, coverage); + if(version == 0 && coverage == 0x0001) { + if (offset + length > tab.size()) { +// qDebug("length ouf ot bounds"); + goto end; + } + const uchar *data = table + offset + 6; + + ushort nPairs = qFromBigEndian(data); + if(nPairs * 6 + 8 > length - 6) { +// qDebug("corrupt table!"); + // corrupt table + goto end; + } + + int off = 8; + for(int i = 0; i < nPairs; ++i) { + QFontEngine::KernPair p; + p.left_right = (((uint)qFromBigEndian(data+off)) << 16) + qFromBigEndian(data+off+2); + p.adjust = QFixed(((int)(short)qFromBigEndian(data+off+4))) / scalingFactor; + kerning_pairs.append(p); + off += 6; + } + } + offset += length; + } + } +end: + qSort(kerning_pairs); +// for (int i = 0; i < kerning_pairs.count(); ++i) +// qDebug() << 'i' << i << "left_right" << hex << kerning_pairs.at(i).left_right; +} + +#else +void QFontEngine::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const +{ +} +#endif + +int QFontEngine::glyphCount() const +{ + QByteArray maxpTable = getSfntTable(MAKE_TAG('m', 'a', 'x', 'p')); + if (maxpTable.size() < 6) + return 0; + return qFromBigEndian(reinterpret_cast(maxpTable.constData() + 4)); +} + +const uchar *QFontEngine::getCMap(const uchar *table, uint tableSize, bool *isSymbolFont, int *cmapSize) +{ + const uchar *header = table; + if (tableSize < 4) + return 0; + + const uchar *endPtr = table + tableSize; + + // version check + if (qFromBigEndian(header) != 0) + return 0; + + unsigned short numTables = qFromBigEndian(header + 2); + const uchar *maps = table + 4; + if (maps + 8 * numTables > endPtr) + return 0; + + enum { + Invalid, + Symbol, + AppleRoman, + Unicode11, + Unicode, + MicrosoftUnicode, + MicrosoftUnicodeExtended + }; + + int symbolTable = -1; + int tableToUse = -1; + int score = Invalid; + for (int n = 0; n < numTables; ++n) { + const quint16 platformId = qFromBigEndian(maps + 8 * n); + const quint16 platformSpecificId = qFromBigEndian(maps + 8 * n + 2); + switch (platformId) { + case 0: // Unicode + if (score < Unicode && + (platformSpecificId == 0 || + platformSpecificId == 2 || + platformSpecificId == 3)) { + tableToUse = n; + score = Unicode; + } else if (score < Unicode11 && platformSpecificId == 1) { + tableToUse = n; + score = Unicode11; + } + break; + case 1: // Apple + if (score < AppleRoman && platformSpecificId == 0) { // Apple Roman + tableToUse = n; + score = AppleRoman; + } + break; + case 3: // Microsoft + switch (platformSpecificId) { + case 0: + symbolTable = n; + if (score < Symbol) { + tableToUse = n; + score = Symbol; + } + break; + case 1: + if (score < MicrosoftUnicode) { + tableToUse = n; + score = MicrosoftUnicode; + } + break; + case 0xa: + if (score < MicrosoftUnicodeExtended) { + tableToUse = n; + score = MicrosoftUnicodeExtended; + } + break; + default: + break; + } + default: + break; + } + } + if(tableToUse < 0) + return 0; + +resolveTable: + *isSymbolFont = (score == Symbol); + + unsigned int unicode_table = qFromBigEndian(maps + 8*tableToUse + 4); + + if (!unicode_table || unicode_table + 8 > tableSize) + return 0; + + // get the header of the unicode table + header = table + unicode_table; + + unsigned short format = qFromBigEndian(header); + unsigned int length; + if(format < 8) + length = qFromBigEndian(header + 2); + else + length = qFromBigEndian(header + 4); + + if (table + unicode_table + length > endPtr) + return 0; + *cmapSize = length; + + // To support symbol fonts that contain a unicode table for the symbol area + // we check the cmap tables and fall back to symbol font unless that would + // involve losing information from the unicode table + if (symbolTable > -1 && ((score == Unicode) || (score == Unicode11))) { + const uchar *selectedTable = table + unicode_table; + + // Check that none of the latin1 range are in the unicode table + bool unicodeTableHasLatin1 = false; + for (int uc=0x00; uc<0x100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, uc) != 0) { + unicodeTableHasLatin1 = true; + break; + } + } + + // Check that at least one symbol char is in the unicode table + bool unicodeTableHasSymbols = false; + if (!unicodeTableHasLatin1) { + for (int uc=0xf000; uc<0xf100; ++uc) { + if (getTrueTypeGlyphIndex(selectedTable, uc) != 0) { + unicodeTableHasSymbols = true; + break; + } + } + } + + // Fall back to symbol table + if (!unicodeTableHasLatin1 && unicodeTableHasSymbols) { + tableToUse = symbolTable; + score = Symbol; + goto resolveTable; + } + } + + return table + unicode_table; +} + +quint32 QFontEngine::getTrueTypeGlyphIndex(const uchar *cmap, uint unicode) +{ + unsigned short format = qFromBigEndian(cmap); + if (format == 0) { + if (unicode < 256) + return (int) *(cmap+6+unicode); + } else if (format == 4) { + /* some fonts come with invalid cmap tables, where the last segment + specified end = start = rangeoffset = 0xffff, delta = 0x0001 + Since 0xffff is never a valid Unicode char anyway, we just get rid of the issue + by returning 0 for 0xffff + */ + if(unicode >= 0xffff) + return 0; + quint16 segCountX2 = qFromBigEndian(cmap + 6); + const unsigned char *ends = cmap + 14; + int i = 0; + for (; i < segCountX2/2 && qFromBigEndian(ends + 2*i) < unicode; i++) {} + + const unsigned char *idx = ends + segCountX2 + 2 + 2*i; + quint16 startIndex = qFromBigEndian(idx); + + if (startIndex > unicode) + return 0; + + idx += segCountX2; + qint16 idDelta = (qint16)qFromBigEndian(idx); + idx += segCountX2; + quint16 idRangeoffset_t = (quint16)qFromBigEndian(idx); + + quint16 glyphIndex; + if (idRangeoffset_t) { + quint16 id = qFromBigEndian(idRangeoffset_t + 2*(unicode - startIndex) + idx); + if (id) + glyphIndex = (idDelta + id) % 0x10000; + else + glyphIndex = 0; + } else { + glyphIndex = (idDelta + unicode) % 0x10000; + } + return glyphIndex; + } else if (format == 6) { + quint16 tableSize = qFromBigEndian(cmap + 2); + + quint16 firstCode6 = qFromBigEndian(cmap + 6); + if (unicode < firstCode6) + return 0; + + quint16 entryCount6 = qFromBigEndian(cmap + 8); + if (entryCount6 * 2 + 10 > tableSize) + return 0; + + quint16 sentinel6 = firstCode6 + entryCount6; + if (unicode >= sentinel6) + return 0; + + quint16 entryIndex6 = unicode - firstCode6; + return qFromBigEndian(cmap + 10 + (entryIndex6 * 2)); + } else if (format == 12) { + quint32 nGroups = qFromBigEndian(cmap + 12); + + cmap += 16; // move to start of groups + + int left = 0, right = nGroups - 1; + while (left <= right) { + int middle = left + ( ( right - left ) >> 1 ); + + quint32 startCharCode = qFromBigEndian(cmap + 12*middle); + if(unicode < startCharCode) + right = middle - 1; + else { + quint32 endCharCode = qFromBigEndian(cmap + 12*middle + 4); + if(unicode <= endCharCode) + return qFromBigEndian(cmap + 12*middle + 8) + unicode - startCharCode; + left = middle + 1; + } + } + } else { + qDebug("cmap table of format %d not implemented", format); + } + + return 0; +} + +Q_GLOBAL_STATIC_WITH_INITIALIZER(QVector, qt_grayPalette, { + x->resize(256); + QRgb *it = x->data(); + for (int i = 0; i < x->size(); ++i, ++it) + *it = 0xff000000 | i | (i<<8) | (i<<16); +}) + +const QVector &QFontEngine::grayPalette() +{ + return *qt_grayPalette(); +} + +// ------------------------------------------------------------------ +// The box font engine +// ------------------------------------------------------------------ + +QFontEngineBox::QFontEngineBox(int size) + : _size(size) +{ + cache_cost = sizeof(QFontEngineBox); +} + +QFontEngineBox::~QFontEngineBox() +{ +} + +bool QFontEngineBox::stringToCMap(const QChar *, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags) const +{ + if (*nglyphs < len) { + *nglyphs = len; + return false; + } + + for (int i = 0; i < len; i++) { + glyphs->glyphs[i] = 0; + glyphs->advances_x[i] = _size; + glyphs->advances_y[i] = 0; + } + + *nglyphs = len; + glyphs->numGlyphs = len; + return true; +} + +void QFontEngineBox::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags) const +{ + for (int i = 0; i < glyphs->numGlyphs; i++) { + glyphs->advances_x[i] = _size; + glyphs->advances_y[i] = 0; + } +} + +void QFontEngineBox::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (!glyphs.numGlyphs) + return; + + QVarLengthArray positions; + QVarLengthArray positioned_glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions); + + QSize s(_size - 3, _size - 3); + for (int k = 0; k < positions.size(); k++) + path->addRect(QRectF(positions[k].toPointF(), s)); +} + +glyph_metrics_t QFontEngineBox::boundingBox(const QGlyphLayout &glyphs) +{ + glyph_metrics_t overall; + overall.width = _size*glyphs.numGlyphs; + overall.height = _size; + overall.xoff = overall.width; + return overall; +} + +#if defined(Q_WS_QWS) +void QFontEngineBox::draw(QPaintEngine *p, qreal x, qreal y, const QTextItemInt &ti) +{ + if (!ti.glyphs.numGlyphs) + return; + + // any fixes here should probably also be done in QPaintEnginePrivate::drawBoxTextItem + QSize s(_size - 3, _size - 3); + + QVarLengthArray positions; + QVarLengthArray glyphs; + QTransform matrix = QTransform::fromTranslate(x, y - _size); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + + QPainter *painter = p->painter(); + painter->save(); + painter->setBrush(Qt::NoBrush); + QPen pen = painter->pen(); + pen.setWidthF(lineThickness().toReal()); + painter->setPen(pen); + for (int k = 0; k < positions.size(); k++) + painter->drawRect(QRectF(positions[k].toPointF(), s)); + painter->restore(); +} +#endif + +glyph_metrics_t QFontEngineBox::boundingBox(glyph_t) +{ + return glyph_metrics_t(0, -_size, _size, _size, _size, 0); +} + + + +QFixed QFontEngineBox::ascent() const +{ + return _size; +} + +QFixed QFontEngineBox::descent() const +{ + return 0; +} + +QFixed QFontEngineBox::leading() const +{ + QFixed l = _size * QFixed::fromReal(qreal(0.15)); + return l.ceil(); +} + +qreal QFontEngineBox::maxCharWidth() const +{ + return _size; +} + +#ifdef Q_WS_X11 +int QFontEngineBox::cmap() const +{ + return -1; +} +#endif + +const char *QFontEngineBox::name() const +{ + return "null"; +} + +bool QFontEngineBox::canRender(const QChar *, int) +{ + return true; +} + +QFontEngine::Type QFontEngineBox::type() const +{ + return Box; +} + +QImage QFontEngineBox::alphaMapForGlyph(glyph_t) +{ + QImage image(_size, _size, QImage::Format_Indexed8); + QVector colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + image.setColorTable(colors); + image.fill(0); + + // can't use qpainter for index8; so use setPixel to draw our rectangle. + for (int i=2; i <= _size-3; ++i) { + image.setPixel(i, 2, 255); + image.setPixel(i, _size-3, 255); + image.setPixel(2, i, 255); + image.setPixel(_size-3, i, 255); + } + return image; +} + +// ------------------------------------------------------------------ +// Multi engine +// ------------------------------------------------------------------ + +static inline uchar highByte(glyph_t glyph) +{ return glyph >> 24; } + +// strip high byte from glyph +static inline glyph_t stripped(glyph_t glyph) +{ return glyph & 0x00ffffff; } + +QFontEngineMulti::QFontEngineMulti(int engineCount) +{ + engines.fill(0, engineCount); + cache_cost = 0; +} + +QFontEngineMulti::~QFontEngineMulti() +{ + for (int i = 0; i < engines.size(); ++i) { + QFontEngine *fontEngine = engines.at(i); + if (fontEngine) { + fontEngine->ref.deref(); + if (fontEngine->cache_count == 0 && fontEngine->ref == 0) + delete fontEngine; + } + } +} + +bool QFontEngineMulti::stringToCMap(const QChar *str, int len, + QGlyphLayout *glyphs, int *nglyphs, + QTextEngine::ShaperFlags flags) const +{ + int ng = *nglyphs; + if (!engine(0)->stringToCMap(str, len, glyphs, &ng, flags)) + return false; + + int glyph_pos = 0; + for (int i = 0; i < len; ++i) { + bool surrogate = (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 && i < len-1 + && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); + if (glyphs->glyphs[glyph_pos] == 0) { + + QGlyphLayoutInstance tmp = glyphs->instance(glyph_pos); + for (int x = 1; x < engines.size(); ++x) { + QFontEngine *engine = engines.at(x); + if (!engine) { + const_cast(this)->loadEngine(x); + engine = engines.at(x); + } + Q_ASSERT(engine != 0); + if (engine->type() == Box) + continue; + glyphs->advances_x[glyph_pos] = glyphs->advances_y[glyph_pos] = 0; + glyphs->offsets[glyph_pos] = QFixedPoint(); + int num = 2; + QGlyphLayout offs = glyphs->mid(glyph_pos, num); + engine->stringToCMap(str + i, surrogate ? 2 : 1, &offs, &num, flags); + Q_ASSERT(num == 1); // surrogates only give 1 glyph + if (glyphs->glyphs[glyph_pos]) { + // set the high byte to indicate which engine the glyph came from + glyphs->glyphs[glyph_pos] |= (x << 24); + break; + } + } + // ensure we use metrics from the 1st font when we use the fallback image. + if (!glyphs->glyphs[glyph_pos]) { + glyphs->setInstance(glyph_pos, tmp); + } + } + if (surrogate) + ++i; + ++glyph_pos; + } + + *nglyphs = ng; + glyphs->numGlyphs = ng; + return true; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(const QGlyphLayout &glyphs) +{ + if (glyphs.numGlyphs <= 0) + return glyph_metrics_t(); + + glyph_metrics_t overall; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + // merge the bounding box for this run + const glyph_metrics_t gm = engine(which)->boundingBox(glyphs.mid(start, end - start)); + + overall.x = qMin(overall.x, gm.x); + overall.y = qMin(overall.y, gm.y); + overall.width = overall.xoff + gm.width; + overall.height = qMax(overall.height + overall.y, gm.height + gm.y) - + qMin(overall.y, gm.y); + overall.xoff += gm.xoff; + overall.yoff += gm.yoff; + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + return overall; +} + +void QFontEngineMulti::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, + QPainterPath *path, QTextItem::RenderFlags flags) +{ + if (glyphs.numGlyphs <= 0) + return; + + int which = highByte(glyphs.glyphs[0]); + int start = 0; + int end, i; + if (flags & QTextItem::RightToLeft) { + for (int gl = 0; gl < glyphs.numGlyphs; gl++) { + x += glyphs.advances_x[gl].toReal(); + y += glyphs.advances_y[gl].toReal(); + } + } + for (end = 0; end < glyphs.numGlyphs; ++end) { + const int e = highByte(glyphs.glyphs[end]); + if (e == which) + continue; + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) { + x -= glyphs.advances_x[i].toReal(); + y -= glyphs.advances_y[i].toReal(); + } + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; + + if (!(flags & QTextItem::RightToLeft)) { + for (i = start; i < end; ++i) { + x += glyphs.advances_x[i].toReal(); + y += glyphs.advances_y[i].toReal(); + } + } + + // change engine + start = end; + which = e; + } + + if (flags & QTextItem::RightToLeft) { + for (i = start; i < end; ++i) { + x -= glyphs.advances_x[i].toReal(); + y -= glyphs.advances_y[i].toReal(); + } + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs.glyphs[i] = stripped(glyphs.glyphs[i]); + + engine(which)->addOutlineToPath(x, y, glyphs.mid(start, end - start), path, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs.glyphs[i] = hi | glyphs.glyphs[i]; +} + +void QFontEngineMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->recalcAdvances(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +void QFontEngineMulti::doKerning(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + if (glyphs->numGlyphs <= 0) + return; + + int which = highByte(glyphs->glyphs[0]); + int start = 0; + int end, i; + for (end = 0; end < glyphs->numGlyphs; ++end) { + const int e = highByte(glyphs->glyphs[end]); + if (e == which) + continue; + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs and update x and y + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; + + // change engine + start = end; + which = e; + } + + // set the high byte to zero + for (i = start; i < end; ++i) + glyphs->glyphs[i] = stripped(glyphs->glyphs[i]); + + QGlyphLayout offs = glyphs->mid(start, end - start); + engine(which)->doKerning(&offs, flags); + + // reset the high byte for all glyphs + const int hi = which << 24; + for (i = start; i < end; ++i) + glyphs->glyphs[i] = hi | glyphs->glyphs[i]; +} + +glyph_metrics_t QFontEngineMulti::boundingBox(glyph_t glyph) +{ + const int which = highByte(glyph); + Q_ASSERT(which < engines.size()); + return engine(which)->boundingBox(stripped(glyph)); +} + +QFixed QFontEngineMulti::ascent() const +{ return engine(0)->ascent(); } + +QFixed QFontEngineMulti::descent() const +{ return engine(0)->descent(); } + +QFixed QFontEngineMulti::leading() const +{ + return engine(0)->leading(); +} + +QFixed QFontEngineMulti::xHeight() const +{ + return engine(0)->xHeight(); +} + +QFixed QFontEngineMulti::averageCharWidth() const +{ + return engine(0)->averageCharWidth(); +} + +QFixed QFontEngineMulti::lineThickness() const +{ + return engine(0)->lineThickness(); +} + +QFixed QFontEngineMulti::underlinePosition() const +{ + return engine(0)->underlinePosition(); +} + +qreal QFontEngineMulti::maxCharWidth() const +{ + return engine(0)->maxCharWidth(); +} + +qreal QFontEngineMulti::minLeftBearing() const +{ + return engine(0)->minLeftBearing(); +} + +qreal QFontEngineMulti::minRightBearing() const +{ + return engine(0)->minRightBearing(); +} + +bool QFontEngineMulti::canRender(const QChar *string, int len) +{ + if (engine(0)->canRender(string, len)) + return true; + + QVarLengthGlyphLayoutArray glyphs(len); + int nglyphs = len; + if (stringToCMap(string, len, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly) == false) { + glyphs.resize(nglyphs); + stringToCMap(string, len, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly); + } + + bool allExist = true; + for (int i = 0; i < nglyphs; i++) { + if (!glyphs.glyphs[i]) { + allExist = false; + break; + } + } + + return allExist; +} + +QImage QFontEngineMulti::alphaMapForGlyph(glyph_t) +{ + Q_ASSERT(false); + return QImage(); +} + + +QT_END_NAMESPACE