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/lassert.h"
30 #include "support/debug.h"
34 #include <QPixmapCache>
35 #include <QTextLayout>
37 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
38 // drawing text. This is especially useful for older PPC/Mac systems.
39 #if defined(Q_WS_X11) || defined(QPA_XCB)
40 #define USE_PIXMAP_CACHE 0
42 #define USE_PIXMAP_CACHE 1
46 using namespace lyx::support;
51 const int Painter::thin_line = 1;
53 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
54 : QPainter(device), Painter(pixel_ratio),
55 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
57 // new QPainter has default QPen:
58 current_color_ = guiApp->colorCache().get(Color_black);
59 current_ls_ = line_solid;
60 current_lw_ = thin_line;
64 GuiPainter::~GuiPainter()
67 //lyxerr << "GuiPainter::end()" << endl;
71 void GuiPainter::setQPainterPen(QColor const & col,
72 Painter::line_style ls, int lw)
74 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
81 QPen pen = QPainter::pen();
85 case line_solid: pen.setStyle(Qt::SolidLine); break;
86 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
95 QString GuiPainter::generateStringSignature(QString const & str,
100 sig.append(QChar(static_cast<short>(f.family())));
101 sig.append(QChar(static_cast<short>(f.series())));
102 sig.append(QChar(static_cast<short>(f.realShape())));
103 sig.append(QChar(static_cast<short>(f.size())));
104 Color const & color = f.realColor();
105 sig.append(QChar(static_cast<short>(color.baseColor)));
106 sig.append(QChar(static_cast<short>(color.mergeColor)));
107 sig.append(QString::number(wordspacing));
108 if (!monochrome_min_.empty()) {
109 QColor const & min = monochrome_min_.top();
110 QColor const & max = monochrome_max_.top();
111 sig.append(QChar(static_cast<short>(min.red())));
112 sig.append(QChar(static_cast<short>(min.green())));
113 sig.append(QChar(static_cast<short>(min.blue())));
114 sig.append(QChar(static_cast<short>(max.red())));
115 sig.append(QChar(static_cast<short>(max.green())));
116 sig.append(QChar(static_cast<short>(max.blue())));
122 QColor GuiPainter::computeColor(Color col)
124 return filterColor(guiApp->colorCache().get(col));
128 QColor GuiPainter::filterColor(QColor const & col)
130 if (monochrome_min_.empty())
133 // map into [min,max] interval
134 QColor const & min = monochrome_min_.top();
135 QColor const & max = monochrome_max_.top();
137 qreal v = col.valueF();
138 v *= v; // make it a bit steeper (i.e. darker)
140 qreal minr, ming, minb;
141 qreal maxr, maxg, maxb;
142 min.getRgbF(&minr, &ming, &minb);
143 max.getRgbF(&maxr, &maxg, &maxb);
147 v * (minr - maxr) + maxr,
148 v * (ming - maxg) + maxg,
149 v * (minb - maxb) + maxb);
154 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
156 QColor qmin = filterColor(guiApp->colorCache().get(min));
157 QColor qmax = filterColor(guiApp->colorCache().get(max));
158 monochrome_min_.push(qmin);
159 monochrome_max_.push(qmax);
163 void GuiPainter::leaveMonochromeMode()
165 LASSERT(!monochrome_min_.empty(), return);
166 monochrome_min_.pop();
167 monochrome_max_.pop();
171 void GuiPainter::point(int x, int y, Color col)
173 if (!isDrawingEnabled())
176 setQPainterPen(computeColor(col));
181 void GuiPainter::line(int x1, int y1, int x2, int y2,
186 if (!isDrawingEnabled())
189 setQPainterPen(computeColor(col), ls, lw);
190 bool const do_antialiasing = renderHints() & TextAntialiasing
191 && x1 != x2 && y1 != y2;
192 setRenderHint(Antialiasing, do_antialiasing);
193 drawLine(x1, y1, x2, y2);
194 setRenderHint(Antialiasing, false);
198 void GuiPainter::lines(int const * xp, int const * yp, int np,
204 if (!isDrawingEnabled())
207 // double the size if needed
209 static QVector<QPoint> points(32);
210 if (np > points.size())
211 points.resize(2 * np);
213 bool antialias = false;
214 for (int i = 0; i < np; ++i) {
215 points[i].setX(xp[i]);
216 points[i].setY(yp[i]);
218 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
220 QColor const color = computeColor(col);
221 setQPainterPen(color, ls, lw);
222 bool const text_is_antialiased = renderHints() & TextAntialiasing;
223 setRenderHint(Antialiasing, antialias && text_is_antialiased);
224 if (fs == fill_none) {
225 drawPolyline(points.data(), np);
227 QBrush const oldbrush = brush();
228 setBrush(QBrush(color));
229 drawPolygon(points.data(), np, fs == fill_oddeven ?
230 Qt::OddEvenFill : Qt::WindingFill);
233 setRenderHint(Antialiasing, false);
237 void GuiPainter::path(int const * xp, int const * yp,
238 int const * c1x, int const * c1y,
239 int const * c2x, int const * c2y,
246 if (!isDrawingEnabled())
250 // This is the starting point, so its control points are meaningless
251 bpath.moveTo(xp[0], yp[0]);
253 for (int i = 1; i < np; ++i) {
254 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
255 c2x[i] == xp[i] && c2y[i] == yp[i];
257 bpath.lineTo(xp[i], yp[i]);
259 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
261 QColor const color = computeColor(col);
262 setQPainterPen(color, ls, lw);
263 bool const text_is_antialiased = renderHints() & TextAntialiasing;
264 setRenderHint(Antialiasing, text_is_antialiased);
267 fillPath(bpath, QBrush(color));
268 setRenderHint(Antialiasing, false);
272 void GuiPainter::rectangle(int x, int y, int w, int h,
277 if (!isDrawingEnabled())
280 setQPainterPen(computeColor(col), ls, lw);
281 drawRect(x, y, w, h);
285 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
287 if (!isDrawingEnabled())
290 fillRect(x, y, w, h, guiApp->colorCache().get(col));
294 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
295 int a1, int a2, Color col)
297 if (!isDrawingEnabled())
300 // LyX usings 1/64ths degree, Qt usings 1/16th
301 setQPainterPen(computeColor(col));
302 bool const do_antialiasing = renderHints() & TextAntialiasing;
303 setRenderHint(Antialiasing, do_antialiasing);
304 drawArc(x, y, w, h, a1 / 4, a2 / 4);
305 setRenderHint(Antialiasing, false);
309 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
311 graphics::GuiImage const & qlimage =
312 static_cast<graphics::GuiImage const &>(i);
314 fillRectangle(x, y, w, h, Color_graphicsbg);
316 if (!isDrawingEnabled())
319 QImage const image = qlimage.image();
320 QRectF const drect = QRectF(x, y, w, h);
321 QRectF const srect = QRectF(0, 0, image.width(), image.height());
322 // Bilinear filtering is needed on a rare occasion for instant previews when
323 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
324 // This filter is optimised by qt on pixel-aligned images, so this does not
325 // affect performances in other cases.
326 setRenderHint(SmoothPixmapTransform);
327 drawImage(drect, image, srect);
328 setRenderHint(SmoothPixmapTransform, false);
332 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
334 text(x, y, docstring(1, c), f);
338 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
340 text(x, y, s, f, Auto, 0.0, 0.0);
344 void GuiPainter::do_drawText(int x, int y, QString str,
345 GuiPainter::Direction const dir,
346 FontInfo const & f, QFont ff)
348 setQPainterPen(computeColor(f.realColor()));
352 /* In LyX, the character direction is forced by the language.
353 * Therefore, we have to signal that fact to Qt.
356 /* Use unicode override characters to enforce drawing direction
357 * Source: http://www.iamcal.com/understanding-bidirectional-text/
360 // Right-to-left override: forces to draw text right-to-left
361 str = QChar(0x202E) + str;
363 // Left-to-right override: forces to draw text left-to-right
364 str = QChar(0x202D) + str;
367 /* This looks like a cleaner solution, but it has drawbacks
368 * - does not work reliably (Mac OS X, ...)
369 * - it is not really documented
370 * Keep it here for now, in case it can be helpful
372 //This is much stronger than setLayoutDirection.
375 flag = Qt::TextForceRightToLeft;
377 flag = Qt::TextForceLeftToRight;
378 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
379 flag | Qt::TextDontClip,
385 void GuiPainter::text(int x, int y, docstring const & s,
386 FontInfo const & f, Direction const dir,
387 double const wordspacing, double const tw)
389 //LYXERR0("text: x=" << x << ", s=" << s);
390 if (s.empty() || !isDrawingEnabled())
393 /* Caution: The following ucs4 to QString conversions work for symbol fonts
394 only because they are no real conversions but simple casts in reality.
395 When we want to draw a symbol or calculate the metrics we pass the position
396 of the symbol in the font (as given in lib/symbols) as a char_type to the
397 frontend. This is just wrong, because the symbol is no UCS4 character at
398 all. You can think of this number as the code point of the symbol in a
399 custom symbol encoding. It works because this char_type is later on again
400 interpreted as a position in the font.
401 The correct solution would be to have extra functions for symbols, but that
402 would require to duplicate a lot of frontend and mathed support code.
404 QString str = toqstr(s);
407 // HACK: QT3 refuses to show single compose characters
408 // Still needed with Qt4?
409 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
413 QFont ff = getFont(f);
414 ff.setWordSpacing(wordspacing);
415 GuiFontMetrics const & fm = getFontMetrics(f);
419 // Note that we have to take in account space stretching (word spacing)
420 textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
422 textwidth = static_cast<int>(tw);
424 textDecoration(f, x, y, textwidth);
426 if (use_pixmap_cache_) {
428 QString key = generateStringSignature(str, f, wordspacing);
430 // Warning: Left bearing is in general negative! Only the case
431 // where left bearing is negative is of interest WRT the
432 // pixmap width and the text x-position.
433 // Only the left bearing of the first character is important
434 // as we always write from left to right, even for
435 // right-to-left languages.
436 // FIXME: this is probably broken for RTL now that we draw full strings.
437 // Morover the first/last element is possibly not the right one since the glyph may have changed.
438 int const lb = min(fm.lbearing(s[0]), 0);
439 int const mA = fm.maxAscent();
440 if (QPixmapCache::find(key, pm)) {
441 // Draw the cached pixmap.
442 drawPixmap(x + lb, y - mA, pm);
446 // Only the right bearing of the last character is
447 // important as we always write from left to right,
448 // even for right-to-left languages.
449 int const rb = fm.rbearing(s[s.size()-1]);
450 int const w = textwidth + rb - lb;
451 int const mD = fm.maxDescent();
452 int const h = mA + mD;
453 if (w > 0 && h > 0) {
454 pm = QPixmap(static_cast<int>(pixelRatio() * w),
455 static_cast<int>(pixelRatio() * h));
456 #if QT_VERSION >= 0x050000
457 pm.setDevicePixelRatio(pixelRatio());
459 pm.fill(Qt::transparent);
460 GuiPainter p(&pm, pixelRatio());
461 p.do_drawText(-lb, mA, str, dir, f, ff);
462 QPixmapCache::insert(key, pm);
463 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
464 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
467 // Draw the new cached pixmap.
468 drawPixmap(x + lb, y - mA, pm);
469 //rectangle(x-lb, y-mA, w, h, Color_green);
474 // don't use the pixmap cache,
475 do_drawText(x, y, str, dir, f, ff);
476 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
477 // << " at " << x << "," << y);
481 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
482 double const wordspacing, double const tw)
484 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
489 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
490 Color other, size_type const from, size_type const to,
491 double const wordspacing, double const tw)
493 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
494 FontInfo fi = f.fontInfo();
495 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
498 int const ascent = fm.maxAscent();
499 int const height = fm.maxAscent() + fm.maxDescent();
500 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
501 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
505 // First the part in other color
506 Color const orig = fi.realColor();
507 fi.setPaintColor(other);
508 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
510 text(x, y, str, fi, dir, wordspacing, tw);
512 // Then the part in normal color
513 // Note that in Qt5, it is not possible to use Qt::UniteClip,
514 // therefore QRegion is used.
515 fi.setPaintColor(orig);
516 QRegion region(viewport());
517 setClipRegion(region - clip);
518 text(x, y, str, fi, dir, wordspacing, tw);
523 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
525 if (f.underbar() == FONT_ON)
526 underline(f, x, y, width);
527 if (f.strikeout() == FONT_ON)
528 strikeoutLine(f, x, y, width);
529 if (f.uuline() == FONT_ON)
530 doubleUnderline(f, x, y, width);
531 if (f.uwave() == FONT_ON)
532 // f.color() doesn't work on some circumstances
533 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
537 static int max(int a, int b) { return a > b ? a : b; }
540 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
543 fillRectangle(x, y, w, h, Color_buttonhoverbg);
545 fillRectangle(x, y, w, h, Color_buttonbg);
546 buttonFrame(x, y, w, h);
550 void GuiPainter::buttonFrame(int x, int y, int w, int h)
552 line(x, y, x, y + h - 1, Color_buttonframe);
553 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
554 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
555 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
559 void GuiPainter::rectText(int x, int y, docstring const & str,
560 FontInfo const & font, Color back, Color frame)
566 FontMetrics const & fm = theFontMetrics(font);
567 fm.rectText(str, width, ascent, descent);
569 if (back != Color_none)
570 fillRectangle(x + 1, y - ascent + 1, width - 1,
571 ascent + descent - 1, back);
573 if (frame != Color_none)
574 rectangle(x, y - ascent, width, ascent + descent, frame);
576 text(x + 3, y, str, font);
580 void GuiPainter::buttonText(int x, int y, docstring const & str,
581 FontInfo const & font, bool mouseHover)
587 FontMetrics const & fm = theFontMetrics(font);
588 fm.buttonText(str, width, ascent, descent);
590 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
592 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
593 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
597 int GuiPainter::preeditText(int x, int y, char_type c,
598 FontInfo const & font, preedit_style style)
600 FontInfo temp_font = font;
601 FontMetrics const & fm = theFontMetrics(font);
602 int ascent = fm.maxAscent();
603 int descent = fm.maxDescent();
604 int height = ascent + descent;
605 int width = fm.width(c);
608 case preedit_default:
609 // default unselecting mode.
610 fillRectangle(x, y - height + 1, width, height, Color_background);
611 dashedUnderline(font, x, y - descent + 1, width);
613 case preedit_selecting:
614 // We are in selecting mode: white text on black background.
615 fillRectangle(x, y - height + 1, width, height, Color_black);
616 temp_font.setColor(Color_white);
619 // The character comes with a cursor.
620 fillRectangle(x, y - height + 1, width, height, Color_background);
621 underline(font, x, y - descent + 1, width);
624 text(x, y - descent + 1, c, temp_font);
630 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
633 FontMetrics const & fm = theFontMetrics(f);
634 int const pos = fm.underlinePos();
636 line(x, y + pos, x + width, y + pos,
637 f.realColor(), ls, fm.lineWidth());
641 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
643 FontMetrics const & fm = theFontMetrics(f);
644 int const pos = fm.strikeoutPos();
646 line(x, y - pos, x + width, y - pos,
647 f.realColor(), line_solid, fm.lineWidth());
651 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
653 FontMetrics const & fm = theFontMetrics(f);
654 int const pos1 = fm.underlinePos() + fm.lineWidth();
655 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
657 line(x, y + pos1, x + width, y + pos1,
658 f.realColor(), line_solid, fm.lineWidth());
659 line(x, y + pos2, x + width, y + pos2,
660 f.realColor(), line_solid, fm.lineWidth());
664 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
666 FontMetrics const & fm = theFontMetrics(f);
668 int const below = max(fm.maxDescent() / 2, 2);
669 int height = max((fm.maxDescent() / 4) - 1, 1);
674 for (int n = 0; n != height; ++n)
675 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
679 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
681 setQPainterPen(computeColor(col));
683 int const xend = x + width;
685 //FIXME: I am not sure if Antialiasing gives the best effect.
686 //setRenderHint(Antialiasing, true);
689 drawLine(x, y - height, x + step, y + height);
691 drawLine(x, y + height, x + step/2, y + height);
694 //setRenderHint(Antialiasing, false);
697 } // namespace frontend