From a1641e27d2e2f5e29362e3737be6b9d75714d138 Mon Sep 17 00:00:00 2001
From: Jiang Jiang <jiang.jiang@nokia.com>
Date: Mon, 9 Aug 2010 15:56:34 +0200
Subject: Add text decoration support to QStaticText

The original code path of QStaticText does not include decoration
drawing, this patch generalized the drawTextItemDecoration()
function to draw decoration for drawText(), then use that to draw
decoration for QStaticText. A helper function called
drawDecorationForGlyphs() is made to allow easier extension for
direct glyphs drawing support.

Task-number: QTBUG-12121
Reviewed-by: Eskil
---
 src/gui/painting/qpainter.cpp              |  84 +++++++++++++++++++----
 tests/auto/qstatictext/tst_qstatictext.cpp | 106 +++++++++++++++++++++++++++++
 2 files changed, 177 insertions(+), 13 deletions(-)

diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp
index 9dadbd5..b694d9c 100644
--- a/src/gui/painting/qpainter.cpp
+++ b/src/gui/painting/qpainter.cpp
@@ -90,6 +90,15 @@ void qt_format_text(const QFont &font,
                     const QRectF &_r, int tf, const QTextOption *option, const QString& str, QRectF *brect,
                     int tabstops, int* tabarray, int tabarraylen,
                     QPainter *painter);
+static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const QFontEngine *fe,
+                                   QTextCharFormat::UnderlineStyle underlineStyle,
+                                   const QTextItem::RenderFlags flags, qreal width,
+                                   const QTextCharFormat &charFormat);
+// Helper function to calculate left most position, width and flags for decoration drawing
+static void drawDecorationForGlyphs(QPainter *painter, const glyph_t *glyphArray,
+                                    const QFixedPoint *positions, int glyphCount,
+                                    QFontEngine *fontEngine, const QFont &font,
+                                    const QTextCharFormat &charFormat);
 
 static inline QGradient::CoordinateMode coordinateMode(const QBrush &brush)
 {
@@ -5923,6 +5932,10 @@ void QPainter::drawStaticText(const QPointF &topLeftPosition, const QStaticText
             currentColor = item->color;
         }
         d->extended->drawStaticTextItem(item);
+
+        drawDecorationForGlyphs(this, item->glyphs, item->glyphPositions,
+                                item->numGlyphs, item->fontEngine, staticText_d->font,
+                                QTextCharFormat());
     }
     if (currentColor != oldPen.color())
         setPen(oldPen);
@@ -6290,15 +6303,15 @@ static QPixmap generateWavyPixmap(qreal maxRadius, const QPen &pen)
     return pixmap;
 }
 
-static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const QTextItemInt &ti)
+static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const QFontEngine *fe,
+                                   QTextCharFormat::UnderlineStyle underlineStyle,
+                                   const QTextItem::RenderFlags flags, qreal width,
+                                   const QTextCharFormat &charFormat)
 {
-    QTextCharFormat::UnderlineStyle underlineStyle = ti.underlineStyle;
     if (underlineStyle == QTextCharFormat::NoUnderline
-        && !(ti.flags & (QTextItem::StrikeOut | QTextItem::Overline)))
+        && !(flags & (QTextItem::StrikeOut | QTextItem::Overline)))
         return;
 
-    QFontEngine *fe = ti.fontEngine;
-
     const QPen oldPen = painter->pen();
     const QBrush oldBrush = painter->brush();
     painter->setBrush(Qt::NoBrush);
@@ -6307,7 +6320,7 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const
     pen.setWidthF(fe->lineThickness().toReal());
     pen.setCapStyle(Qt::FlatCap);
 
-    QLineF line(pos.x(), pos.y(), pos.x() + ti.width.toReal(), pos.y());
+    QLineF line(pos.x(), pos.y(), pos.x() + width, pos.y());
 
     const qreal underlineOffset = fe->underlinePosition().toReal();
     // deliberately ceil the offset to avoid the underline coming too close to
@@ -6322,21 +6335,21 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const
         painter->save();
         painter->translate(0, pos.y() + 1);
 
-        QColor uc = ti.charFormat.underlineColor();
+        QColor uc = charFormat.underlineColor();
         if (uc.isValid())
             pen.setColor(uc);
 
         // Adapt wave to underlineOffset or pen width, whatever is larger, to make it work on all platforms
         const QPixmap wave = generateWavyPixmap(qMax(underlineOffset, pen.widthF()), pen);
-        const int descent = (int) ti.descent.toReal();
+        const int descent = (int) fe->descent().toReal();
 
         painter->setBrushOrigin(painter->brushOrigin().x(), 0);
-        painter->fillRect(pos.x(), 0, qCeil(ti.width.toReal()), qMin(wave.height(), descent), wave);
+        painter->fillRect(pos.x(), 0, qCeil(width), qMin(wave.height(), descent), wave);
         painter->restore();
     } else if (underlineStyle != QTextCharFormat::NoUnderline) {
         QLineF underLine(line.x1(), underlinePos, line.x2(), underlinePos);
 
-        QColor uc = ti.charFormat.underlineColor();
+        QColor uc = charFormat.underlineColor();
         if (uc.isValid())
             pen.setColor(uc);
 
@@ -6348,14 +6361,14 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const
     pen.setStyle(Qt::SolidLine);
     pen.setColor(oldPen.color());
 
-    if (ti.flags & QTextItem::StrikeOut) {
+    if (flags & QTextItem::StrikeOut) {
         QLineF strikeOutLine = line;
         strikeOutLine.translate(0., - fe->ascent().toReal() / 3.);
         painter->setPen(pen);
         painter->drawLine(strikeOutLine);
     }
 
-    if (ti.flags & QTextItem::Overline) {
+    if (flags & QTextItem::Overline) {
         QLineF overLine = line;
         overLine.translate(0., - fe->ascent().toReal());
         painter->setPen(pen);
@@ -6366,6 +6379,50 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const
     painter->setBrush(oldBrush);
 }
 
+static void drawDecorationForGlyphs(QPainter *painter, const glyph_t *glyphArray,
+                                    const QFixedPoint *positions, int glyphCount,
+                                    QFontEngine *fontEngine, const QFont &font,
+                                    const QTextCharFormat &charFormat)
+{
+    if (!(font.underline() || font.strikeOut() || font.overline()))
+        return;
+
+    QFixed leftMost;
+    QFixed rightMost;
+    QFixed baseLine;
+    for (int i=0; i<glyphCount; ++i) {
+        glyph_metrics_t gm = fontEngine->boundingBox(glyphArray[i]);
+        if (i == 0 || leftMost > positions[i].x)
+            leftMost = positions[i].x;
+
+        // We don't support glyphs that do not share a common baseline. If this turns out to
+        // be a relevant use case, then we need to find clusters of glyphs that share a baseline
+        // and do a drawTextItemDecorations call per cluster.
+        if (i == 0 || baseLine < positions[i].y)
+            baseLine = positions[i].y;
+
+        // We use the advance rather than the actual bounds to match the algorithm in drawText()
+        if (i == 0 || rightMost < positions[i].x + gm.xoff)
+            rightMost = positions[i].x + gm.xoff;
+    }
+
+    QFixed width = rightMost - leftMost;
+    QTextItem::RenderFlags flags = 0;
+
+    if (font.underline())
+        flags |= QTextItem::Underline;
+    if (font.overline())
+        flags |= QTextItem::Overline;
+    if (font.strikeOut())
+        flags |= QTextItem::StrikeOut;
+
+    drawTextItemDecoration(painter, QPointF(leftMost.toReal(), baseLine.toReal()),
+                           fontEngine,
+                           font.underline() ? QTextCharFormat::SingleUnderline
+                                            : QTextCharFormat::NoUnderline, flags,
+                           width.toReal(), charFormat);
+}
+
 void QPainter::drawTextItem(const QPointF &p, const QTextItem &_ti)
 {
 #ifdef QT_DEBUG_DRAW
@@ -6496,7 +6553,8 @@ void QPainter::drawTextItem(const QPointF &p, const QTextItem &_ti)
         else
             d->engine->drawTextItem(p, ti);
     }
-    drawTextItemDecoration(this, p, ti);
+    drawTextItemDecoration(this, p, ti.fontEngine, ti.underlineStyle, ti.flags, ti.width.toReal(),
+                           ti.charFormat);
 
     if (d->state->renderHints != oldRenderHints) {
         d->state->renderHints = oldRenderHints;
diff --git a/tests/auto/qstatictext/tst_qstatictext.cpp b/tests/auto/qstatictext/tst_qstatictext.cpp
index 1d166f4..0ae5320 100644
--- a/tests/auto/qstatictext/tst_qstatictext.cpp
+++ b/tests/auto/qstatictext/tst_qstatictext.cpp
@@ -85,6 +85,10 @@ private slots:
     void setPenPlainText();
     void setPenRichText();
     void richTextOverridesPen();
+
+    void drawStruckOutText();
+    void drawOverlinedText();
+    void drawUnderlinedText();
 };
 
 void tst_QStaticText::init()
@@ -620,5 +624,107 @@ void tst_QStaticText::richTextOverridesPen()
     }
 }
 
+void tst_QStaticText::drawStruckOutText()
+{
+    QPixmap imageDrawText(1000, 1000);
+    QPixmap imageDrawStaticText(1000, 1000);
+
+    imageDrawText.fill(Qt::white);
+    imageDrawStaticText.fill(Qt::white);
+
+    QString s = QString::fromLatin1("Foobar");
+
+    QFont font;
+    font.setStrikeOut(true);
+
+    {
+        QPainter p(&imageDrawText);
+        p.setFont(font);
+        p.drawText(QPointF(50, 50), s);
+    }
+
+    {
+        QPainter p(&imageDrawStaticText);
+        QStaticText text = QStaticText(s);
+        p.setFont(font);
+        p.drawStaticText(QPointF(50, 50 - QFontMetricsF(p.font()).ascent()), text);
+    }
+
+#if defined(DEBUG_SAVE_IMAGE)
+    imageDrawText.save("drawStruckOutText_imageDrawText.png");
+    imageDrawStaticText.save("drawStruckOutText_imageDrawStaticText.png");
+#endif
+
+    QCOMPARE(imageDrawText, imageDrawStaticText);
+}
+
+void tst_QStaticText::drawOverlinedText()
+{
+    QPixmap imageDrawText(1000, 1000);
+    QPixmap imageDrawStaticText(1000, 1000);
+
+    imageDrawText.fill(Qt::white);
+    imageDrawStaticText.fill(Qt::white);
+
+    QString s = QString::fromLatin1("Foobar");
+
+    QFont font;
+    font.setOverline(true);
+
+    {
+        QPainter p(&imageDrawText);
+        p.setFont(font);
+        p.drawText(QPointF(50, 50), s);
+    }
+
+    {
+        QPainter p(&imageDrawStaticText);
+        QStaticText text = QStaticText(s);
+        p.setFont(font);
+        p.drawStaticText(QPointF(50, 50 - QFontMetricsF(p.font()).ascent()), text);
+    }
+
+#if defined(DEBUG_SAVE_IMAGE)
+    imageDrawText.save("drawOverlinedText_imageDrawText.png");
+    imageDrawStaticText.save("drawOverlinedText_imageDrawStaticText.png");
+#endif
+
+    QCOMPARE(imageDrawText, imageDrawStaticText);
+}
+
+void tst_QStaticText::drawUnderlinedText()
+{
+    QPixmap imageDrawText(1000, 1000);
+    QPixmap imageDrawStaticText(1000, 1000);
+
+    imageDrawText.fill(Qt::white);
+    imageDrawStaticText.fill(Qt::white);
+
+    QString s = QString::fromLatin1("Foobar");
+
+    QFont font;
+    font.setUnderline(true);
+
+    {
+        QPainter p(&imageDrawText);
+        p.setFont(font);
+        p.drawText(QPointF(50, 50), s);
+    }
+
+    {
+        QPainter p(&imageDrawStaticText);
+        QStaticText text = QStaticText(s);
+        p.setFont(font);
+        p.drawStaticText(QPointF(50, 50 - QFontMetricsF(p.font()).ascent()), text);
+    }
+
+#if defined(DEBUG_SAVE_IMAGE)
+    imageDrawText.save("drawUnderlinedText_imageDrawText.png");
+    imageDrawStaticText.save("drawUnderlinedText_imageDrawStaticText.png");
+#endif
+
+    QCOMPARE(imageDrawText, imageDrawStaticText);
+}
+
 QTEST_MAIN(tst_QStaticText)
 #include "tst_qstatictext.moc"
-- 
cgit v0.12