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, Auto, 0.0, 0.0);
342 void GuiPainter::do_drawText(int x, int y, QString str,
343 GuiPainter::Direction const dir,
344 FontInfo const & f, QFont ff)
346 setQPainterPen(computeColor(f.realColor()));
350 /* In LyX, the character direction is forced by the language.
351 * Therefore, we have to signal that fact to Qt.
354 /* Use unicode override characters to enforce drawing direction
355 * Source: http://www.iamcal.com/understanding-bidirectional-text/
358 // Right-to-left override: forces to draw text right-to-left
359 str = QChar(0x202E) + str;
361 // Left-to-right override: forces to draw text left-to-right
362 str = QChar(0x202D) + str;
365 /* This looks like a cleaner solution, but it has drawbacks
366 * - does not work reliably (Mac OS X, ...)
367 * - it is not really documented
368 * Keep it here for now, in case it can be helpful
370 //This is much stronger than setLayoutDirection.
373 flag = Qt::TextForceRightToLeft;
375 flag = Qt::TextForceLeftToRight;
376 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
377 flag | Qt::TextDontClip,
383 void GuiPainter::text(int x, int y, docstring const & s,
384 FontInfo const & f, Direction const dir,
385 double const wordspacing, double const tw)
387 //LYXERR0("text: x=" << x << ", s=" << s);
388 if (s.empty() || !isDrawingEnabled())
391 /* Caution: The following ucs4 to QString conversions work for symbol fonts
392 only because they are no real conversions but simple casts in reality.
393 When we want to draw a symbol or calculate the metrics we pass the position
394 of the symbol in the font (as given in lib/symbols) as a char_type to the
395 frontend. This is just wrong, because the symbol is no UCS4 character at
396 all. You can think of this number as the code point of the symbol in a
397 custom symbol encoding. It works because this char_type is later on again
398 interpreted as a position in the font.
399 The correct solution would be to have extra functions for symbols, but that
400 would require to duplicate a lot of frontend and mathed support code.
402 QString str = toqstr(s);
405 // HACK: QT3 refuses to show single compose characters
406 // Still needed with Qt4?
407 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
411 QFont ff = getFont(f);
412 ff.setWordSpacing(wordspacing);
413 GuiFontMetrics const & fm = getFontMetrics(f);
417 // Note that we have to take in account space stretching (word spacing)
418 textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
420 textwidth = static_cast<int>(tw);
422 textDecoration(f, x, y, textwidth);
424 if (use_pixmap_cache_) {
426 QString key = generateStringSignature(str, f, wordspacing);
428 // Warning: Left bearing is in general negative! Only the case
429 // where left bearing is negative is of interest WRT the
430 // pixmap width and the text x-position.
431 // Only the left bearing of the first character is important
432 // as we always write from left to right, even for
433 // right-to-left languages.
434 // FIXME: this is probably broken for RTL now that we draw full strings.
435 // Morover the first/last element is possibly not the right one since the glyph may have changed.
436 int const lb = min(fm.lbearing(s[0]), 0);
437 int const mA = fm.maxAscent();
438 if (QPixmapCache::find(key, pm)) {
439 // Draw the cached pixmap.
440 drawPixmap(x + lb, y - mA, pm);
444 // Only the right bearing of the last character is
445 // important as we always write from left to right,
446 // even for right-to-left languages.
447 int const rb = fm.rbearing(s[s.size()-1]);
448 int const w = textwidth + rb - lb;
449 int const mD = fm.maxDescent();
450 int const h = mA + mD;
451 if (w > 0 && h > 0) {
452 pm = QPixmap(static_cast<int>(pixelRatio() * w),
453 static_cast<int>(pixelRatio() * h));
454 #if QT_VERSION >= 0x050000
455 pm.setDevicePixelRatio(pixelRatio());
457 pm.fill(Qt::transparent);
458 GuiPainter p(&pm, pixelRatio());
459 p.do_drawText(-lb, mA, str, dir, f, ff);
460 QPixmapCache::insert(key, pm);
461 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
462 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
465 // Draw the new cached pixmap.
466 drawPixmap(x + lb, y - mA, pm);
467 //rectangle(x-lb, y-mA, w, h, Color_green);
472 // don't use the pixmap cache,
473 do_drawText(x, y, str, dir, f, ff);
474 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
475 // << " at " << x << "," << y);
479 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
480 double const wordspacing, double const tw)
482 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
487 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
488 Color other, size_type const from, size_type const to,
489 double const wordspacing, double const tw)
491 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
492 FontInfo fi = f.fontInfo();
493 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
496 int const ascent = fm.maxAscent();
497 int const height = fm.maxAscent() + fm.maxDescent();
498 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
499 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
503 // First the part in other color
504 Color const orig = fi.realColor();
505 fi.setPaintColor(other);
506 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
508 text(x, y, str, fi, dir, wordspacing, tw);
510 // Then the part in normal color
511 // Note that in Qt5, it is not possible to use Qt::UniteClip,
512 // therefore QRegion is used.
513 fi.setPaintColor(orig);
514 QRegion region(viewport());
515 setClipRegion(region - clip);
516 text(x, y, str, fi, dir, wordspacing, tw);
521 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
523 if (f.underbar() == FONT_ON)
524 underline(f, x, y, width);
525 if (f.strikeout() == FONT_ON)
526 strikeoutLine(f, x, y, width);
527 if (f.uuline() == FONT_ON)
528 doubleUnderline(f, x, y, width);
529 if (f.uwave() == FONT_ON)
530 // f.color() doesn't work on some circumstances
531 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
535 static int max(int a, int b) { return a > b ? a : b; }
538 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
541 fillRectangle(x, y, w, h, Color_buttonhoverbg);
543 fillRectangle(x, y, w, h, Color_buttonbg);
544 buttonFrame(x, y, w, h);
548 void GuiPainter::buttonFrame(int x, int y, int w, int h)
550 line(x, y, x, y + h - 1, Color_buttonframe);
551 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
552 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
553 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
557 void GuiPainter::rectText(int x, int y, docstring const & str,
558 FontInfo const & font, Color back, Color frame)
564 FontMetrics const & fm = theFontMetrics(font);
565 fm.rectText(str, width, ascent, descent);
567 if (back != Color_none)
568 fillRectangle(x + 1, y - ascent + 1, width - 1,
569 ascent + descent - 1, back);
571 if (frame != Color_none)
572 rectangle(x, y - ascent, width, ascent + descent, frame);
574 text(x + 3, y, str, font);
578 void GuiPainter::buttonText(int x, int y, docstring const & str,
579 FontInfo const & font, bool mouseHover)
585 FontMetrics const & fm = theFontMetrics(font);
586 fm.buttonText(str, width, ascent, descent);
588 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
590 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
591 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
595 int GuiPainter::preeditText(int x, int y, char_type c,
596 FontInfo const & font, preedit_style style)
598 FontInfo temp_font = font;
599 FontMetrics const & fm = theFontMetrics(font);
600 int ascent = fm.maxAscent();
601 int descent = fm.maxDescent();
602 int height = ascent + descent;
603 int width = fm.width(c);
606 case preedit_default:
607 // default unselecting mode.
608 fillRectangle(x, y - height + 1, width, height, Color_background);
609 dashedUnderline(font, x, y - descent + 1, width);
611 case preedit_selecting:
612 // We are in selecting mode: white text on black background.
613 fillRectangle(x, y - height + 1, width, height, Color_black);
614 temp_font.setColor(Color_white);
617 // The character comes with a cursor.
618 fillRectangle(x, y - height + 1, width, height, Color_background);
619 underline(font, x, y - descent + 1, width);
622 text(x, y - descent + 1, c, temp_font);
628 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
631 FontMetrics const & fm = theFontMetrics(f);
632 int const pos = fm.underlinePos();
634 line(x, y + pos, x + width, y + pos,
635 f.realColor(), ls, fm.lineWidth());
639 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
641 FontMetrics const & fm = theFontMetrics(f);
642 int const pos = fm.strikeoutPos();
644 line(x, y - pos, x + width, y - pos,
645 f.realColor(), line_solid, fm.lineWidth());
649 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
651 FontMetrics const & fm = theFontMetrics(f);
652 int const pos1 = fm.underlinePos() + fm.lineWidth();
653 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
655 line(x, y + pos1, x + width, y + pos1,
656 f.realColor(), line_solid, fm.lineWidth());
657 line(x, y + pos2, x + width, y + pos2,
658 f.realColor(), line_solid, fm.lineWidth());
662 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
664 FontMetrics const & fm = theFontMetrics(f);
666 int const below = max(fm.maxDescent() / 2, 2);
667 int height = max((fm.maxDescent() / 4) - 1, 1);
672 for (int n = 0; n != height; ++n)
673 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
677 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
679 setQPainterPen(computeColor(col));
681 int const xend = x + width;
683 //FIXME: I am not sure if Antialiasing gives the best effect.
684 //setRenderHint(Antialiasing, true);
687 drawLine(x, y - height, x + step, y + height);
689 drawLine(x, y + height, x + step/2, y + height);
692 //setRenderHint(Antialiasing, false);
695 } // namespace frontend