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::text(int x, int y, docstring const & s, FontInfo const & f)
338 text(x, y, s, f, false, 0.0, 0.0);
342 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
344 setQPainterPen(computeColor(f.realColor()));
348 /* In LyX, the character direction is forced by the language.
349 * Therefore, we have to signal that fact to Qt.
352 /* Use unicode override characters to enforce drawing direction
353 * Source: http://www.iamcal.com/understanding-bidirectional-text/
356 // Right-to-left override: forces to draw text right-to-left
357 str = QChar(0x202E) + str;
359 // Left-to-right override: forces to draw text left-to-right
360 str = QChar(0x202D) + str;
363 /* This looks like a cleaner solution, but it has drawbacks
364 * - does not work reliably (Mac OS X, ...)
365 * - it is not really documented
366 * Keep it here for now, in case it can be helpful
368 //This is much stronger than setLayoutDirection.
369 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
370 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
371 flag | Qt::TextDontClip,
377 void GuiPainter::text(int x, int y, docstring const & s,
378 FontInfo const & f, bool const rtl,
379 double const wordspacing, double const tw)
381 //LYXERR0("text: x=" << x << ", s=" << s);
382 if (s.empty() || !isDrawingEnabled())
385 /* Caution: The following ucs4 to QString conversions work for symbol fonts
386 only because they are no real conversions but simple casts in reality.
387 When we want to draw a symbol or calculate the metrics we pass the position
388 of the symbol in the font (as given in lib/symbols) as a char_type to the
389 frontend. This is just wrong, because the symbol is no UCS4 character at
390 all. You can think of this number as the code point of the symbol in a
391 custom symbol encoding. It works because this char_type is later on again
392 interpreted as a position in the font.
393 The correct solution would be to have extra functions for symbols, but that
394 would require to duplicate a lot of frontend and mathed support code.
396 QString str = toqstr(s);
399 // HACK: QT3 refuses to show single compose characters
400 // Still needed with Qt4?
401 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
405 QFont ff = getFont(f);
406 ff.setWordSpacing(wordspacing);
407 GuiFontMetrics const & fm = getFontMetrics(f);
411 // Note that we have to take in account space stretching (word spacing)
412 textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
414 textwidth = static_cast<int>(tw);
416 textDecoration(f, x, y, textwidth);
418 if (use_pixmap_cache_) {
420 QString key = generateStringSignature(str, f, wordspacing);
422 // Warning: Left bearing is in general negative! Only the case
423 // where left bearing is negative is of interest WRT the
424 // pixmap width and the text x-position.
425 // Only the left bearing of the first character is important
426 // as we always write from left to right, even for
427 // right-to-left languages.
428 // FIXME: this is probably broken for RTL now that we draw full strings.
429 // Morover the first/last element is possibly not the right one since the glyph may have changed.
430 int const lb = min(fm.lbearing(s[0]), 0);
431 int const mA = fm.maxAscent();
432 if (QPixmapCache::find(key, pm)) {
433 // Draw the cached pixmap.
434 drawPixmap(x + lb, y - mA, pm);
438 // Only the right bearing of the last character is
439 // important as we always write from left to right,
440 // even for right-to-left languages.
441 int const rb = fm.rbearing(s[s.size()-1]);
442 int const w = textwidth + rb - lb;
443 int const mD = fm.maxDescent();
444 int const h = mA + mD;
445 if (w > 0 && h > 0) {
446 pm = QPixmap(static_cast<int>(pixelRatio() * w),
447 static_cast<int>(pixelRatio() * h));
448 #if QT_VERSION >= 0x050000
449 pm.setDevicePixelRatio(pixelRatio());
451 pm.fill(Qt::transparent);
452 GuiPainter p(&pm, pixelRatio());
453 p.do_drawText(-lb, mA, str, rtl, f, ff);
454 QPixmapCache::insert(key, pm);
455 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
456 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
459 // Draw the new cached pixmap.
460 drawPixmap(x + lb, y - mA, pm);
461 //rectangle(x-lb, y-mA, w, h, Color_green);
466 // don't use the pixmap cache,
467 do_drawText(x, y, str, rtl, f, ff);
468 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
469 // << " at " << x << "," << y);
473 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
474 double const wordspacing, double const tw)
476 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing, tw);
480 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
481 Color other, size_type const from, size_type const to,
482 double const wordspacing, double const tw)
484 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
485 FontInfo fi = f.fontInfo();
486 bool const rtl = f.isVisibleRightToLeft();
489 int const ascent = fm.maxAscent();
490 int const height = fm.maxAscent() + fm.maxDescent();
491 int xmin = fm.pos2x(str, from, rtl, wordspacing);
492 int xmax = fm.pos2x(str, to, rtl, wordspacing);
496 // First the part in other color
497 Color const orig = fi.realColor();
498 fi.setPaintColor(other);
499 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
501 text(x, y, str, fi, rtl, wordspacing, tw);
503 // Then the part in normal color
504 // Note that in Qt5, it is not possible to use Qt::UniteClip,
505 // therefore QRegion is used.
506 fi.setPaintColor(orig);
507 QRegion region(viewport());
508 setClipRegion(region - clip);
509 text(x, y, str, fi, rtl, wordspacing, tw);
514 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
516 if (f.underbar() == FONT_ON)
517 underline(f, x, y, width);
518 if (f.strikeout() == FONT_ON)
519 strikeoutLine(f, x, y, width);
520 if (f.uuline() == FONT_ON)
521 doubleUnderline(f, x, y, width);
522 if (f.uwave() == FONT_ON)
523 // f.color() doesn't work on some circumstances
524 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
528 static int max(int a, int b) { return a > b ? a : b; }
531 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
534 fillRectangle(x, y, w, h, Color_buttonhoverbg);
536 fillRectangle(x, y, w, h, Color_buttonbg);
537 buttonFrame(x, y, w, h);
541 void GuiPainter::buttonFrame(int x, int y, int w, int h)
543 line(x, y, x, y + h - 1, Color_buttonframe);
544 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
545 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
546 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
550 void GuiPainter::rectText(int x, int y, docstring const & str,
551 FontInfo const & font, Color back, Color frame)
557 FontMetrics const & fm = theFontMetrics(font);
558 fm.rectText(str, width, ascent, descent);
560 if (back != Color_none)
561 fillRectangle(x + 1, y - ascent + 1, width - 1,
562 ascent + descent - 1, back);
564 if (frame != Color_none)
565 rectangle(x, y - ascent, width, ascent + descent, frame);
567 text(x + 3, y, str, font);
571 void GuiPainter::buttonText(int x, int y, docstring const & str,
572 FontInfo const & font, bool mouseHover)
578 FontMetrics const & fm = theFontMetrics(font);
579 fm.buttonText(str, width, ascent, descent);
581 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
583 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
584 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
588 int GuiPainter::preeditText(int x, int y, char_type c,
589 FontInfo const & font, preedit_style style)
591 FontInfo temp_font = font;
592 FontMetrics const & fm = theFontMetrics(font);
593 int ascent = fm.maxAscent();
594 int descent = fm.maxDescent();
595 int height = ascent + descent;
596 int width = fm.width(c);
599 case preedit_default:
600 // default unselecting mode.
601 fillRectangle(x, y - height + 1, width, height, Color_background);
602 dashedUnderline(font, x, y - descent + 1, width);
604 case preedit_selecting:
605 // We are in selecting mode: white text on black background.
606 fillRectangle(x, y - height + 1, width, height, Color_black);
607 temp_font.setColor(Color_white);
610 // The character comes with a cursor.
611 fillRectangle(x, y - height + 1, width, height, Color_background);
612 underline(font, x, y - descent + 1, width);
615 text(x, y - descent + 1, c, temp_font);
621 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
624 FontMetrics const & fm = theFontMetrics(f);
625 int const pos = fm.underlinePos();
627 line(x, y + pos, x + width, y + pos,
628 f.realColor(), ls, fm.lineWidth());
632 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
634 FontMetrics const & fm = theFontMetrics(f);
635 int const pos = fm.strikeoutPos();
637 line(x, y - pos, x + width, y - pos,
638 f.realColor(), line_solid, fm.lineWidth());
642 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
644 FontMetrics const & fm = theFontMetrics(f);
645 int const pos1 = fm.underlinePos() + fm.lineWidth();
646 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
648 line(x, y + pos1, x + width, y + pos1,
649 f.realColor(), line_solid, fm.lineWidth());
650 line(x, y + pos2, x + width, y + pos2,
651 f.realColor(), line_solid, fm.lineWidth());
655 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
657 FontMetrics const & fm = theFontMetrics(f);
659 int const below = max(fm.maxDescent() / 2, 2);
660 int height = max((fm.maxDescent() / 4) - 1, 1);
665 for (int n = 0; n != height; ++n)
666 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
670 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
672 setQPainterPen(computeColor(col));
674 int const xend = x + width;
676 //FIXME: I am not sure if Antialiasing gives the best effect.
677 //setRenderHint(Antialiasing, true);
680 drawLine(x, y - height, x + step, y + height);
682 drawLine(x, y + height, x + step/2, y + height);
685 //setRenderHint(Antialiasing, false);
688 } // namespace frontend