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 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
332 return 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 int 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);
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 if (!isDrawingEnabled())
412 textDecoration(f, x, y, textwidth);
414 if (use_pixmap_cache_) {
416 QString key = generateStringSignature(str, f, wordspacing);
418 // Warning: Left bearing is in general negative! Only the case
419 // where left bearing is negative is of interest WRT the
420 // pixmap width and the text x-position.
421 // Only the left bearing of the first character is important
422 // as we always write from left to right, even for
423 // right-to-left languages.
424 // FIXME: this is probably broken for RTL now that we draw full strings.
425 // Morover the first/last element is possibly not the right one since the glyph may have changed.
426 int const lb = min(fm.lbearing(s[0]), 0);
427 int const mA = fm.maxAscent();
428 if (QPixmapCache::find(key, pm)) {
429 // Draw the cached pixmap.
430 drawPixmap(x + lb, y - mA, pm);
434 // Only the right bearing of the last character is
435 // important as we always write from left to right,
436 // even for right-to-left languages.
437 int const rb = fm.rbearing(s[s.size()-1]);
438 int const w = textwidth + rb - lb;
439 int const mD = fm.maxDescent();
440 int const h = mA + mD;
441 if (w > 0 && h > 0) {
442 pm = QPixmap(static_cast<int>(pixelRatio() * w),
443 static_cast<int>(pixelRatio() * h));
444 #if QT_VERSION >= 0x050000
445 pm.setDevicePixelRatio(pixelRatio());
447 pm.fill(Qt::transparent);
448 GuiPainter p(&pm, pixelRatio());
449 p.do_drawText(-lb, mA, str, rtl, f, ff);
450 QPixmapCache::insert(key, pm);
451 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
452 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
455 // Draw the new cached pixmap.
456 drawPixmap(x + lb, y - mA, pm);
457 //rectangle(x-lb, y-mA, w, h, Color_green);
462 // don't use the pixmap cache,
463 do_drawText(x, y, str, rtl, f, ff);
464 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
465 // << " at " << x << "," << y);
470 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
471 double const wordspacing)
473 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
477 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
478 Color other, size_type const from, size_type const to,
479 double const wordspacing)
481 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
482 FontInfo fi = f.fontInfo();
483 bool const rtl = f.isVisibleRightToLeft();
486 int const ascent = fm.maxAscent();
487 int const height = fm.maxAscent() + fm.maxDescent();
488 int xmin = fm.pos2x(str, from, rtl, wordspacing);
489 int xmax = fm.pos2x(str, to, rtl, wordspacing);
493 // First the part in other color
494 Color const orig = fi.realColor();
495 fi.setPaintColor(other);
496 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
498 int const textwidth = text(x, y, str, fi, rtl, wordspacing);
500 // Then the part in normal color
501 // Note that in Qt5, it is not possible to use Qt::UniteClip,
502 // therefore QRegion is used.
503 fi.setPaintColor(orig);
504 QRegion region(viewport());
505 setClipRegion(region - clip);
506 text(x, y, str, fi, rtl, wordspacing);
513 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
515 if (f.underbar() == FONT_ON)
516 underline(f, x, y, width);
517 if (f.strikeout() == FONT_ON)
518 strikeoutLine(f, x, y, width);
519 if (f.uuline() == FONT_ON)
520 doubleUnderline(f, x, y, width);
521 if (f.uwave() == FONT_ON)
522 // f.color() doesn't work on some circumstances
523 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
527 static int max(int a, int b) { return a > b ? a : b; }
530 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
533 fillRectangle(x, y, w, h, Color_buttonhoverbg);
535 fillRectangle(x, y, w, h, Color_buttonbg);
536 buttonFrame(x, y, w, h);
540 void GuiPainter::buttonFrame(int x, int y, int w, int h)
542 line(x, y, x, y + h - 1, Color_buttonframe);
543 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
544 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
545 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
549 void GuiPainter::rectText(int x, int y, docstring const & str,
550 FontInfo const & font, Color back, Color frame)
556 FontMetrics const & fm = theFontMetrics(font);
557 fm.rectText(str, width, ascent, descent);
559 if (back != Color_none)
560 fillRectangle(x + 1, y - ascent + 1, width - 1,
561 ascent + descent - 1, back);
563 if (frame != Color_none)
564 rectangle(x, y - ascent, width, ascent + descent, frame);
566 text(x + 3, y, str, font);
570 void GuiPainter::buttonText(int x, int y, docstring const & str,
571 FontInfo const & font, bool mouseHover)
577 FontMetrics const & fm = theFontMetrics(font);
578 fm.buttonText(str, width, ascent, descent);
580 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
582 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
583 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
587 int GuiPainter::preeditText(int x, int y, char_type c,
588 FontInfo const & font, preedit_style style)
590 FontInfo temp_font = font;
591 FontMetrics const & fm = theFontMetrics(font);
592 int ascent = fm.maxAscent();
593 int descent = fm.maxDescent();
594 int height = ascent + descent;
595 int width = fm.width(c);
598 case preedit_default:
599 // default unselecting mode.
600 fillRectangle(x, y - height + 1, width, height, Color_background);
601 dashedUnderline(font, x, y - descent + 1, width);
603 case preedit_selecting:
604 // We are in selecting mode: white text on black background.
605 fillRectangle(x, y - height + 1, width, height, Color_black);
606 temp_font.setColor(Color_white);
609 // The character comes with a cursor.
610 fillRectangle(x, y - height + 1, width, height, Color_background);
611 underline(font, x, y - descent + 1, width);
614 text(x, y - descent + 1, c, temp_font);
620 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
623 FontMetrics const & fm = theFontMetrics(f);
624 int const pos = fm.underlinePos();
626 line(x, y + pos, x + width, y + pos,
627 f.realColor(), ls, fm.lineWidth());
631 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
633 FontMetrics const & fm = theFontMetrics(f);
634 int const pos = fm.strikeoutPos();
636 line(x, y - pos, x + width, y - pos,
637 f.realColor(), line_solid, fm.lineWidth());
641 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
643 FontMetrics const & fm = theFontMetrics(f);
644 int const pos1 = fm.underlinePos() + fm.lineWidth();
645 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
647 line(x, y + pos1, x + width, y + pos1,
648 f.realColor(), line_solid, fm.lineWidth());
649 line(x, y + pos2, x + width, y + pos2,
650 f.realColor(), line_solid, fm.lineWidth());
654 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
656 FontMetrics const & fm = theFontMetrics(f);
658 int const below = max(fm.maxDescent() / 2, 2);
659 int height = max((fm.maxDescent() / 4) - 1, 1);
664 for (int n = 0; n != height; ++n)
665 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
669 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
671 setQPainterPen(computeColor(col));
673 int const xend = x + width;
675 //FIXME: I am not sure if Antialiasing gives the best effect.
676 //setRenderHint(Antialiasing, true);
679 drawLine(x, y - height, x + step, y + height);
681 drawLine(x, y + height, x + step/2, y + height);
684 //setRenderHint(Antialiasing, false);
687 } // namespace frontend