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"
27 #include "insets/Inset.h"
29 #include "support/debug.h"
30 #include "support/lassert.h"
31 #include "support/lyxlib.h"
35 #include <QPixmapCache>
36 #include <QTextLayout>
38 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
39 // drawing text. This is especially useful for older PPC/Mac systems.
40 #if defined(Q_WS_X11) || defined(QPA_XCB)
41 #define USE_PIXMAP_CACHE 0
43 #define USE_PIXMAP_CACHE 1
47 using namespace lyx::support;
52 const int Painter::thin_line = 1;
54 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
55 : QPainter(device), Painter(pixel_ratio),
56 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
58 // new QPainter has default QPen:
59 current_color_ = guiApp->colorCache().get(Color_black);
60 current_ls_ = line_solid;
61 current_lw_ = thin_line;
65 GuiPainter::~GuiPainter()
68 //lyxerr << "GuiPainter::end()" << endl;
72 void GuiPainter::setQPainterPen(QColor const & col,
73 Painter::line_style ls, int lw)
75 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
82 QPen pen = QPainter::pen();
87 case line_solid_aliased:
88 pen.setStyle(Qt::SolidLine); break;
90 pen.setStyle(Qt::DotLine); break;
99 QString GuiPainter::generateStringSignature(QString const & str,
104 sig.append(QChar(static_cast<short>(f.family())));
105 sig.append(QChar(static_cast<short>(f.series())));
106 sig.append(QChar(static_cast<short>(f.realShape())));
107 sig.append(QChar(static_cast<short>(f.size())));
108 Color const & color = f.realColor();
109 sig.append(QChar(static_cast<short>(color.baseColor)));
110 sig.append(QChar(static_cast<short>(color.mergeColor)));
111 sig.append(QString::number(wordspacing));
112 if (!monochrome_min_.empty()) {
113 QColor const & min = monochrome_min_.top();
114 QColor const & max = monochrome_max_.top();
115 sig.append(QChar(static_cast<short>(min.red())));
116 sig.append(QChar(static_cast<short>(min.green())));
117 sig.append(QChar(static_cast<short>(min.blue())));
118 sig.append(QChar(static_cast<short>(max.red())));
119 sig.append(QChar(static_cast<short>(max.green())));
120 sig.append(QChar(static_cast<short>(max.blue())));
126 QColor GuiPainter::computeColor(Color col)
128 return filterColor(guiApp->colorCache().get(col));
132 QColor GuiPainter::filterColor(QColor const & col)
134 if (monochrome_min_.empty())
137 // map into [min,max] interval
138 QColor const & min = monochrome_min_.top();
139 QColor const & max = monochrome_max_.top();
141 qreal v = col.valueF();
142 v *= v; // make it a bit steeper (i.e. darker)
144 qreal minr, ming, minb;
145 qreal maxr, maxg, maxb;
146 min.getRgbF(&minr, &ming, &minb);
147 max.getRgbF(&maxr, &maxg, &maxb);
151 v * (minr - maxr) + maxr,
152 v * (ming - maxg) + maxg,
153 v * (minb - maxb) + maxb);
158 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
160 QColor qmin = filterColor(guiApp->colorCache().get(min));
161 QColor qmax = filterColor(guiApp->colorCache().get(max));
162 monochrome_min_.push(qmin);
163 monochrome_max_.push(qmax);
167 void GuiPainter::leaveMonochromeMode()
169 LASSERT(!monochrome_min_.empty(), return);
170 monochrome_min_.pop();
171 monochrome_max_.pop();
175 void GuiPainter::point(int x, int y, Color col)
177 if (!isDrawingEnabled())
180 setQPainterPen(computeColor(col));
185 void GuiPainter::line(int x1, int y1, int x2, int y2,
190 if (!isDrawingEnabled())
193 setQPainterPen(computeColor(col), ls, lw);
194 bool const do_antialiasing = renderHints() & TextAntialiasing
195 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
196 setRenderHint(Antialiasing, do_antialiasing);
197 drawLine(x1, y1, x2, y2);
198 setRenderHint(Antialiasing, false);
202 void GuiPainter::lines(int const * xp, int const * yp, int np,
208 if (!isDrawingEnabled())
211 // double the size if needed
213 static QVector<QPoint> points(32);
214 if (np > points.size())
215 points.resize(2 * np);
217 // Note: the proper way to not get blurry vertical and horizontal lines is
218 // to add 0.5 to all coordinates.
219 bool antialias = false;
220 for (int i = 0; i < np; ++i) {
221 points[i].setX(xp[i]);
222 points[i].setY(yp[i]);
224 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
226 QColor const color = computeColor(col);
227 setQPainterPen(color, ls, lw);
228 bool const text_is_antialiased = renderHints() & TextAntialiasing;
229 setRenderHint(Antialiasing,
230 antialias && text_is_antialiased && ls != line_solid_aliased);
231 if (fs == fill_none) {
232 drawPolyline(points.data(), np);
234 QBrush const oldbrush = brush();
235 setBrush(QBrush(color));
236 drawPolygon(points.data(), np, fs == fill_oddeven ?
237 Qt::OddEvenFill : Qt::WindingFill);
240 setRenderHint(Antialiasing, false);
244 void GuiPainter::path(int const * xp, int const * yp,
245 int const * c1x, int const * c1y,
246 int const * c2x, int const * c2y,
253 if (!isDrawingEnabled())
257 // This is the starting point, so its control points are meaningless
258 bpath.moveTo(xp[0], yp[0]);
260 for (int i = 1; i < np; ++i) {
261 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
262 c2x[i] == xp[i] && c2y[i] == yp[i];
264 bpath.lineTo(xp[i], yp[i]);
266 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
268 QColor const color = computeColor(col);
269 setQPainterPen(color, ls, lw);
270 bool const text_is_antialiased = renderHints() & TextAntialiasing;
271 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
274 fillPath(bpath, QBrush(color));
275 setRenderHint(Antialiasing, false);
279 void GuiPainter::rectangle(int x, int y, int w, int h,
284 if (!isDrawingEnabled())
287 setQPainterPen(computeColor(col), ls, lw);
288 drawRect(x, y, w, h);
292 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
294 if (!isDrawingEnabled())
297 fillRect(x, y, w, h, guiApp->colorCache().get(col));
301 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
302 int a1, int a2, Color col)
304 if (!isDrawingEnabled())
307 // LyX usings 1/64ths degree, Qt usings 1/16th
308 setQPainterPen(computeColor(col));
309 bool const do_antialiasing = renderHints() & TextAntialiasing;
310 setRenderHint(Antialiasing, do_antialiasing);
311 drawArc(x, y, w, h, a1 / 4, a2 / 4);
312 setRenderHint(Antialiasing, false);
316 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
318 graphics::GuiImage const & qlimage =
319 static_cast<graphics::GuiImage const &>(i);
321 fillRectangle(x, y, w, h, Color_graphicsbg);
323 if (!isDrawingEnabled())
326 QImage const image = qlimage.image();
327 QRectF const drect = QRectF(x, y, w, h);
328 QRectF const srect = QRectF(0, 0, image.width(), image.height());
329 // Bilinear filtering is needed on a rare occasion for instant previews when
330 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
331 // This filter is optimised by qt on pixel-aligned images, so this does not
332 // affect performances in other cases.
333 setRenderHint(SmoothPixmapTransform);
334 drawImage(drect, image, srect);
335 setRenderHint(SmoothPixmapTransform, false);
339 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
341 text(x, y, docstring(1, c), f);
345 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
347 text(x, y, s, f, Auto, 0.0, 0.0);
351 void GuiPainter::do_drawText(int x, int y, QString str,
352 GuiPainter::Direction const dir,
353 FontInfo const & f, QFont ff)
355 setQPainterPen(computeColor(f.realColor()));
359 /* In LyX, the character direction is forced by the language.
360 * Therefore, we have to signal that fact to Qt.
363 /* Use unicode override characters to enforce drawing direction
364 * Source: http://www.iamcal.com/understanding-bidirectional-text/
367 // Right-to-left override: forces to draw text right-to-left
368 str = QChar(0x202E) + str;
370 // Left-to-right override: forces to draw text left-to-right
371 str = QChar(0x202D) + str;
374 /* This looks like a cleaner solution, but it has drawbacks
375 * - does not work reliably (Mac OS X, ...)
376 * - it is not really documented
377 * Keep it here for now, in case it can be helpful
379 //This is much stronger than setLayoutDirection.
382 flag = Qt::TextForceRightToLeft;
384 flag = Qt::TextForceLeftToRight;
385 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
386 flag | Qt::TextDontClip,
392 void GuiPainter::text(int x, int y, docstring const & s,
393 FontInfo const & f, Direction const dir,
394 double const wordspacing, double const tw)
396 //LYXERR0("text: x=" << x << ", s=" << s);
397 if (s.empty() || !isDrawingEnabled())
400 /* Caution: The following ucs4 to QString conversions work for symbol fonts
401 only because they are no real conversions but simple casts in reality.
402 When we want to draw a symbol or calculate the metrics we pass the position
403 of the symbol in the font (as given in lib/symbols) as a char_type to the
404 frontend. This is just wrong, because the symbol is no UCS4 character at
405 all. You can think of this number as the code point of the symbol in a
406 custom symbol encoding. It works because this char_type is later on again
407 interpreted as a position in the font.
408 The correct solution would be to have extra functions for symbols, but that
409 would require to duplicate a lot of frontend and mathed support code.
411 QString str = toqstr(s);
414 // HACK: QT3 refuses to show single compose characters
415 // Still needed with Qt4?
416 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
420 QFont ff = getFont(f);
421 ff.setWordSpacing(wordspacing);
422 GuiFontMetrics const & fm = getFontMetrics(f);
426 // Note that we have to take in account space stretching (word spacing)
427 textwidth = fm.width(s) +
428 static_cast<int>(fm.countExpanders(s) * wordspacing);
430 textwidth = static_cast<int>(tw);
432 textDecoration(f, x, y, textwidth);
434 if (use_pixmap_cache_) {
436 QString key = generateStringSignature(str, f, wordspacing);
438 // Warning: Left bearing is in general negative! Only the case
439 // where left bearing is negative is of interest WRT the
440 // pixmap width and the text x-position.
441 // Only the left bearing of the first character is important
442 // as we always write from left to right, even for
443 // right-to-left languages.
444 // FIXME: this is probably broken for RTL now that we draw full strings.
445 // Morover the first/last element is possibly not the right one since the glyph may have changed.
446 int const lb = min(fm.lbearing(s[0]), 0);
447 int const mA = fm.maxAscent();
448 if (QPixmapCache::find(key, pm)) {
449 // Draw the cached pixmap.
450 drawPixmap(x + lb, y - mA, pm);
454 // Only the right bearing of the last character is
455 // important as we always write from left to right,
456 // even for right-to-left languages.
457 int const rb = fm.rbearing(s[s.size()-1]);
458 int const w = textwidth + rb - lb;
459 int const mD = fm.maxDescent();
460 int const h = mA + mD;
461 if (w > 0 && h > 0) {
462 pm = QPixmap(static_cast<int>(pixelRatio() * w),
463 static_cast<int>(pixelRatio() * h));
464 #if QT_VERSION >= 0x050000
465 pm.setDevicePixelRatio(pixelRatio());
467 pm.fill(Qt::transparent);
468 GuiPainter p(&pm, pixelRatio());
469 p.do_drawText(-lb, mA, str, dir, f, ff);
470 QPixmapCache::insert(key, pm);
471 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
472 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
475 // Draw the new cached pixmap.
476 drawPixmap(x + lb, y - mA, pm);
477 //rectangle(x-lb, y-mA, w, h, Color_green);
482 // don't use the pixmap cache
483 setQPainterPen(computeColor(f.realColor()));
485 shared_ptr<QTextLayout const> ptl =
486 fm.getTextLayout(s, dir == RtL, wordspacing);
487 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
494 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
495 // << " at " << x << "," << y);
499 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
500 double const wordspacing, double const tw)
502 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
507 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
508 Color other, size_type const from, size_type const to,
509 double const wordspacing, double const tw)
511 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
512 FontInfo fi = f.fontInfo();
513 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
516 int const ascent = fm.maxAscent();
517 int const height = fm.maxAscent() + fm.maxDescent();
518 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
519 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
523 // First the part in other color
524 Color const orig = fi.realColor();
525 fi.setPaintColor(other);
526 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
528 text(x, y, str, fi, dir, wordspacing, tw);
530 // Then the part in normal color
531 // Note that in Qt5, it is not possible to use Qt::UniteClip,
532 // therefore QRegion is used.
533 fi.setPaintColor(orig);
534 QRegion region(viewport());
535 setClipRegion(region - clip);
536 text(x, y, str, fi, dir, wordspacing, tw);
541 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
543 if (f.underbar() == FONT_ON)
544 underline(f, x, y, width);
545 if (f.strikeout() == FONT_ON)
546 strikeoutLine(f, x, y, width);
547 if (f.xout() == FONT_ON)
548 crossoutLines(f, x, y, width);
549 if (f.uuline() == FONT_ON)
550 doubleUnderline(f, x, y, width);
551 if (f.uwave() == FONT_ON)
552 // f.color() doesn't work on some circumstances
553 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
557 static int max(int a, int b) { return a > b ? a : b; }
560 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
563 fillRectangle(x, y, w, h, Color_buttonhoverbg);
565 fillRectangle(x, y, w, h, Color_buttonbg);
566 buttonFrame(x, y, w, h);
570 void GuiPainter::buttonFrame(int x, int y, int w, int h)
572 line(x, y, x, y + h - 1, Color_buttonframe);
573 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
574 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
575 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
579 void GuiPainter::rectText(int x, int y, docstring const & str,
580 FontInfo const & font, Color back, Color frame)
586 FontMetrics const & fm = theFontMetrics(font);
587 fm.rectText(str, width, ascent, descent);
589 if (back != Color_none)
590 fillRectangle(x + 1, y - ascent + 1, width - 1,
591 ascent + descent - 1, back);
593 if (frame != Color_none)
594 rectangle(x, y - ascent, width, ascent + descent, frame);
596 text(x + 3, y, str, font);
600 void GuiPainter::buttonText(int x, int y, docstring const & str,
601 FontInfo const & font, bool mouseHover)
607 FontMetrics const & fm = theFontMetrics(font);
608 fm.buttonText(str, width, ascent, descent);
610 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
612 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
613 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
617 int GuiPainter::preeditText(int x, int y, char_type c,
618 FontInfo const & font, preedit_style style)
620 FontInfo temp_font = font;
621 FontMetrics const & fm = theFontMetrics(font);
622 int ascent = fm.maxAscent();
623 int descent = fm.maxDescent();
624 int height = ascent + descent;
625 int width = fm.width(c);
628 case preedit_default:
629 // default unselecting mode.
630 fillRectangle(x, y - height + 1, width, height, Color_background);
631 dashedUnderline(font, x, y - descent + 1, width);
633 case preedit_selecting:
634 // We are in selecting mode: white text on black background.
635 fillRectangle(x, y - height + 1, width, height, Color_black);
636 temp_font.setColor(Color_white);
639 // The character comes with a cursor.
640 fillRectangle(x, y - height + 1, width, height, Color_background);
641 underline(font, x, y - descent + 1, width);
644 text(x, y - descent + 1, c, temp_font);
650 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
653 FontMetrics const & fm = theFontMetrics(f);
654 int const pos = fm.underlinePos();
656 line(x, y + pos, x + width, y + pos,
657 f.realColor(), ls, fm.lineWidth());
661 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
663 FontMetrics const & fm = theFontMetrics(f);
664 int const pos = fm.strikeoutPos();
666 line(x, y - pos, x + width, y - pos,
667 f.realColor(), line_solid, fm.lineWidth());
671 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
674 tmpf.setXout(FONT_OFF);
676 // the definition of \xout in ulem.sty is
677 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
678 // Let's mimick it somewhat.
679 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
680 for (int i = 0 ; i < iround(width / offset) ; ++i)
681 text(x + iround(i * offset), y, '/', tmpf);
685 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
687 FontMetrics const & fm = theFontMetrics(f);
688 int const pos1 = fm.underlinePos() + fm.lineWidth();
689 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
691 line(x, y + pos1, x + width, y + pos1,
692 f.realColor(), line_solid, fm.lineWidth());
693 line(x, y + pos2, x + width, y + pos2,
694 f.realColor(), line_solid, fm.lineWidth());
698 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
700 FontMetrics const & fm = theFontMetrics(f);
702 int const below = max(fm.maxDescent() / 2, 2);
703 int height = max((fm.maxDescent() / 4) - 1, 1);
708 for (int n = 0; n != height; ++n)
709 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
713 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
715 setQPainterPen(computeColor(col));
717 int const xend = x + width;
719 //FIXME: I am not sure if Antialiasing gives the best effect.
720 //setRenderHint(Antialiasing, true);
723 drawLine(x, y - height, x + step, y + height);
725 drawLine(x, y + height, x + step/2, y + height);
728 //setRenderHint(Antialiasing, false);
731 } // namespace frontend