]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiPainter.cpp
Use <cstdint> instead of <boost/cstdint.hpp>
[lyx.git] / src / frontends / qt4 / GuiPainter.cpp
index 68161d1be9edd4e561eb9dd1f2b6ba7dc20180ca..c1f3677815f26b646f49b1e4ceda2b04356717b2 100644 (file)
 #include "GuiImage.h"
 #include "qt_helpers.h"
 
-#include "FontInfo.h"
-#include "Language.h"
+#include "Font.h"
 #include "LyXRC.h"
 
-#include "insets/Inset.h"
-
-#include "support/lassert.h"
 #include "support/debug.h"
+#include "support/lassert.h"
+#include "support/lyxlib.h"
 
-#include <QPixmapCache>
-#include <QTextLayout>
+#include <algorithm>
 
-// Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
-// drawing text. This is especially useful for older PPC/Mac systems.
-#if defined(Q_WS_X11)
-#define USE_PIXMAP_CACHE 0
-#else
-#define USE_PIXMAP_CACHE 1
-#endif
+#include <QTextLayout>
 
 using namespace std;
+using namespace lyx::support;
 
 namespace lyx {
 namespace frontend {
 
-GuiPainter::GuiPainter(QPaintDevice * device)
-       : QPainter(device), Painter(),
-         use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
+const int Painter::thin_line = 1;
+
+GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
+       : QPainter(device), Painter(pixel_ratio)
 {
-       // new QPainter has default QPen:
-       current_color_ = guiApp->colorCache().get(Color_black);
-       current_ls_ = line_solid;
-       current_lw_ = line_thin;
+       // set cache correctly
+       current_color_ = pen().color();
+       current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
+       current_lw_ = pen().width();
 }
 
 
@@ -64,7 +57,7 @@ GuiPainter::~GuiPainter()
 
 
 void GuiPainter::setQPainterPen(QColor const & col,
-       Painter::line_style ls, Painter::line_width lw)
+       Painter::line_style ls, int lw)
 {
        if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
                return;
@@ -77,42 +70,20 @@ void GuiPainter::setQPainterPen(QColor const & col,
        pen.setColor(col);
 
        switch (ls) {
-               case line_solid: pen.setStyle(Qt::SolidLine); break;
-               case line_onoffdash: pen.setStyle(Qt::DotLine); break;
+       case line_solid:
+       case line_solid_aliased:
+               pen.setStyle(Qt::SolidLine); break;
+       case line_onoffdash:
+               pen.setStyle(Qt::DotLine); break;
        }
 
-       switch (lw) {
-               case line_thin: pen.setWidth(0); break;
-               case line_thick: pen.setWidth(3); break;
-       }
+       pen.setWidth(lw);
 
        setPen(pen);
 }
 
 
-QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
-{
-       QString sig = str;
-       sig.append(QChar(static_cast<short>(f.family())));
-       sig.append(QChar(static_cast<short>(f.series())));
-       sig.append(QChar(static_cast<short>(f.realShape())));
-       sig.append(QChar(static_cast<short>(f.size())));
-       sig.append(QChar(static_cast<short>(f.color())));
-       if (!monochrome_min_.empty()) {
-               QColor const & min = monochrome_min_.top();
-               QColor const & max = monochrome_max_.top();
-               sig.append(QChar(static_cast<short>(min.red())));
-               sig.append(QChar(static_cast<short>(min.green())));
-               sig.append(QChar(static_cast<short>(min.blue())));
-               sig.append(QChar(static_cast<short>(max.red())));
-               sig.append(QChar(static_cast<short>(max.green())));
-               sig.append(QChar(static_cast<short>(max.blue())));
-       }
-       return sig;
-}
-
-
-QColor GuiPainter::computeColor(ColorCode col)
+QColor GuiPainter::computeColor(Color col)
 {
        return filterColor(guiApp->colorCache().get(col));
 }
@@ -126,15 +97,15 @@ QColor GuiPainter::filterColor(QColor const & col)
        // map into [min,max] interval
        QColor const & min = monochrome_min_.top();
        QColor const & max = monochrome_max_.top();
-                       
+
        qreal v = col.valueF();
        v *= v; // make it a bit steeper (i.e. darker)
-               
+
        qreal minr, ming, minb;
        qreal maxr, maxg, maxb;
        min.getRgbF(&minr, &ming, &minb);
        max.getRgbF(&maxr, &maxg, &maxb);
-                       
+
        QColor c;
        c.setRgbF(
                v * (minr - maxr) + maxr,
@@ -144,7 +115,7 @@ QColor GuiPainter::filterColor(QColor const & col)
 }
 
 
-void GuiPainter::enterMonochromeMode(ColorCode const & min, ColorCode const & max)
+void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
 {
        QColor qmin = filterColor(guiApp->colorCache().get(min));
        QColor qmax = filterColor(guiApp->colorCache().get(max));
@@ -155,33 +126,27 @@ void GuiPainter::enterMonochromeMode(ColorCode const & min, ColorCode const & ma
 
 void GuiPainter::leaveMonochromeMode()
 {
-       LASSERT(!monochrome_min_.empty(), /**/);
+       LASSERT(!monochrome_min_.empty(), return);
        monochrome_min_.pop();
        monochrome_max_.pop();
 }
 
 
-void GuiPainter::point(int x, int y, ColorCode col)
+void GuiPainter::point(int x, int y, Color col)
 {
-       if (!isDrawingEnabled())
-               return;
-
        setQPainterPen(computeColor(col));
        drawPoint(x, y);
 }
 
 
 void GuiPainter::line(int x1, int y1, int x2, int y2,
-       ColorCode col,
+       Color col,
        line_style ls,
-       line_width lw)
+       int lw)
 {
-       if (!isDrawingEnabled())
-               return;
-
        setQPainterPen(computeColor(col), ls, lw);
        bool const do_antialiasing = renderHints() & TextAntialiasing
-               && x1 != x2 && y1 != y2;
+               && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
        setRenderHint(Antialiasing, do_antialiasing);
        drawLine(x1, y1, x2, y2);
        setRenderHint(Antialiasing, false);
@@ -189,18 +154,19 @@ void GuiPainter::line(int x1, int y1, int x2, int y2,
 
 
 void GuiPainter::lines(int const * xp, int const * yp, int np,
-       ColorCode col,
+       Color col,
+       fill_style fs,
        line_style ls,
-       line_width lw)
+       int lw)
 {
-       if (!isDrawingEnabled())
-               return;
-
        // double the size if needed
+       // FIXME THREAD
        static QVector<QPoint> points(32);
        if (np > points.size())
                points.resize(2 * np);
 
+       // Note: the proper way to not get blurry vertical and horizontal lines is
+       // to add 0.5 to all coordinates.
        bool antialias = false;
        for (int i = 0; i < np; ++i) {
                points[i].setX(xp[i]);
@@ -208,42 +174,75 @@ void GuiPainter::lines(int const * xp, int const * yp, int np,
                if (i != 0)
                        antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
        }
-       setQPainterPen(computeColor(col), ls, lw);
+       QColor const color = computeColor(col);
+       setQPainterPen(color, ls, lw);
        bool const text_is_antialiased = renderHints() & TextAntialiasing;
-       setRenderHint(Antialiasing, antialias && text_is_antialiased);
-       drawPolyline(points.data(), np);
+       setRenderHint(Antialiasing,
+                     antialias && text_is_antialiased && ls != line_solid_aliased);
+       if (fs == fill_none) {
+               drawPolyline(points.data(), np);
+       } else {
+               QBrush const oldbrush = brush();
+               setBrush(QBrush(color));
+               drawPolygon(points.data(), np, fs == fill_oddeven ?
+                           Qt::OddEvenFill : Qt::WindingFill);
+               setBrush(oldbrush);
+       }
        setRenderHint(Antialiasing, false);
 }
 
 
-void GuiPainter::rectangle(int x, int y, int w, int h,
-       ColorCode col,
+void GuiPainter::path(int const * xp, int const * yp,
+       int const * c1x, int const * c1y,
+       int const * c2x, int const * c2y,
+       int np,
+       Color col,
+       fill_style fs,
        line_style ls,
-       line_width lw)
+       int lw)
 {
-       if (!isDrawingEnabled())
-               return;
+       QPainterPath bpath;
+       // This is the starting point, so its control points are meaningless
+       bpath.moveTo(xp[0], yp[0]);
+
+       for (int i = 1; i < np; ++i) {
+               bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
+                           c2x[i] == xp[i] && c2y[i] == yp[i];
+               if (line)
+                       bpath.lineTo(xp[i], yp[i]);
+               else
+                       bpath.cubicTo(c1x[i], c1y[i],  c2x[i], c2y[i], xp[i], yp[i]);
+       }
+       QColor const color = computeColor(col);
+       setQPainterPen(color, ls, lw);
+       bool const text_is_antialiased = renderHints() & TextAntialiasing;
+       setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
+       drawPath(bpath);
+       if (fs != fill_none)
+               fillPath(bpath, QBrush(color));
+       setRenderHint(Antialiasing, false);
+}
+
 
+void GuiPainter::rectangle(int x, int y, int w, int h,
+       Color col,
+       line_style ls,
+       int lw)
+{
        setQPainterPen(computeColor(col), ls, lw);
        drawRect(x, y, w, h);
 }
 
 
-void GuiPainter::fillRectangle(int x, int y, int w, int h, ColorCode col)
+void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
 {
-       if (!isDrawingEnabled())
-               return;
-
        fillRect(x, y, w, h, guiApp->colorCache().get(col));
 }
 
 
 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
-       int a1, int a2, ColorCode col)
+       int a1, int a2, Color col)
 {
-       if (!isDrawingEnabled())
-               return;
-
        // LyX usings 1/64ths degree, Qt usings 1/16th
        setQPainterPen(computeColor(col));
        bool const do_antialiasing = renderHints() & TextAntialiasing;
@@ -260,52 +259,38 @@ void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
 
        fillRectangle(x, y, w, h, Color_graphicsbg);
 
-       if (!isDrawingEnabled())
-               return;
-
-       drawImage(x, y, qlimage.image(), 0, 0, w, h);
+       QImage const image = qlimage.image();
+       QRectF const drect = QRectF(x, y, w, h);
+       QRectF const srect = QRectF(0, 0, image.width(), image.height());
+       // Bilinear filtering is needed on a rare occasion for instant previews when
+       // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
+       // This filter is optimised by qt on pixel-aligned images, so this does not
+       // affect performances in other cases.
+       setRenderHint(SmoothPixmapTransform);
+       drawImage(drect, image, srect);
+       setRenderHint(SmoothPixmapTransform, false);
 }
 
 
-int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
+void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
 {
-       docstring s(1, c);
-       return text(x, y, s, f);
+       text(x, y, docstring(1, c), f);
 }
 
 
-int GuiPainter::smallCapsText(int x, int y,
-       QString const & s, FontInfo const & f)
+void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
 {
-       FontInfo smallfont(f);
-       smallfont.decSize().decSize().setShape(UP_SHAPE);
-
-       QFont const & qfont = getFont(f);
-       QFont const & qsmallfont = getFont(smallfont);
-
-       setQPainterPen(computeColor(f.realColor()));
-       int textwidth = 0;
-       size_t const ls = s.length();
-       for (unsigned int i = 0; i < ls; ++i) {
-               QChar const c = s[i].toUpper();
-               if (c != s.at(i)) {
-                       setFont(qsmallfont);
-               } else {
-                       setFont(qfont);
-               }
-               if (isDrawingEnabled())
-                       drawText(x + textwidth, y, c);
-               textwidth += fontMetrics().width(c);
-       }
-       return textwidth;
+       text(x, y, s, f, Auto, 0.0, 0.0);
 }
 
 
-int GuiPainter::text(int x, int y, docstring const & s,
-               FontInfo const & f)
+void GuiPainter::text(int x, int y, docstring const & s,
+                      FontInfo const & f, Direction const dir,
+                      double const wordspacing, double const tw)
 {
+       //LYXERR0("text: x=" << x << ", s=" << s);
        if (s.empty())
-               return 0;
+               return;
 
        /* Caution: The following ucs4 to QString conversions work for symbol fonts
        only because they are no real conversions but simple casts in reality.
@@ -313,8 +298,8 @@ int GuiPainter::text(int x, int y, docstring const & s,
        of the symbol in the font (as given in lib/symbols) as a char_type to the
        frontend. This is just wrong, because the symbol is no UCS4 character at
        all. You can think of this number as the code point of the symbol in a
-       custom symbol encoding. It works because this char_type is lateron again
-       interpreted as a position in the font again.
+       custom symbol encoding. It works because this char_type is later on again
+       interpreted as a position in the font.
        The correct solution would be to have extra functions for symbols, but that
        would require to duplicate a lot of frontend and mathed support code.
        */
@@ -327,131 +312,104 @@ int GuiPainter::text(int x, int y, docstring const & s,
                str = ' ' + str;
 #endif
 
-       QFont const & ff = getFont(f); 
-       GuiFontMetrics const & fm = getFontMetrics(f); 
+       QFont ff = getFont(f);
+       ff.setWordSpacing(wordspacing);
+       GuiFontMetrics const & fm = getFontMetrics(f);
 
-       int textwidth;
+       int textwidth = 0;
+       if (tw == 0.0)
+               // Take into account space stretching (word spacing)
+               textwidth = fm.width(s) +
+                       static_cast<int>(fm.countExpanders(s) * wordspacing);
+       else
+               textwidth = static_cast<int>(tw);
 
-       if (f.realShape() == SMALLCAPS_SHAPE) {
-               textwidth = smallCapsText(x, y, str, f);
-               if (f.underbar() == FONT_ON)
-                       underline(f, x, y, textwidth);
-               return textwidth;
-       }
+       textDecoration(f, x, y, textwidth);
 
-       // Here we use the font width cache instead of
-       //   textwidth = fontMetrics().width(str);
-       // because the above is awfully expensive on MacOSX
-       textwidth = fm.width(s);
-       if (f.underbar() == FONT_ON)
-               underline(f, x, y, textwidth);
-
-       if (!isDrawingEnabled())
-               return textwidth;
-
-       // Qt4 does not display a glyph whose codepoint is the
-       // same as that of a soft-hyphen (0x00ad), unless it
-       // occurs at a line-break. As a kludge, we force Qt to
-       // render this glyph using a one-column line.
-       if (s.size() == 1 && str[0].unicode() == 0x00ad) {
-               setQPainterPen(computeColor(f.realColor()));
-               QTextLayout adsymbol(str);
-               adsymbol.setFont(ff);
-               adsymbol.beginLayout();
-               QTextLine line = adsymbol.createLine();
-               line.setNumColumns(1);
-               line.setPosition(QPointF(0, -line.ascent()));
-               adsymbol.endLayout();
-               line.draw(this, QPointF(x, y));
-               return textwidth;
-       }
-
-       if (!use_pixmap_cache_) {
-               // don't use the pixmap cache,
-               // draw directly onto the painting device
-               setQPainterPen(computeColor(f.realColor()));
+       setQPainterPen(computeColor(f.realColor()));
+       if (dir != Auto) {
+               auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
+               QTextLine const & tline = ptl->lineForTextPosition(0);
+               ptl->draw(this, QPointF(x, y - tline.ascent()));
+       } else {
                if (font() != ff)
                        setFont(ff);
-               // We need to draw the text as LTR as we use our own bidi code.
-               QPainter::setLayoutDirection(Qt::LeftToRight);
                drawText(x, y, str);
-               //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
-               //      << " at " << x << "," << y);
-               return textwidth;
        }
-
-       QPixmap pm;
-       QString key = generateStringSignature(str, f);
-       // Warning: Left bearing is in general negative! Only the case
-       // where left bearing is negative is of interest WRT the the 
-       // pixmap width and the text x-position.
-       // Only the left bearing of the first character is important
-       // as we always write from left to right, even for
-       // right-to-left languages.
-       int const lb = min(fm.lbearing(s[0]), 0);
-       int const mA = fm.maxAscent();
-       if (!QPixmapCache::find(key, pm)) {
-               // Only the right bearing of the last character is
-               // important as we always write from left to right,
-               // even for right-to-left languages.
-               int const rb = fm.rbearing(s[s.size()-1]);
-               int const w = textwidth + rb - lb;
-               int const mD = fm.maxDescent();
-               int const h = mA + mD;
-               if (w <= 0 || h <= 0) {
-                       LYXERR(Debug::PAINTING, "Invalid pixmap cache image for '" << s << "' h=" << h << " w=" << w);
-                       return textwidth;
-               }
-
-               pm = QPixmap(w, h);
-               pm.fill(Qt::transparent);
-               GuiPainter p(&pm);
-               p.setQPainterPen(computeColor(f.realColor()));
-               if (p.font() != ff)
-                       p.setFont(ff);
-               // We need to draw the text as LTR as we use our own bidi code.
-               p.setLayoutDirection(Qt::LeftToRight);
-               p.drawText(-lb, mA, str);
-               QPixmapCache::insert(key, pm);
-               //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
-               //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth 
-               //      << "  rb=" << rb);
-       }
-       // Draw the cached pixmap.
-       drawPixmap(x + lb, y - mA, pm);
-
-       return textwidth;
+       //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
+       //      << " at " << x << "," << y);
 }
 
 
-static int max(int a, int b) { return a > b ? a : b; }
+void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
+                      double const wordspacing, double const tw)
+{
+       text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
+            wordspacing, tw);
+}
 
 
-void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
+void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
+                      Color other, size_type const from, size_type const to,
+                      double const wordspacing, double const tw)
 {
-       if (mouseHover)
-               fillRectangle(x, y, w, h, Color_buttonhoverbg);
-       else
-               fillRectangle(x, y, w, h, Color_buttonbg);
-       buttonFrame(x, y, w, h);
+       GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
+       FontInfo fi = f.fontInfo();
+       Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
+
+       // dimensions
+       int const ascent = fm.maxAscent();
+       int const height = fm.maxAscent() + fm.maxDescent();
+       int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
+       int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
+       // Avoid this case, since it would make the `other' text spill in some cases
+       if (xmin == xmax) {
+               text(x, y, str, fi, dir, wordspacing, tw);
+               return;
+       } else if (xmin > xmax)
+               swap(xmin, xmax);
+
+       // First the part in other color
+       Color const orig = fi.realColor();
+       fi.setPaintColor(other);
+       QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
+       setClipRegion(clip);
+       text(x, y, str, fi, dir, wordspacing, tw);
+
+       // Then the part in normal color
+       // Note that in Qt5, it is not possible to use Qt::UniteClip,
+       // therefore QRegion is used.
+       fi.setPaintColor(orig);
+       QRegion region(viewport());
+       setClipRegion(region - clip);
+       text(x, y, str, fi, dir, wordspacing, tw);
+       setClipping(false);
 }
 
 
-void GuiPainter::buttonFrame(int x, int y, int w, int h)
+void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
 {
-       line(x, y, x, y + h - 1, Color_buttonframe);
-       line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
-       line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
-       line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
+       if (f.underbar() == FONT_ON)
+               underline(f, x, y, width);
+       if (f.strikeout() == FONT_ON)
+               strikeoutLine(f, x, y, width);
+       if (f.xout() == FONT_ON)
+               crossoutLines(f, x, y, width);
+       if (f.uuline() == FONT_ON)
+               doubleUnderline(f, x, y, width);
+       if (f.uwave() == FONT_ON)
+               // f.color() doesn't work on some circumstances
+               wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
 }
 
 
+static int max(int a, int b) { return a > b ? a : b; }
+
+
 void GuiPainter::rectText(int x, int y, docstring const & str,
-       FontInfo const & font, ColorCode back, ColorCode frame)
+       FontInfo const & font, Color back, Color frame)
 {
-       int width;
-       int ascent;
-       int descent;
+       int width, ascent, descent;
 
        FontMetrics const & fm = theFontMetrics(font);
        fm.rectText(str, width, ascent, descent);
@@ -463,24 +421,25 @@ void GuiPainter::rectText(int x, int y, docstring const & str,
        if (frame != Color_none)
                rectangle(x, y - ascent, width, ascent + descent, frame);
 
+       // FIXME: let offset depend on font
        text(x + 3, y, str, font);
 }
 
 
-void GuiPainter::buttonText(int x, int y, docstring const & str,
-       FontInfo const & font, bool mouseHover)
+void GuiPainter::buttonText(int x, int baseline, docstring const & s,
+       FontInfo const & font, Color back, Color frame, int offset)
 {
-       int width;
-       int ascent;
-       int descent;
+       int width, ascent, descent;
 
        FontMetrics const & fm = theFontMetrics(font);
-       fm.buttonText(str, width, ascent, descent);
+       fm.buttonText(s, offset, width, ascent, descent);
 
-       static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
+       static int const d = offset / 2;
 
-       button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
-       text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
+       fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
+                             ascent + descent - 1, back);
+       rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
+       text(x + offset, baseline, s, font);
 }
 
 
@@ -517,17 +476,51 @@ int GuiPainter::preeditText(int x, int y, char_type c,
 }
 
 
-void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
+void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
+                           line_style ls)
 {
        FontMetrics const & fm = theFontMetrics(f);
+       int const pos = fm.underlinePos();
 
-       int const below = max(fm.maxDescent() / 2, 2);
-       int const height = max((fm.maxDescent() / 4) - 1, 1);
+       line(x, y + pos, x + width, y + pos,
+            f.realColor(), ls, fm.lineWidth());
+}
 
-       if (height < 2)
-               line(x, y + below, x + width, y + below, f.realColor());
-       else
-               fillRectangle(x, y + below, width, below + height, f.realColor());
+
+void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
+{
+       FontMetrics const & fm = theFontMetrics(f);
+       int const pos = fm.strikeoutPos();
+
+       line(x, y - pos, x + width, y - pos,
+            f.realColor(), line_solid, fm.lineWidth());
+}
+
+
+void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
+{
+       FontInfo tmpf = f;
+       tmpf.setXout(FONT_OFF);
+
+       // the definition of \xout in ulem.sty is
+    //  \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
+       // Let's mimick it somewhat.
+       double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
+       for (int i = 0 ; i < iround(width / offset) ; ++i)
+               text(x + iround(i * offset), y, '/', tmpf);
+}
+
+
+void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
+{
+       FontMetrics const & fm = theFontMetrics(f);
+       int const pos1 = fm.underlinePos() + fm.lineWidth();
+       int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
+
+       line(x, y + pos1, x + width, y + pos1,
+                f.realColor(), line_solid, fm.lineWidth());
+       line(x, y + pos2, x + width, y + pos2,
+                f.realColor(), line_solid, fm.lineWidth());
 }
 
 
@@ -545,5 +538,24 @@ void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
                line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
 }
 
+
+void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
+{
+       setQPainterPen(computeColor(col));
+       int const step = 2;
+       int const xend = x + width;
+       int height = 1;
+       //FIXME: I am not sure if Antialiasing gives the best effect.
+       //setRenderHint(Antialiasing, true);
+       while (x < xend) {
+               height = - height;
+               drawLine(x, y - height, x + step, y + height);
+               x += step;
+               drawLine(x, y + height, x + step/2, y + height);
+               x += step/2;
+       }
+       //setRenderHint(Antialiasing, false);
+}
+
 } // namespace frontend
 } // namespace lyx