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"
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 = 0;
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52 : QPainter(device), Painter(pixel_ratio),
53 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
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();
83 case line_solid: pen.setStyle(Qt::SolidLine); break;
84 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
93 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
96 sig.append(QChar(static_cast<short>(f.family())));
97 sig.append(QChar(static_cast<short>(f.series())));
98 sig.append(QChar(static_cast<short>(f.realShape())));
99 sig.append(QChar(static_cast<short>(f.size())));
100 Color const & color = f.realColor();
101 sig.append(QChar(static_cast<short>(color.baseColor)));
102 sig.append(QChar(static_cast<short>(color.mergeColor)));
103 if (!monochrome_min_.empty()) {
104 QColor const & min = monochrome_min_.top();
105 QColor const & max = monochrome_max_.top();
106 sig.append(QChar(static_cast<short>(min.red())));
107 sig.append(QChar(static_cast<short>(min.green())));
108 sig.append(QChar(static_cast<short>(min.blue())));
109 sig.append(QChar(static_cast<short>(max.red())));
110 sig.append(QChar(static_cast<short>(max.green())));
111 sig.append(QChar(static_cast<short>(max.blue())));
117 QColor GuiPainter::computeColor(Color col)
119 return filterColor(guiApp->colorCache().get(col));
123 QColor GuiPainter::filterColor(QColor const & col)
125 if (monochrome_min_.empty())
128 // map into [min,max] interval
129 QColor const & min = monochrome_min_.top();
130 QColor const & max = monochrome_max_.top();
132 qreal v = col.valueF();
133 v *= v; // make it a bit steeper (i.e. darker)
135 qreal minr, ming, minb;
136 qreal maxr, maxg, maxb;
137 min.getRgbF(&minr, &ming, &minb);
138 max.getRgbF(&maxr, &maxg, &maxb);
142 v * (minr - maxr) + maxr,
143 v * (ming - maxg) + maxg,
144 v * (minb - maxb) + maxb);
149 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
151 QColor qmin = filterColor(guiApp->colorCache().get(min));
152 QColor qmax = filterColor(guiApp->colorCache().get(max));
153 monochrome_min_.push(qmin);
154 monochrome_max_.push(qmax);
158 void GuiPainter::leaveMonochromeMode()
160 LASSERT(!monochrome_min_.empty(), return);
161 monochrome_min_.pop();
162 monochrome_max_.pop();
166 void GuiPainter::point(int x, int y, Color col)
168 if (!isDrawingEnabled())
171 setQPainterPen(computeColor(col));
176 void GuiPainter::line(int x1, int y1, int x2, int y2,
181 if (!isDrawingEnabled())
184 setQPainterPen(computeColor(col), ls, lw);
185 bool const do_antialiasing = renderHints() & TextAntialiasing
186 && x1 != x2 && y1 != y2;
187 setRenderHint(Antialiasing, do_antialiasing);
188 drawLine(x1, y1, x2, y2);
189 setRenderHint(Antialiasing, false);
193 void GuiPainter::lines(int const * xp, int const * yp, int np,
199 if (!isDrawingEnabled())
202 // double the size if needed
204 static QVector<QPoint> points(32);
205 if (np > points.size())
206 points.resize(2 * np);
208 bool antialias = false;
209 for (int i = 0; i < np; ++i) {
210 points[i].setX(xp[i]);
211 points[i].setY(yp[i]);
213 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
215 QColor const color = computeColor(col);
216 setQPainterPen(color, ls, lw);
217 bool const text_is_antialiased = renderHints() & TextAntialiasing;
218 setRenderHint(Antialiasing, antialias && text_is_antialiased);
219 if (fs == fill_none) {
220 drawPolyline(points.data(), np);
222 QBrush const oldbrush = brush();
223 setBrush(QBrush(color));
224 drawPolygon(points.data(), np, fs == fill_oddeven ?
225 Qt::OddEvenFill : Qt::WindingFill);
228 setRenderHint(Antialiasing, false);
232 void GuiPainter::path(int const * xp, int const * yp,
233 int const * c1x, int const * c1y,
234 int const * c2x, int const * c2y,
241 if (!isDrawingEnabled())
245 // This is the starting point, so its control points are meaningless
246 bpath.moveTo(xp[0], yp[0]);
248 for (int i = 1; i < np; ++i) {
249 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
250 c2x[i] == xp[i] && c2y[i] == yp[i];
252 bpath.lineTo(xp[i], yp[i]);
254 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
256 QColor const color = computeColor(col);
257 setQPainterPen(color, ls, lw);
258 bool const text_is_antialiased = renderHints() & TextAntialiasing;
259 setRenderHint(Antialiasing, text_is_antialiased);
262 fillPath(bpath, QBrush(color));
263 setRenderHint(Antialiasing, false);
267 void GuiPainter::rectangle(int x, int y, int w, int h,
272 if (!isDrawingEnabled())
275 setQPainterPen(computeColor(col), ls, lw);
276 drawRect(x, y, w, h);
280 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
282 if (!isDrawingEnabled())
285 fillRect(x, y, w, h, guiApp->colorCache().get(col));
289 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
290 int a1, int a2, Color col)
292 if (!isDrawingEnabled())
295 // LyX usings 1/64ths degree, Qt usings 1/16th
296 setQPainterPen(computeColor(col));
297 bool const do_antialiasing = renderHints() & TextAntialiasing;
298 setRenderHint(Antialiasing, do_antialiasing);
299 drawArc(x, y, w, h, a1 / 4, a2 / 4);
300 setRenderHint(Antialiasing, false);
304 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
306 graphics::GuiImage const & qlimage =
307 static_cast<graphics::GuiImage const &>(i);
309 fillRectangle(x, y, w, h, Color_graphicsbg);
311 if (!isDrawingEnabled())
314 QImage const image = qlimage.image();
315 QRectF const drect = QRectF(x, y, w, h);
316 QRectF const srect = QRectF(0, 0, image.width(), image.height());
317 drawImage(drect, image, srect);
321 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
323 return text(x, y, docstring(1, c), f);
327 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
329 setQPainterPen(computeColor(f.realColor()));
333 /* In LyX, the character direction is forced by the language.
334 * Therefore, we have to signal that fact to Qt.
337 /* Use unicode override characters to enforce drawing direction
338 * Source: http://www.iamcal.com/understanding-bidirectional-text/
341 // Right-to-left override: forces to draw text right-to-left
342 str = QChar(0x202E) + str;
344 // Left-to-right override: forces to draw text left-to-right
345 str = QChar(0x202D) + str;
348 /* This looks like a cleaner solution, but it has drawbacks
349 * - does not work reliably (Mac OS X, ...)
350 * - it is not really documented
351 * Keep it here for now, in case it can be helpful
353 //This is much stronger than setLayoutDirection.
354 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
355 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
356 flag | Qt::TextDontClip,
362 int GuiPainter::text(int x, int y, docstring const & s,
363 FontInfo const & f, bool const rtl,
364 double const wordspacing)
366 //LYXERR0("text: x=" << x << ", s=" << s);
370 /* Caution: The following ucs4 to QString conversions work for symbol fonts
371 only because they are no real conversions but simple casts in reality.
372 When we want to draw a symbol or calculate the metrics we pass the position
373 of the symbol in the font (as given in lib/symbols) as a char_type to the
374 frontend. This is just wrong, because the symbol is no UCS4 character at
375 all. You can think of this number as the code point of the symbol in a
376 custom symbol encoding. It works because this char_type is lateron again
377 interpreted as a position in the font again.
378 The correct solution would be to have extra functions for symbols, but that
379 would require to duplicate a lot of frontend and mathed support code.
381 QString str = toqstr(s);
384 // HACK: QT3 refuses to show single compose characters
385 // Still needed with Qt4?
386 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
390 QFont ff = getFont(f);
391 ff.setWordSpacing(wordspacing);
392 GuiFontMetrics const & fm = getFontMetrics(f);
394 // Here we use the font width cache instead of
395 // textwidth = fontMetrics().width(str);
396 // because the above is awfully expensive on MacOSX
397 // Note that we have to take in account space stretching (word spacing)
398 int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
400 if (!isDrawingEnabled())
403 textDecoration(f, x, y, textwidth);
405 if (use_pixmap_cache_) {
407 QString key = generateStringSignature(str, f);
409 // Warning: Left bearing is in general negative! Only the case
410 // where left bearing is negative is of interest WRT the
411 // pixmap width and the text x-position.
412 // Only the left bearing of the first character is important
413 // as we always write from left to right, even for
414 // right-to-left languages.
415 // FIXME: this is probably broken for RTL now that we draw full strings.
416 // Morover the first/last element is possibly not the right one since the glyph may have changed.
417 int const lb = min(fm.lbearing(s[0]), 0);
418 int const mA = fm.maxAscent();
419 if (QPixmapCache::find(key, pm)) {
420 // Draw the cached pixmap.
421 drawPixmap(x + lb, y - mA, pm);
425 // Only the right bearing of the last character is
426 // important as we always write from left to right,
427 // even for right-to-left languages.
428 int const rb = fm.rbearing(s[s.size()-1]);
429 int const w = textwidth + rb - lb;
430 int const mD = fm.maxDescent();
431 int const h = mA + mD;
432 if (w > 0 && h > 0) {
433 pm = QPixmap(static_cast<int>(pixelRatio() * w),
434 static_cast<int>(pixelRatio() * h));
435 #if QT_VERSION >= 0x050000
436 pm.setDevicePixelRatio(pixelRatio());
438 pm.fill(Qt::transparent);
439 GuiPainter p(&pm, pixelRatio());
440 p.do_drawText(-lb, mA, str, rtl, f, ff);
441 QPixmapCache::insert(key, pm);
442 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
443 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
446 // Draw the new cached pixmap.
447 drawPixmap(x + lb, y - mA, pm);
448 //rectangle(x-lb, y-mA, w, h, Color_green);
453 // don't use the pixmap cache,
454 do_drawText(x, y, str, rtl, f, ff);
455 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
456 // << " at " << x << "," << y);
461 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
462 double const wordspacing)
464 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
468 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
469 Color other, size_type const from, size_type const to,
470 double const wordspacing)
472 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
473 FontInfo fi = f.fontInfo();
474 bool const rtl = f.isVisibleRightToLeft();
477 int const ascent = fm.maxAscent();
478 int const height = fm.maxAscent() + fm.maxDescent();
479 int xmin = fm.pos2x(str, from, rtl, wordspacing);
480 int xmax = fm.pos2x(str, to, rtl, wordspacing);
484 // First the part in other color
485 Color const orig = fi.realColor();
486 fi.setPaintColor(other);
487 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
489 int const textwidth = text(x, y, str, fi, rtl, wordspacing);
491 // Then the part in normal color
492 // Note that in Qt5, it is not possible to use Qt::UniteClip,
493 // therefore QRegion is used.
494 fi.setPaintColor(orig);
495 QRegion region(viewport());
496 setClipRegion(region - clip);
497 text(x, y, str, fi, rtl, wordspacing);
504 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
506 if (f.underbar() == FONT_ON)
507 underline(f, x, y, width);
508 if (f.strikeout() == FONT_ON)
509 strikeoutLine(f, x, y, width);
510 if (f.uuline() == FONT_ON)
511 doubleUnderline(f, x, y, width);
512 if (f.uwave() == FONT_ON)
513 // f.color() doesn't work on some circumstances
514 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
518 static int max(int a, int b) { return a > b ? a : b; }
521 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
524 fillRectangle(x, y, w, h, Color_buttonhoverbg);
526 fillRectangle(x, y, w, h, Color_buttonbg);
527 buttonFrame(x, y, w, h);
531 void GuiPainter::buttonFrame(int x, int y, int w, int h)
533 line(x, y, x, y + h - 1, Color_buttonframe);
534 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
535 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
536 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
540 void GuiPainter::rectText(int x, int y, docstring const & str,
541 FontInfo const & font, Color back, Color frame)
547 FontMetrics const & fm = theFontMetrics(font);
548 fm.rectText(str, width, ascent, descent);
550 if (back != Color_none)
551 fillRectangle(x + 1, y - ascent + 1, width - 1,
552 ascent + descent - 1, back);
554 if (frame != Color_none)
555 rectangle(x, y - ascent, width, ascent + descent, frame);
557 text(x + 3, y, str, font);
561 void GuiPainter::buttonText(int x, int y, docstring const & str,
562 FontInfo const & font, bool mouseHover)
568 FontMetrics const & fm = theFontMetrics(font);
569 fm.buttonText(str, width, ascent, descent);
571 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
573 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
574 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
578 int GuiPainter::preeditText(int x, int y, char_type c,
579 FontInfo const & font, preedit_style style)
581 FontInfo temp_font = font;
582 FontMetrics const & fm = theFontMetrics(font);
583 int ascent = fm.maxAscent();
584 int descent = fm.maxDescent();
585 int height = ascent + descent;
586 int width = fm.width(c);
589 case preedit_default:
590 // default unselecting mode.
591 fillRectangle(x, y - height + 1, width, height, Color_background);
592 dashedUnderline(font, x, y - descent + 1, width);
594 case preedit_selecting:
595 // We are in selecting mode: white text on black background.
596 fillRectangle(x, y - height + 1, width, height, Color_black);
597 temp_font.setColor(Color_white);
600 // The character comes with a cursor.
601 fillRectangle(x, y - height + 1, width, height, Color_background);
602 underline(font, x, y - descent + 1, width);
605 text(x, y - descent + 1, c, temp_font);
611 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
614 FontMetrics const & fm = theFontMetrics(f);
615 int const pos = fm.underlinePos();
617 line(x, y + pos, x + width, y + pos,
618 f.realColor(), ls, fm.lineWidth());
622 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
624 FontMetrics const & fm = theFontMetrics(f);
625 int const pos = fm.strikeoutPos();
627 line(x, y - pos, x + width, y - pos,
628 f.realColor(), line_solid, fm.lineWidth());
632 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
634 FontMetrics const & fm = theFontMetrics(f);
635 int const pos1 = fm.underlinePos() + fm.lineWidth();
636 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
638 line(x, y + pos1, x + width, y + pos1,
639 f.realColor(), line_solid, fm.lineWidth());
640 line(x, y + pos2, x + width, y + pos2,
641 f.realColor(), line_solid, fm.lineWidth());
645 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
647 FontMetrics const & fm = theFontMetrics(f);
649 int const below = max(fm.maxDescent() / 2, 2);
650 int height = max((fm.maxDescent() / 4) - 1, 1);
655 for (int n = 0; n != height; ++n)
656 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
660 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
662 setQPainterPen(computeColor(col));
664 int const xend = x + width;
666 //FIXME: I am not sure if Antialiasing gives the best effect.
667 //setRenderHint(Antialiasing, true);
670 drawLine(x, y - height, x + step, y + height);
672 drawLine(x, y + height, x + step/2, y + height);
675 //setRenderHint(Antialiasing, false);
678 } // namespace frontend