3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
7 * \author Abdelrazak Younes
9 * Full author contact details are available in file CREDITS.
14 #include "GuiPainter.h"
16 #include "ColorCache.h"
17 #include "GuiApplication.h"
18 #include "GuiFontLoader.h"
19 #include "GuiFontMetrics.h"
21 #include "qt_helpers.h"
26 #include "support/debug.h"
27 #include "support/lassert.h"
28 #include "support/lyxlib.h"
32 #include <QPixmapCache>
33 #include <QTextLayout>
35 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
36 // drawing text. This is especially useful for older PPC/Mac systems.
37 #if defined(Q_WS_X11) || defined(QPA_XCB)
38 #define USE_PIXMAP_CACHE 0
40 #define USE_PIXMAP_CACHE 1
44 using namespace lyx::support;
49 const int Painter::thin_line = 1;
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52 : QPainter(device), Painter(pixel_ratio),
53 use_pixmap_cache_(false)
55 // new QPainter has default QPen:
56 current_color_ = guiApp->colorCache().get(Color_black);
57 current_ls_ = line_solid;
58 current_lw_ = thin_line;
62 GuiPainter::~GuiPainter()
65 //lyxerr << "GuiPainter::end()" << endl;
69 void GuiPainter::setQPainterPen(QColor const & col,
70 Painter::line_style ls, int lw)
72 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
79 QPen pen = QPainter::pen();
84 case line_solid_aliased:
85 pen.setStyle(Qt::SolidLine); break;
87 pen.setStyle(Qt::DotLine); break;
96 QString GuiPainter::generateStringSignature(QString const & str,
101 sig.append(QChar(static_cast<short>(f.family())));
102 sig.append(QChar(static_cast<short>(f.series())));
103 sig.append(QChar(static_cast<short>(f.realShape())));
104 sig.append(QChar(static_cast<short>(f.size())));
105 Color const & color = f.realColor();
106 sig.append(QChar(static_cast<short>(color.baseColor)));
107 sig.append(QChar(static_cast<short>(color.mergeColor)));
108 sig.append(QString::number(wordspacing));
109 if (!monochrome_min_.empty()) {
110 QColor const & min = monochrome_min_.top();
111 QColor const & max = monochrome_max_.top();
112 sig.append(QChar(static_cast<short>(min.red())));
113 sig.append(QChar(static_cast<short>(min.green())));
114 sig.append(QChar(static_cast<short>(min.blue())));
115 sig.append(QChar(static_cast<short>(max.red())));
116 sig.append(QChar(static_cast<short>(max.green())));
117 sig.append(QChar(static_cast<short>(max.blue())));
123 QColor GuiPainter::computeColor(Color col)
125 return filterColor(guiApp->colorCache().get(col));
129 QColor GuiPainter::filterColor(QColor const & col)
131 if (monochrome_min_.empty())
134 // map into [min,max] interval
135 QColor const & min = monochrome_min_.top();
136 QColor const & max = monochrome_max_.top();
138 qreal v = col.valueF();
139 v *= v; // make it a bit steeper (i.e. darker)
141 qreal minr, ming, minb;
142 qreal maxr, maxg, maxb;
143 min.getRgbF(&minr, &ming, &minb);
144 max.getRgbF(&maxr, &maxg, &maxb);
148 v * (minr - maxr) + maxr,
149 v * (ming - maxg) + maxg,
150 v * (minb - maxb) + maxb);
155 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
157 QColor qmin = filterColor(guiApp->colorCache().get(min));
158 QColor qmax = filterColor(guiApp->colorCache().get(max));
159 monochrome_min_.push(qmin);
160 monochrome_max_.push(qmax);
164 void GuiPainter::leaveMonochromeMode()
166 LASSERT(!monochrome_min_.empty(), return);
167 monochrome_min_.pop();
168 monochrome_max_.pop();
172 void GuiPainter::point(int x, int y, Color col)
174 if (!isDrawingEnabled())
177 setQPainterPen(computeColor(col));
182 void GuiPainter::line(int x1, int y1, int x2, int y2,
187 if (!isDrawingEnabled())
190 setQPainterPen(computeColor(col), ls, lw);
191 bool const do_antialiasing = renderHints() & TextAntialiasing
192 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
193 setRenderHint(Antialiasing, do_antialiasing);
194 drawLine(x1, y1, x2, y2);
195 setRenderHint(Antialiasing, false);
199 void GuiPainter::lines(int const * xp, int const * yp, int np,
205 if (!isDrawingEnabled())
208 // double the size if needed
210 static QVector<QPoint> points(32);
211 if (np > points.size())
212 points.resize(2 * np);
214 // Note: the proper way to not get blurry vertical and horizontal lines is
215 // to add 0.5 to all coordinates.
216 bool antialias = false;
217 for (int i = 0; i < np; ++i) {
218 points[i].setX(xp[i]);
219 points[i].setY(yp[i]);
221 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
223 QColor const color = computeColor(col);
224 setQPainterPen(color, ls, lw);
225 bool const text_is_antialiased = renderHints() & TextAntialiasing;
226 setRenderHint(Antialiasing,
227 antialias && text_is_antialiased && ls != line_solid_aliased);
228 if (fs == fill_none) {
229 drawPolyline(points.data(), np);
231 QBrush const oldbrush = brush();
232 setBrush(QBrush(color));
233 drawPolygon(points.data(), np, fs == fill_oddeven ?
234 Qt::OddEvenFill : Qt::WindingFill);
237 setRenderHint(Antialiasing, false);
241 void GuiPainter::path(int const * xp, int const * yp,
242 int const * c1x, int const * c1y,
243 int const * c2x, int const * c2y,
250 if (!isDrawingEnabled())
254 // This is the starting point, so its control points are meaningless
255 bpath.moveTo(xp[0], yp[0]);
257 for (int i = 1; i < np; ++i) {
258 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
259 c2x[i] == xp[i] && c2y[i] == yp[i];
261 bpath.lineTo(xp[i], yp[i]);
263 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
265 QColor const color = computeColor(col);
266 setQPainterPen(color, ls, lw);
267 bool const text_is_antialiased = renderHints() & TextAntialiasing;
268 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
271 fillPath(bpath, QBrush(color));
272 setRenderHint(Antialiasing, false);
276 void GuiPainter::rectangle(int x, int y, int w, int h,
281 if (!isDrawingEnabled())
284 setQPainterPen(computeColor(col), ls, lw);
285 drawRect(x, y, w, h);
289 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
291 if (!isDrawingEnabled())
294 fillRect(x, y, w, h, guiApp->colorCache().get(col));
298 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
299 int a1, int a2, Color col)
301 if (!isDrawingEnabled())
304 // LyX usings 1/64ths degree, Qt usings 1/16th
305 setQPainterPen(computeColor(col));
306 bool const do_antialiasing = renderHints() & TextAntialiasing;
307 setRenderHint(Antialiasing, do_antialiasing);
308 drawArc(x, y, w, h, a1 / 4, a2 / 4);
309 setRenderHint(Antialiasing, false);
313 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
315 graphics::GuiImage const & qlimage =
316 static_cast<graphics::GuiImage const &>(i);
318 fillRectangle(x, y, w, h, Color_graphicsbg);
320 if (!isDrawingEnabled())
323 QImage const image = qlimage.image();
324 QRectF const drect = QRectF(x, y, w, h);
325 QRectF const srect = QRectF(0, 0, image.width(), image.height());
326 // Bilinear filtering is needed on a rare occasion for instant previews when
327 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
328 // This filter is optimised by qt on pixel-aligned images, so this does not
329 // affect performances in other cases.
330 setRenderHint(SmoothPixmapTransform);
331 drawImage(drect, image, srect);
332 setRenderHint(SmoothPixmapTransform, false);
336 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
338 text(x, y, docstring(1, c), f);
342 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
344 text(x, y, s, f, Auto, 0.0, 0.0);
348 void GuiPainter::do_drawText(int x, int y, QString str,
349 GuiPainter::Direction const dir,
350 FontInfo const & f, QFont ff)
352 setQPainterPen(computeColor(f.realColor()));
356 /* In LyX, the character direction is forced by the language.
357 * Therefore, we have to signal that fact to Qt.
360 /* Use unicode override characters to enforce drawing direction
361 * Source: http://www.iamcal.com/understanding-bidirectional-text/
364 // Right-to-left override: forces to draw text right-to-left
365 str = QChar(0x202E) + str;
367 // Left-to-right override: forces to draw text left-to-right
368 str = QChar(0x202D) + str;
371 /* This looks like a cleaner solution, but it has drawbacks
372 * - does not work reliably (Mac OS X, ...)
373 * - it is not really documented
374 * Keep it here for now, in case it can be helpful
376 //This is much stronger than setLayoutDirection.
379 flag = Qt::TextForceRightToLeft;
381 flag = Qt::TextForceLeftToRight;
382 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
383 flag | Qt::TextDontClip,
389 void GuiPainter::text(int x, int y, docstring const & s,
390 FontInfo const & f, Direction const dir,
391 double const wordspacing, double const tw)
393 //LYXERR0("text: x=" << x << ", s=" << s);
394 if (s.empty() || !isDrawingEnabled())
397 /* Caution: The following ucs4 to QString conversions work for symbol fonts
398 only because they are no real conversions but simple casts in reality.
399 When we want to draw a symbol or calculate the metrics we pass the position
400 of the symbol in the font (as given in lib/symbols) as a char_type to the
401 frontend. This is just wrong, because the symbol is no UCS4 character at
402 all. You can think of this number as the code point of the symbol in a
403 custom symbol encoding. It works because this char_type is later on again
404 interpreted as a position in the font.
405 The correct solution would be to have extra functions for symbols, but that
406 would require to duplicate a lot of frontend and mathed support code.
408 QString str = toqstr(s);
411 // HACK: QT3 refuses to show single compose characters
412 // Still needed with Qt4?
413 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
417 QFont ff = getFont(f);
418 ff.setWordSpacing(wordspacing);
419 GuiFontMetrics const & fm = getFontMetrics(f);
423 // Note that we have to take in account space stretching (word spacing)
424 textwidth = fm.width(s) +
425 static_cast<int>(fm.countExpanders(s) * wordspacing);
427 textwidth = static_cast<int>(tw);
429 textDecoration(f, x, y, textwidth);
431 if (use_pixmap_cache_) {
433 QString key = generateStringSignature(str, f, wordspacing);
435 // Warning: Left bearing is in general negative! Only the case
436 // where left bearing is negative is of interest WRT the
437 // pixmap width and the text x-position.
438 // Only the left bearing of the first character is important
439 // as we always write from left to right, even for
440 // right-to-left languages.
441 // FIXME: this is probably broken for RTL now that we draw full strings.
442 // Morover the first/last element is possibly not the right one since the glyph may have changed.
443 int const lb = min(fm.lbearing(s[0]), 0);
444 int const mA = fm.maxAscent();
445 if (QPixmapCache::find(key, pm)) {
446 // Draw the cached pixmap.
447 drawPixmap(x + lb, y - mA, pm);
451 // Only the right bearing of the last character is
452 // important as we always write from left to right,
453 // even for right-to-left languages.
454 int const rb = fm.rbearing(s[s.size()-1]);
455 int const w = textwidth + rb - lb;
456 int const mD = fm.maxDescent();
457 int const h = mA + mD;
458 if (w > 0 && h > 0) {
459 pm = QPixmap(static_cast<int>(pixelRatio() * w),
460 static_cast<int>(pixelRatio() * h));
461 #if QT_VERSION >= 0x050000
462 pm.setDevicePixelRatio(pixelRatio());
464 pm.fill(Qt::transparent);
465 GuiPainter p(&pm, pixelRatio());
466 p.do_drawText(-lb, mA, str, dir, f, ff);
467 QPixmapCache::insert(key, pm);
468 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
469 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
472 // Draw the new cached pixmap.
473 drawPixmap(x + lb, y - mA, pm);
474 //rectangle(x-lb, y-mA, w, h, Color_green);
479 // don't use the pixmap cache
480 setQPainterPen(computeColor(f.realColor()));
482 shared_ptr<QTextLayout const> ptl =
483 fm.getTextLayout(s, dir == RtL, wordspacing);
484 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
491 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
492 // << " at " << x << "," << y);
496 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
497 double const wordspacing, double const tw)
499 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
504 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
505 Color other, size_type const from, size_type const to,
506 double const wordspacing, double const tw)
508 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
509 FontInfo fi = f.fontInfo();
510 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
513 int const ascent = fm.maxAscent();
514 int const height = fm.maxAscent() + fm.maxDescent();
515 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
516 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
520 // First the part in other color
521 Color const orig = fi.realColor();
522 fi.setPaintColor(other);
523 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
525 text(x, y, str, fi, dir, wordspacing, tw);
527 // Then the part in normal color
528 // Note that in Qt5, it is not possible to use Qt::UniteClip,
529 // therefore QRegion is used.
530 fi.setPaintColor(orig);
531 QRegion region(viewport());
532 setClipRegion(region - clip);
533 text(x, y, str, fi, dir, wordspacing, tw);
538 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
540 if (f.underbar() == FONT_ON)
541 underline(f, x, y, width);
542 if (f.strikeout() == FONT_ON)
543 strikeoutLine(f, x, y, width);
544 if (f.xout() == FONT_ON)
545 crossoutLines(f, x, y, width);
546 if (f.uuline() == FONT_ON)
547 doubleUnderline(f, x, y, width);
548 if (f.uwave() == FONT_ON)
549 // f.color() doesn't work on some circumstances
550 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
554 static int max(int a, int b) { return a > b ? a : b; }
557 void GuiPainter::rectText(int x, int y, docstring const & str,
558 FontInfo const & font, Color back, Color frame)
560 int width, ascent, descent;
562 FontMetrics const & fm = theFontMetrics(font);
563 fm.rectText(str, width, ascent, descent);
565 if (back != Color_none)
566 fillRectangle(x + 1, y - ascent + 1, width - 1,
567 ascent + descent - 1, back);
569 if (frame != Color_none)
570 rectangle(x, y - ascent, width, ascent + descent, frame);
572 // FIXME: let offset depend on font
573 text(x + 3, y, str, font);
577 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
578 FontInfo const & font, Color back, Color frame, int offset)
580 int width, ascent, descent;
582 FontMetrics const & fm = theFontMetrics(font);
583 fm.buttonText(s, offset, width, ascent, descent);
585 static int const d = offset / 2;
587 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
588 ascent + descent - 1, back);
589 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
590 text(x + offset, baseline, s, font);
594 int GuiPainter::preeditText(int x, int y, char_type c,
595 FontInfo const & font, preedit_style style)
597 FontInfo temp_font = font;
598 FontMetrics const & fm = theFontMetrics(font);
599 int ascent = fm.maxAscent();
600 int descent = fm.maxDescent();
601 int height = ascent + descent;
602 int width = fm.width(c);
605 case preedit_default:
606 // default unselecting mode.
607 fillRectangle(x, y - height + 1, width, height, Color_background);
608 dashedUnderline(font, x, y - descent + 1, width);
610 case preedit_selecting:
611 // We are in selecting mode: white text on black background.
612 fillRectangle(x, y - height + 1, width, height, Color_black);
613 temp_font.setColor(Color_white);
616 // The character comes with a cursor.
617 fillRectangle(x, y - height + 1, width, height, Color_background);
618 underline(font, x, y - descent + 1, width);
621 text(x, y - descent + 1, c, temp_font);
627 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
630 FontMetrics const & fm = theFontMetrics(f);
631 int const pos = fm.underlinePos();
633 line(x, y + pos, x + width, y + pos,
634 f.realColor(), ls, fm.lineWidth());
638 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
640 FontMetrics const & fm = theFontMetrics(f);
641 int const pos = fm.strikeoutPos();
643 line(x, y - pos, x + width, y - pos,
644 f.realColor(), line_solid, fm.lineWidth());
648 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
651 tmpf.setXout(FONT_OFF);
653 // the definition of \xout in ulem.sty is
654 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
655 // Let's mimick it somewhat.
656 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
657 for (int i = 0 ; i < iround(width / offset) ; ++i)
658 text(x + iround(i * offset), y, '/', tmpf);
662 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
664 FontMetrics const & fm = theFontMetrics(f);
665 int const pos1 = fm.underlinePos() + fm.lineWidth();
666 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
668 line(x, y + pos1, x + width, y + pos1,
669 f.realColor(), line_solid, fm.lineWidth());
670 line(x, y + pos2, x + width, y + pos2,
671 f.realColor(), line_solid, fm.lineWidth());
675 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
677 FontMetrics const & fm = theFontMetrics(f);
679 int const below = max(fm.maxDescent() / 2, 2);
680 int height = max((fm.maxDescent() / 4) - 1, 1);
685 for (int n = 0; n != height; ++n)
686 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
690 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
692 setQPainterPen(computeColor(col));
694 int const xend = x + width;
696 //FIXME: I am not sure if Antialiasing gives the best effect.
697 //setRenderHint(Antialiasing, true);
700 drawLine(x, y - height, x + step, y + height);
702 drawLine(x, y + height, x + step/2, y + height);
705 //setRenderHint(Antialiasing, false);
708 } // namespace frontend