}
}
+glyph_metrics_t QFontEngineBox::boundingBox(const QGlyphLayout &glyphs) const
+{
+ glyph_metrics_t overall;
+ overall.width = _size*glyphs.numGlyphs;
+ overall.height = _size;
+ overall.xoff = overall.width;
+ return overall;
+}
+
glyph_metrics_t QFontEngineBox::boundingBox(glyph_t) const
{
return glyph_metrics_t(0, -_size, _size, _size, _size);
}
}
+glyph_metrics_t QFontEngineFT::boundingBox(const QGlyphLayout &glyphs) const
+{
+ glyph_metrics_t overall;
+ // initialize with line height, we get the same behaviour on all platforms
+ overall.y = -ascent();
+ overall.height = ascent() + descent() + 1;
+
+ QFixed ymax = 0;
+ QFixed xmax = 0;
+ for (int i = 0; i < glyphs.numGlyphs; i++) {
+ QFontMetric* metric = getMetrics(glyphs.glyphs[i]);
+ Q_ASSERT(metric);
+
+ QFixed x = overall.xoff - (-TRUNC(metric->left));
+ QFixed y = TRUNC(metric->top);
+ overall.x = qMin(overall.x, x);
+ overall.y = qMin(overall.y, y);
+ xmax = qMax(xmax, x + TRUNC(metric->right - metric->left));
+ ymax = qMax(ymax, y + TRUNC(metric->top - metric->bottom));
+ overall.xoff += qRound(TRUNC(metric->advancex));
+
+ }
+ overall.height = qMax(overall.height, ymax - overall.y);
+ overall.width = xmax - overall.x;
+
+ return overall;
+}
+
glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph) const
{
glyph_metrics_t overall;
virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
QTextEngine::ShaperFlags flags) const;
+ virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) const;
virtual glyph_metrics_t boundingBox(glyph_t glyph) const;
virtual void recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const;
virtual void addOutlineToPath(qreal, qreal, const QGlyphLayout &, QPainterPath *);
+ virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) const = 0;
virtual glyph_metrics_t boundingBox(glyph_t glyph) const = 0;
virtual QFixed ascent() const = 0;
virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nglyphs,
QPainterPath *path);
+ virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) const;
virtual glyph_metrics_t boundingBox(glyph_t glyph) const;
virtual QFixed ascent() const;
*/
int QFontMetrics::width(const QString &text) const
{
- return boundingRect(QRect(0,0,0,0), Qt::TextDontPrint, text).width();
+ QTextEngine engine(text, d.data());
+ return qRound(engine.width(0, text.size()).toReal());
}
/*!
if (text.isEmpty())
return QRect();
- QPainterPath textpath;
- textpath.setFillRule(Qt::WindingFill);
- textpath.addText(QPointF(), QFont(d.data()), text);
- return textpath.boundingRect().toRect();
+ QTextEngine engine(text, d.data());
+ glyph_metrics_t gm = engine.boundingBox(0, text.size());
+ return QRect(qRound(gm.x.toReal()), qRound(gm.y.toReal()), qRound(gm.width.toReal()), qRound(gm.height.toReal()));
}
/*!
return boundingRect(QRect(0,0,0,0), flags | Qt::TextLongestVariant, text).size();
}
-static inline QString qt_mnemonic_mid(const QString &text, const int pos, const bool showmnemonic)
-{
- if (showmnemonic && pos > 0 && text.at(pos - 1) == QLatin1Char('&') && text.at(pos) != QLatin1Char('&')) {
- return text.mid(0, pos - 1);
- }
- return text.mid(0, pos);
-}
-
-static inline QString qt_elided_text(const QString &text, Qt::TextElideMode mode, qreal width, int flags, const QFontPrivate* font)
-{
- // qDebug() << Q_FUNC_INFO << text << mode << width << flags;
-
- static const QTextEngine::ShaperFlags shaperflags = 0;
-
- const bool showmnemonic = (flags & Qt::TextShowMnemonic);
- int i = 0;
- qreal xoffset = 0.0;
- QUnicodeTables::Script inheritedscript = QUnicodeTables::Common;
- for (i = 0; i < text.size(); i++) {
- int nglyphs = 1;
- QChar textchars[2] = { text.at(i), 0 };
- uint ucs4 = textchars[0].unicode();
- if (textchars[0].isHighSurrogate() && (i + 1) < text.size() && text.at(i + 1).isLowSurrogate()) {
- textchars[1] = text.at(i + 1);
- ucs4 = QChar::surrogateToUcs4(textchars[0], textchars[1]);
- i++;
- nglyphs = 2;
- // qDebug() << Q_FUNC_INFO << ucs4;
- }
-
- QUnicodeTables::Script script = QUnicodeTables::script(ucs4);
- if (script == QUnicodeTables::Inherited) {
- // qDebug() << Q_FUNC_INFO << "inherited" << ucs4;
- script = inheritedscript;
- } else {
- inheritedscript = script;
- }
-
- QFontEngine* engine = font->engineForScript(script);
- if (Q_UNLIKELY(!engine)) {
- qWarning("qt_elided_text: No font engine for script %d", int(script));
- continue;
- }
-
- switch (QChar::category(ucs4)) {
- case QChar::Separator_Line:
- case QChar::Other_Control:
- case QChar::Other_Format: {
- // qDebug() << Q_FUNC_INFO << ucs4;
- continue;
- }
- default: {
- break;
- }
- }
-
- if (showmnemonic && char(ucs4) == char('&')
- && (i + 1) < text.size() && text.at(i + 1) != QLatin1Char('&')) {
- continue;
- }
-
- QGlyphLayoutArray<2> glyphs;
- engine->stringToCMap(textchars, nglyphs, &glyphs, &nglyphs, shaperflags);
-
- xoffset += glyphs.advances_x[0].toReal();
- if (nglyphs == 2) {
- xoffset += glyphs.advances_x[1].toReal();
- }
- Q_ASSERT(nglyphs < 3);
-
- if (xoffset > width) {
- break;
- }
- }
-
- int elidedlength = i;
- // qDebug() << Q_FUNC_INFO << text << width << xoffset << elidedlength;
-
- if (elidedlength <= 0) {
- return QString();
- } else if (elidedlength <= 3 && text.size() > 0) {
- return (QString(text.at(0)) + QChar(0x2026));
- } else if (text.size() <= elidedlength) {
- return text;
- }
-
- switch (mode) {
- case Qt::ElideLeft: {
- return (QLatin1String("...") + qt_mnemonic_mid(text, elidedlength - 3, showmnemonic));
- }
- case Qt::ElideRight: {
- return (qt_mnemonic_mid(text, elidedlength - 3, showmnemonic) + QLatin1String("..."));
- }
- case Qt::ElideMiddle: {
- if (elidedlength <= 5) {
- return text;
- }
- // TODO: mnemonics chopping on the left and right
- const int midlength = (elidedlength / 2);
- return (text.left(midlength - 2) + QLatin1String("...") + text.right(midlength - 1));
- }
- case Qt::ElideNone: {
- return qt_mnemonic_mid(text, elidedlength, showmnemonic);
- }
- }
- Q_UNREACHABLE();
-}
-
/*!
\since 4.2
*/
QString QFontMetrics::elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const
{
- return qt_elided_text(text, mode, qreal(width), flags, d.data());
+ QTextEngine engine(text, d.data());
+ return engine.elidedText(mode, QFixed(width), flags);
}
/*!
*/
qreal QFontMetricsF::width(const QString &text) const
{
- return boundingRect(QRectF(0,0,0,0), Qt::TextDontPrint, text).width();
+ QTextEngine engine(text, d.data());
+ return engine.width(0, text.size()).toReal();
}
/*!
if (text.isEmpty())
return QRectF();
- QPainterPath textpath;
- textpath.setFillRule(Qt::WindingFill);
- textpath.addText(QPointF(), QFont(d.data()), text);
- return textpath.boundingRect();
+ if (text.isEmpty())
+ return QRect();
+
+ QTextEngine engine(text, d.data());
+ glyph_metrics_t gm = engine.boundingBox(0, text.size());
+ return QRectF(gm.x.toReal(), gm.y.toReal(), gm.width.toReal(), gm.height.toReal());
}
/*!
*/
QString QFontMetricsF::elidedText(const QString &text, Qt::TextElideMode mode, qreal width, int flags) const
{
- return qt_elided_text(text, mode, width, flags, d.data());
+ QTextEngine engine(text, d.data());
+ return engine.elidedText(mode, QFixed::fromReal(width), flags);
}
/*!
return w;
}
+glyph_metrics_t QTextEngine::boundingBox(int from, int len) const
+{
+ itemize();
+
+ glyph_metrics_t gm;
+
+ for (int i = 0; i < layoutData->items.size(); i++) {
+ const QScriptItem *si = layoutData->items.constData() + i;
+
+ int pos = si->position;
+ int ilen = length(i);
+ if (pos > from + len)
+ break;
+ if (pos + ilen > from) {
+ if (!si->num_glyphs)
+ shape(i);
+
+ if (si->analysis.flags == QScriptAnalysis::Object) {
+ gm.width += si->width;
+ continue;
+ } else if (si->analysis.flags == QScriptAnalysis::Tab) {
+ gm.width += calculateTabWidth(i, gm.width);
+ continue;
+ }
+
+ unsigned short *logClusters = this->logClusters(si);
+ QGlyphLayout glyphs = shapedGlyphs(si);
+
+ // do the simple thing for now and give the first glyph in a cluster the full width, all other ones 0.
+ int charFrom = from - pos;
+ if (charFrom < 0)
+ charFrom = 0;
+ int glyphStart = logClusters[charFrom];
+ if (charFrom > 0 && logClusters[charFrom-1] == glyphStart)
+ while (charFrom < ilen && logClusters[charFrom] == glyphStart)
+ charFrom++;
+ if (charFrom < ilen) {
+ QFontEngine *fe = fontEngine(*si);
+ glyphStart = logClusters[charFrom];
+ int charEnd = from + len - 1 - pos;
+ if (charEnd >= ilen)
+ charEnd = ilen-1;
+ int glyphEnd = logClusters[charEnd];
+ while (charEnd < ilen && logClusters[charEnd] == glyphEnd)
+ charEnd++;
+ glyphEnd = (charEnd == ilen) ? si->num_glyphs : logClusters[charEnd];
+ if (glyphStart <= glyphEnd ) {
+ glyph_metrics_t m = fe->boundingBox(glyphs.mid(glyphStart, glyphEnd - glyphStart));
+ gm.x = qMin(gm.x, m.x + gm.xoff);
+ gm.y = qMin(gm.y, m.y);
+ gm.width = qMax(gm.width, m.width + gm.xoff);
+ gm.height = qMax(gm.height, m.height);
+ gm.xoff += m.xoff;
+ }
+ }
+ }
+ }
+ return gm;
+}
+
QFont QTextEngine::font(const QScriptItem &si) const
{
QFont font = fnt;
}
}
+/* These two helper functions are used to determine whether we need to insert a ZWJ character
+ between the text that gets truncated and the ellipsis. This is important to get
+ correctly shaped results for arabic text.
+*/
+static inline bool nextCharJoins(const QString &string, int pos)
+{
+ while (pos < string.length() && string.at(pos).category() == QChar::Mark_NonSpacing)
+ ++pos;
+ if (pos == string.length())
+ return false;
+ return string.at(pos).joining() != QChar::OtherJoining;
+}
+
+static inline bool prevCharJoins(const QString &string, int pos)
+{
+ while (pos > 0 && string.at(pos - 1).category() == QChar::Mark_NonSpacing)
+ --pos;
+ if (pos == 0)
+ return false;
+ QChar::Joining joining = string.at(pos - 1).joining();
+ return (joining == QChar::Dual);
+}
+
+QString QTextEngine::elidedText(Qt::TextElideMode mode, const QFixed &width, int flags) const
+{
+ // qDebug() << "elidedText; available width" << width.toReal() << "text width:" << this->width(0, layoutData->string.length()).toReal();
+
+ if (flags & Qt::TextShowMnemonic) {
+ itemize();
+ HB_CharAttributes *attributes = const_cast<HB_CharAttributes *>(this->attributes());
+ if (!attributes)
+ return QString();
+ for (int i = 0; i < layoutData->items.size(); ++i) {
+ QScriptItem &si = layoutData->items[i];
+ if (!si.num_glyphs)
+ shape(i);
+
+ unsigned short *logClusters = this->logClusters(&si);
+ QGlyphLayout glyphs = shapedGlyphs(&si);
+
+ const int end = si.position + length(&si);
+ for (int i = si.position; i < end - 1; ++i) {
+ if (layoutData->string.at(i) == QLatin1Char('&')) {
+ const int gp = logClusters[i - si.position];
+ glyphs.attributes[gp].dontPrint = true;
+ attributes[i + 1].charStop = false;
+ attributes[i + 1].whiteSpace = false;
+ attributes[i + 1].lineBreakType = HB_NoBreak;
+ if (layoutData->string.at(i + 1) == QLatin1Char('&'))
+ ++i;
+ }
+ }
+ }
+ }
+
+ validate();
+
+ if (mode == Qt::ElideNone
+ || this->width(0, layoutData->string.length()) <= width
+ || layoutData->string.length() <= 1)
+ return layoutData->string;
+
+ QFixed ellipsisWidth;
+ QString ellipsisText;
+ {
+ QChar ellipsisChar(0x2026);
+
+ QFontEngine *fe = fnt.d->engineForScript(QUnicodeTables::Common);
+
+ QGlyphLayoutArray<1> ellipsisGlyph;
+ {
+ if (fe->canRender(&ellipsisChar, 1)) {
+ int nGlyphs = 1;
+ fe->stringToCMap(&ellipsisChar, 1, &ellipsisGlyph, &nGlyphs, 0);
+ }
+ }
+
+ if (ellipsisGlyph.glyphs[0]) {
+ ellipsisWidth = ellipsisGlyph.advances_x[0];
+ ellipsisText = ellipsisChar;
+ } else {
+ QString dotDotDot(QLatin1String("..."));
+
+ QGlyphLayoutArray<3> glyphs;
+ int nGlyphs = 3;
+ if (!fe->stringToCMap(dotDotDot.constData(), 3, &glyphs, &nGlyphs, 0))
+ // should never happen...
+ return layoutData->string;
+ for (int i = 0; i < nGlyphs; ++i)
+ ellipsisWidth += glyphs.advances_x[i];
+ ellipsisText = dotDotDot;
+ }
+ }
+
+ const QFixed availableWidth = width - ellipsisWidth;
+ if (availableWidth < 0)
+ return QString();
+
+ const HB_CharAttributes *attributes = this->attributes();
+ if (!attributes)
+ return QString();
+
+ if (mode == Qt::ElideRight) {
+ QFixed currentWidth;
+ int pos;
+ int nextBreak = 0;
+
+ do {
+ pos = nextBreak;
+
+ ++nextBreak;
+ while (nextBreak < layoutData->string.length() && !attributes[nextBreak].charStop)
+ ++nextBreak;
+
+ currentWidth += this->width(pos, nextBreak - pos);
+ } while (nextBreak < layoutData->string.length()
+ && currentWidth < availableWidth);
+
+ if (nextCharJoins(layoutData->string, pos))
+ ellipsisText.prepend(QChar(0x200d) /* ZWJ */);
+
+ return layoutData->string.left(pos) + ellipsisText;
+ } else if (mode == Qt::ElideLeft) {
+ QFixed currentWidth;
+ int pos;
+ int nextBreak = layoutData->string.length();
+
+ do {
+ pos = nextBreak;
+
+ --nextBreak;
+ while (nextBreak > 0 && !attributes[nextBreak].charStop)
+ --nextBreak;
+
+ currentWidth += this->width(nextBreak, pos - nextBreak);
+ } while (nextBreak > 0
+ && currentWidth < availableWidth);
+
+ if (prevCharJoins(layoutData->string, pos))
+ ellipsisText.append(QChar(0x200d) /* ZWJ */);
+
+ return ellipsisText + layoutData->string.mid(pos);
+ } else if (mode == Qt::ElideMiddle) {
+ QFixed leftWidth;
+ QFixed rightWidth;
+
+ int leftPos = 0;
+ int nextLeftBreak = 0;
+
+ int rightPos = layoutData->string.length();
+ int nextRightBreak = layoutData->string.length();
+
+ do {
+ leftPos = nextLeftBreak;
+ rightPos = nextRightBreak;
+
+ ++nextLeftBreak;
+ while (nextLeftBreak < layoutData->string.length() && !attributes[nextLeftBreak].charStop)
+ ++nextLeftBreak;
+
+ --nextRightBreak;
+ while (nextRightBreak > 0 && !attributes[nextRightBreak].charStop)
+ --nextRightBreak;
+
+ leftWidth += this->width(leftPos, nextLeftBreak - leftPos);
+ rightWidth += this->width(nextRightBreak, rightPos - nextRightBreak);
+ } while (nextLeftBreak < layoutData->string.length()
+ && nextRightBreak > 0
+ && leftWidth + rightWidth < availableWidth);
+
+ if (nextCharJoins(layoutData->string, leftPos))
+ ellipsisText.prepend(QChar(0x200d) /* ZWJ */);
+ if (prevCharJoins(layoutData->string, rightPos))
+ ellipsisText.append(QChar(0x200d) /* ZWJ */);
+
+ return layoutData->string.left(leftPos) + ellipsisText + layoutData->string.mid(rightPos);
+ }
+
+ return layoutData->string;
+}
+
void QTextEngine::setBoundary(int strPos) const
{
if (strPos <= 0 || strPos >= layoutData->string.length())
QFixed alignLine(const QScriptLine &line);
QFixed width(int charFrom, int numChars) const;
+ glyph_metrics_t boundingBox(int from, int len) const;
int length(int item) const {
int from = layoutData->items[item].position;
bool atSpace(int position) const;
void indexAdditionalFormats();
+ QString elidedText(Qt::TextElideMode mode, const QFixed &width, int flags) const;
+
void shapeLine(const QScriptLine &line);
QFixed offsetInLigature(const QScriptItem *si, int pos, int max, int glyph_pos);