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 = 1;
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,
98 sig.append(QChar(static_cast<short>(f.family())));
99 sig.append(QChar(static_cast<short>(f.series())));
100 sig.append(QChar(static_cast<short>(f.realShape())));
101 sig.append(QChar(static_cast<short>(f.size())));
102 Color const & color = f.realColor();
103 sig.append(QChar(static_cast<short>(color.baseColor)));
104 sig.append(QChar(static_cast<short>(color.mergeColor)));
105 sig.append(QString::number(wordspacing));
106 if (!monochrome_min_.empty()) {
107 QColor const & min = monochrome_min_.top();
108 QColor const & max = monochrome_max_.top();
109 sig.append(QChar(static_cast<short>(min.red())));
110 sig.append(QChar(static_cast<short>(min.green())));
111 sig.append(QChar(static_cast<short>(min.blue())));
112 sig.append(QChar(static_cast<short>(max.red())));
113 sig.append(QChar(static_cast<short>(max.green())));
114 sig.append(QChar(static_cast<short>(max.blue())));
120 QColor GuiPainter::computeColor(Color col)
122 return filterColor(guiApp->colorCache().get(col));
126 QColor GuiPainter::filterColor(QColor const & col)
128 if (monochrome_min_.empty())
131 // map into [min,max] interval
132 QColor const & min = monochrome_min_.top();
133 QColor const & max = monochrome_max_.top();
135 qreal v = col.valueF();
136 v *= v; // make it a bit steeper (i.e. darker)
138 qreal minr, ming, minb;
139 qreal maxr, maxg, maxb;
140 min.getRgbF(&minr, &ming, &minb);
141 max.getRgbF(&maxr, &maxg, &maxb);
145 v * (minr - maxr) + maxr,
146 v * (ming - maxg) + maxg,
147 v * (minb - maxb) + maxb);
152 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
154 QColor qmin = filterColor(guiApp->colorCache().get(min));
155 QColor qmax = filterColor(guiApp->colorCache().get(max));
156 monochrome_min_.push(qmin);
157 monochrome_max_.push(qmax);
161 void GuiPainter::leaveMonochromeMode()
163 LASSERT(!monochrome_min_.empty(), return);
164 monochrome_min_.pop();
165 monochrome_max_.pop();
169 void GuiPainter::point(int x, int y, Color col)
171 if (!isDrawingEnabled())
174 setQPainterPen(computeColor(col));
179 void GuiPainter::line(int x1, int y1, int x2, int y2,
184 if (!isDrawingEnabled())
187 setQPainterPen(computeColor(col), ls, lw);
188 bool const do_antialiasing = renderHints() & TextAntialiasing
189 && x1 != x2 && y1 != y2;
190 setRenderHint(Antialiasing, do_antialiasing);
191 drawLine(x1, y1, x2, y2);
192 setRenderHint(Antialiasing, false);
196 void GuiPainter::lines(int const * xp, int const * yp, int np,
202 if (!isDrawingEnabled())
205 // double the size if needed
207 static QVector<QPoint> points(32);
208 if (np > points.size())
209 points.resize(2 * np);
211 bool antialias = false;
212 for (int i = 0; i < np; ++i) {
213 points[i].setX(xp[i]);
214 points[i].setY(yp[i]);
216 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
218 QColor const color = computeColor(col);
219 setQPainterPen(color, ls, lw);
220 bool const text_is_antialiased = renderHints() & TextAntialiasing;
221 setRenderHint(Antialiasing, antialias && text_is_antialiased);
222 if (fs == fill_none) {
223 drawPolyline(points.data(), np);
225 QBrush const oldbrush = brush();
226 setBrush(QBrush(color));
227 drawPolygon(points.data(), np, fs == fill_oddeven ?
228 Qt::OddEvenFill : Qt::WindingFill);
231 setRenderHint(Antialiasing, false);
235 void GuiPainter::path(int const * xp, int const * yp,
236 int const * c1x, int const * c1y,
237 int const * c2x, int const * c2y,
244 if (!isDrawingEnabled())
248 // This is the starting point, so its control points are meaningless
249 bpath.moveTo(xp[0], yp[0]);
251 for (int i = 1; i < np; ++i) {
252 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
253 c2x[i] == xp[i] && c2y[i] == yp[i];
255 bpath.lineTo(xp[i], yp[i]);
257 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
259 QColor const color = computeColor(col);
260 setQPainterPen(color, ls, lw);
261 bool const text_is_antialiased = renderHints() & TextAntialiasing;
262 setRenderHint(Antialiasing, text_is_antialiased);
265 fillPath(bpath, QBrush(color));
266 setRenderHint(Antialiasing, false);
270 void GuiPainter::rectangle(int x, int y, int w, int h,
275 if (!isDrawingEnabled())
278 setQPainterPen(computeColor(col), ls, lw);
279 drawRect(x, y, w, h);
283 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
285 if (!isDrawingEnabled())
288 fillRect(x, y, w, h, guiApp->colorCache().get(col));
292 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
293 int a1, int a2, Color col)
295 if (!isDrawingEnabled())
298 // LyX usings 1/64ths degree, Qt usings 1/16th
299 setQPainterPen(computeColor(col));
300 bool const do_antialiasing = renderHints() & TextAntialiasing;
301 setRenderHint(Antialiasing, do_antialiasing);
302 drawArc(x, y, w, h, a1 / 4, a2 / 4);
303 setRenderHint(Antialiasing, false);
307 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
309 graphics::GuiImage const & qlimage =
310 static_cast<graphics::GuiImage const &>(i);
312 fillRectangle(x, y, w, h, Color_graphicsbg);
314 if (!isDrawingEnabled())
317 QImage const image = qlimage.image();
318 QRectF const drect = QRectF(x, y, w, h);
319 QRectF const srect = QRectF(0, 0, image.width(), image.height());
320 // Bilinear filtering is needed on a rare occasion for instant previews when
321 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
322 // This filter is optimised by qt on pixel-aligned images, so this does not
323 // affect performances in other cases.
324 setRenderHint(SmoothPixmapTransform);
325 drawImage(drect, image, srect);
326 setRenderHint(SmoothPixmapTransform, false);
330 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
332 text(x, y, docstring(1, c), f);
336 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
338 setQPainterPen(computeColor(f.realColor()));
342 /* In LyX, the character direction is forced by the language.
343 * Therefore, we have to signal that fact to Qt.
346 /* Use unicode override characters to enforce drawing direction
347 * Source: http://www.iamcal.com/understanding-bidirectional-text/
350 // Right-to-left override: forces to draw text right-to-left
351 str = QChar(0x202E) + str;
353 // Left-to-right override: forces to draw text left-to-right
354 str = QChar(0x202D) + str;
357 /* This looks like a cleaner solution, but it has drawbacks
358 * - does not work reliably (Mac OS X, ...)
359 * - it is not really documented
360 * Keep it here for now, in case it can be helpful
362 //This is much stronger than setLayoutDirection.
363 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
364 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
365 flag | Qt::TextDontClip,
371 void GuiPainter::text(int x, int y, docstring const & s,
372 FontInfo const & f, bool const rtl,
373 double const wordspacing)
375 //LYXERR0("text: x=" << x << ", s=" << s);
376 if (s.empty() || !isDrawingEnabled())
379 /* Caution: The following ucs4 to QString conversions work for symbol fonts
380 only because they are no real conversions but simple casts in reality.
381 When we want to draw a symbol or calculate the metrics we pass the position
382 of the symbol in the font (as given in lib/symbols) as a char_type to the
383 frontend. This is just wrong, because the symbol is no UCS4 character at
384 all. You can think of this number as the code point of the symbol in a
385 custom symbol encoding. It works because this char_type is lateron again
386 interpreted as a position in the font again.
387 The correct solution would be to have extra functions for symbols, but that
388 would require to duplicate a lot of frontend and mathed support code.
390 QString str = toqstr(s);
393 // HACK: QT3 refuses to show single compose characters
394 // Still needed with Qt4?
395 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
399 QFont ff = getFont(f);
400 ff.setWordSpacing(wordspacing);
401 GuiFontMetrics const & fm = getFontMetrics(f);
403 // Here we use the font width cache instead of
404 // textwidth = fontMetrics().width(str);
405 // because the above is awfully expensive on MacOSX
406 // Note that we have to take in account space stretching (word spacing)
407 int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
409 textDecoration(f, x, y, textwidth);
411 if (use_pixmap_cache_) {
413 QString key = generateStringSignature(str, f, wordspacing);
415 // Warning: Left bearing is in general negative! Only the case
416 // where left bearing is negative is of interest WRT the
417 // pixmap width and the text x-position.
418 // Only the left bearing of the first character is important
419 // as we always write from left to right, even for
420 // right-to-left languages.
421 // FIXME: this is probably broken for RTL now that we draw full strings.
422 // Morover the first/last element is possibly not the right one since the glyph may have changed.
423 int const lb = min(fm.lbearing(s[0]), 0);
424 int const mA = fm.maxAscent();
425 if (QPixmapCache::find(key, pm)) {
426 // Draw the cached pixmap.
427 drawPixmap(x + lb, y - mA, pm);
431 // Only the right bearing of the last character is
432 // important as we always write from left to right,
433 // even for right-to-left languages.
434 int const rb = fm.rbearing(s[s.size()-1]);
435 int const w = textwidth + rb - lb;
436 int const mD = fm.maxDescent();
437 int const h = mA + mD;
438 if (w > 0 && h > 0) {
439 pm = QPixmap(static_cast<int>(pixelRatio() * w),
440 static_cast<int>(pixelRatio() * h));
441 #if QT_VERSION >= 0x050000
442 pm.setDevicePixelRatio(pixelRatio());
444 pm.fill(Qt::transparent);
445 GuiPainter p(&pm, pixelRatio());
446 p.do_drawText(-lb, mA, str, rtl, f, ff);
447 QPixmapCache::insert(key, pm);
448 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
449 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
452 // Draw the new cached pixmap.
453 drawPixmap(x + lb, y - mA, pm);
454 //rectangle(x-lb, y-mA, w, h, Color_green);
459 // don't use the pixmap cache,
460 do_drawText(x, y, str, rtl, f, ff);
461 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
462 // << " at " << x << "," << y);
466 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
467 double const wordspacing)
469 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
473 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
474 Color other, size_type const from, size_type const to,
475 double const wordspacing)
477 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
478 FontInfo fi = f.fontInfo();
479 bool const rtl = f.isVisibleRightToLeft();
482 int const ascent = fm.maxAscent();
483 int const height = fm.maxAscent() + fm.maxDescent();
484 int xmin = fm.pos2x(str, from, rtl, wordspacing);
485 int xmax = fm.pos2x(str, to, rtl, wordspacing);
489 // First the part in other color
490 Color const orig = fi.realColor();
491 fi.setPaintColor(other);
492 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
494 text(x, y, str, fi, rtl, wordspacing);
496 // Then the part in normal color
497 // Note that in Qt5, it is not possible to use Qt::UniteClip,
498 // therefore QRegion is used.
499 fi.setPaintColor(orig);
500 QRegion region(viewport());
501 setClipRegion(region - clip);
502 text(x, y, str, fi, rtl, wordspacing);
507 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
509 if (f.underbar() == FONT_ON)
510 underline(f, x, y, width);
511 if (f.strikeout() == FONT_ON)
512 strikeoutLine(f, x, y, width);
513 if (f.uuline() == FONT_ON)
514 doubleUnderline(f, x, y, width);
515 if (f.uwave() == FONT_ON)
516 // f.color() doesn't work on some circumstances
517 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
521 static int max(int a, int b) { return a > b ? a : b; }
524 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
527 fillRectangle(x, y, w, h, Color_buttonhoverbg);
529 fillRectangle(x, y, w, h, Color_buttonbg);
530 buttonFrame(x, y, w, h);
534 void GuiPainter::buttonFrame(int x, int y, int w, int h)
536 line(x, y, x, y + h - 1, Color_buttonframe);
537 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
538 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
539 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
543 void GuiPainter::rectText(int x, int y, docstring const & str,
544 FontInfo const & font, Color back, Color frame)
550 FontMetrics const & fm = theFontMetrics(font);
551 fm.rectText(str, width, ascent, descent);
553 if (back != Color_none)
554 fillRectangle(x + 1, y - ascent + 1, width - 1,
555 ascent + descent - 1, back);
557 if (frame != Color_none)
558 rectangle(x, y - ascent, width, ascent + descent, frame);
560 text(x + 3, y, str, font);
564 void GuiPainter::buttonText(int x, int y, docstring const & str,
565 FontInfo const & font, bool mouseHover)
571 FontMetrics const & fm = theFontMetrics(font);
572 fm.buttonText(str, width, ascent, descent);
574 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
576 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
577 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
581 int GuiPainter::preeditText(int x, int y, char_type c,
582 FontInfo const & font, preedit_style style)
584 FontInfo temp_font = font;
585 FontMetrics const & fm = theFontMetrics(font);
586 int ascent = fm.maxAscent();
587 int descent = fm.maxDescent();
588 int height = ascent + descent;
589 int width = fm.width(c);
592 case preedit_default:
593 // default unselecting mode.
594 fillRectangle(x, y - height + 1, width, height, Color_background);
595 dashedUnderline(font, x, y - descent + 1, width);
597 case preedit_selecting:
598 // We are in selecting mode: white text on black background.
599 fillRectangle(x, y - height + 1, width, height, Color_black);
600 temp_font.setColor(Color_white);
603 // The character comes with a cursor.
604 fillRectangle(x, y - height + 1, width, height, Color_background);
605 underline(font, x, y - descent + 1, width);
608 text(x, y - descent + 1, c, temp_font);
614 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
617 FontMetrics const & fm = theFontMetrics(f);
618 int const pos = fm.underlinePos();
620 line(x, y + pos, x + width, y + pos,
621 f.realColor(), ls, fm.lineWidth());
625 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
627 FontMetrics const & fm = theFontMetrics(f);
628 int const pos = fm.strikeoutPos();
630 line(x, y - pos, x + width, y - pos,
631 f.realColor(), line_solid, fm.lineWidth());
635 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
637 FontMetrics const & fm = theFontMetrics(f);
638 int const pos1 = fm.underlinePos() + fm.lineWidth();
639 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
641 line(x, y + pos1, x + width, y + pos1,
642 f.realColor(), line_solid, fm.lineWidth());
643 line(x, y + pos2, x + width, y + pos2,
644 f.realColor(), line_solid, fm.lineWidth());
648 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
650 FontMetrics const & fm = theFontMetrics(f);
652 int const below = max(fm.maxDescent() / 2, 2);
653 int height = max((fm.maxDescent() / 4) - 1, 1);
658 for (int n = 0; n != height; ++n)
659 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
663 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
665 setQPainterPen(computeColor(col));
667 int const xend = x + width;
669 //FIXME: I am not sure if Antialiasing gives the best effect.
670 //setRenderHint(Antialiasing, true);
673 drawLine(x, y - height, x + step, y + height);
675 drawLine(x, y + height, x + step/2, y + height);
678 //setRenderHint(Antialiasing, false);
681 } // namespace frontend