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"
34 #include <QPixmapCache>
35 #include <QTextLayout>
37 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
38 // drawing text. This is especially useful for older PPC/Mac systems.
39 #if defined(Q_WS_X11) || defined(QPA_XCB)
40 #define USE_PIXMAP_CACHE 0
42 #define USE_PIXMAP_CACHE 1
46 using namespace lyx::support;
51 const int Painter::thin_line = 1;
53 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
54 : QPainter(device), Painter(pixel_ratio),
55 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
57 // new QPainter has default QPen:
58 current_color_ = guiApp->colorCache().get(Color_black);
59 current_ls_ = line_solid;
60 current_lw_ = thin_line;
64 GuiPainter::~GuiPainter()
67 //lyxerr << "GuiPainter::end()" << endl;
71 void GuiPainter::setQPainterPen(QColor const & col,
72 Painter::line_style ls, int lw)
74 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
81 QPen pen = QPainter::pen();
86 case line_solid_aliased:
87 pen.setStyle(Qt::SolidLine); break;
89 pen.setStyle(Qt::DotLine); break;
98 QString GuiPainter::generateStringSignature(QString const & str,
103 sig.append(QChar(static_cast<short>(f.family())));
104 sig.append(QChar(static_cast<short>(f.series())));
105 sig.append(QChar(static_cast<short>(f.realShape())));
106 sig.append(QChar(static_cast<short>(f.size())));
107 Color const & color = f.realColor();
108 sig.append(QChar(static_cast<short>(color.baseColor)));
109 sig.append(QChar(static_cast<short>(color.mergeColor)));
110 sig.append(QString::number(wordspacing));
111 if (!monochrome_min_.empty()) {
112 QColor const & min = monochrome_min_.top();
113 QColor const & max = monochrome_max_.top();
114 sig.append(QChar(static_cast<short>(min.red())));
115 sig.append(QChar(static_cast<short>(min.green())));
116 sig.append(QChar(static_cast<short>(min.blue())));
117 sig.append(QChar(static_cast<short>(max.red())));
118 sig.append(QChar(static_cast<short>(max.green())));
119 sig.append(QChar(static_cast<short>(max.blue())));
125 QColor GuiPainter::computeColor(Color col)
127 return filterColor(guiApp->colorCache().get(col));
131 QColor GuiPainter::filterColor(QColor const & col)
133 if (monochrome_min_.empty())
136 // map into [min,max] interval
137 QColor const & min = monochrome_min_.top();
138 QColor const & max = monochrome_max_.top();
140 qreal v = col.valueF();
141 v *= v; // make it a bit steeper (i.e. darker)
143 qreal minr, ming, minb;
144 qreal maxr, maxg, maxb;
145 min.getRgbF(&minr, &ming, &minb);
146 max.getRgbF(&maxr, &maxg, &maxb);
150 v * (minr - maxr) + maxr,
151 v * (ming - maxg) + maxg,
152 v * (minb - maxb) + maxb);
157 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
159 QColor qmin = filterColor(guiApp->colorCache().get(min));
160 QColor qmax = filterColor(guiApp->colorCache().get(max));
161 monochrome_min_.push(qmin);
162 monochrome_max_.push(qmax);
166 void GuiPainter::leaveMonochromeMode()
168 LASSERT(!monochrome_min_.empty(), return);
169 monochrome_min_.pop();
170 monochrome_max_.pop();
174 void GuiPainter::point(int x, int y, Color col)
176 if (!isDrawingEnabled())
179 setQPainterPen(computeColor(col));
184 void GuiPainter::line(int x1, int y1, int x2, int y2,
189 if (!isDrawingEnabled())
192 setQPainterPen(computeColor(col), ls, lw);
193 bool const do_antialiasing = renderHints() & TextAntialiasing
194 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
195 setRenderHint(Antialiasing, do_antialiasing);
196 drawLine(x1, y1, x2, y2);
197 setRenderHint(Antialiasing, false);
201 void GuiPainter::lines(int const * xp, int const * yp, int np,
207 if (!isDrawingEnabled())
210 // double the size if needed
212 static QVector<QPoint> points(32);
213 if (np > points.size())
214 points.resize(2 * np);
216 bool antialias = false;
217 for (int i = 0; i < np; ++i) {
218 points[i].setX(xp[i]);
219 points[i].setY(yp[i]);
221 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
223 QColor const color = computeColor(col);
224 setQPainterPen(color, ls, lw);
225 bool const text_is_antialiased = renderHints() & TextAntialiasing;
226 setRenderHint(Antialiasing,
227 antialias && text_is_antialiased && ls != line_solid_aliased);
228 if (fs == fill_none) {
229 drawPolyline(points.data(), np);
231 QBrush const oldbrush = brush();
232 setBrush(QBrush(color));
233 drawPolygon(points.data(), np, fs == fill_oddeven ?
234 Qt::OddEvenFill : Qt::WindingFill);
237 setRenderHint(Antialiasing, false);
241 void GuiPainter::path(int const * xp, int const * yp,
242 int const * c1x, int const * c1y,
243 int const * c2x, int const * c2y,
250 if (!isDrawingEnabled())
254 // This is the starting point, so its control points are meaningless
255 bpath.moveTo(xp[0], yp[0]);
257 for (int i = 1; i < np; ++i) {
258 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
259 c2x[i] == xp[i] && c2y[i] == yp[i];
261 bpath.lineTo(xp[i], yp[i]);
263 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
265 QColor const color = computeColor(col);
266 setQPainterPen(color, ls, lw);
267 bool const text_is_antialiased = renderHints() & TextAntialiasing;
268 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
271 fillPath(bpath, QBrush(color));
272 setRenderHint(Antialiasing, false);
276 void GuiPainter::rectangle(int x, int y, int w, int h,
281 if (!isDrawingEnabled())
284 setQPainterPen(computeColor(col), ls, lw);
285 drawRect(x, y, w, h);
289 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
291 if (!isDrawingEnabled())
294 fillRect(x, y, w, h, guiApp->colorCache().get(col));
298 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
299 int a1, int a2, Color col)
301 if (!isDrawingEnabled())
304 // LyX usings 1/64ths degree, Qt usings 1/16th
305 setQPainterPen(computeColor(col));
306 bool const do_antialiasing = renderHints() & TextAntialiasing;
307 setRenderHint(Antialiasing, do_antialiasing);
308 drawArc(x, y, w, h, a1 / 4, a2 / 4);
309 setRenderHint(Antialiasing, false);
313 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
315 graphics::GuiImage const & qlimage =
316 static_cast<graphics::GuiImage const &>(i);
318 fillRectangle(x, y, w, h, Color_graphicsbg);
320 if (!isDrawingEnabled())
323 QImage const image = qlimage.image();
324 QRectF const drect = QRectF(x, y, w, h);
325 QRectF const srect = QRectF(0, 0, image.width(), image.height());
326 // Bilinear filtering is needed on a rare occasion for instant previews when
327 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
328 // This filter is optimised by qt on pixel-aligned images, so this does not
329 // affect performances in other cases.
330 setRenderHint(SmoothPixmapTransform);
331 drawImage(drect, image, srect);
332 setRenderHint(SmoothPixmapTransform, false);
336 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
338 text(x, y, docstring(1, c), f);
342 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
344 text(x, y, s, f, Auto, 0.0, 0.0);
348 void GuiPainter::do_drawText(int x, int y, QString str,
349 GuiPainter::Direction const dir,
350 FontInfo const & f, QFont ff)
352 setQPainterPen(computeColor(f.realColor()));
356 /* In LyX, the character direction is forced by the language.
357 * Therefore, we have to signal that fact to Qt.
360 /* Use unicode override characters to enforce drawing direction
361 * Source: http://www.iamcal.com/understanding-bidirectional-text/
364 // Right-to-left override: forces to draw text right-to-left
365 str = QChar(0x202E) + str;
367 // Left-to-right override: forces to draw text left-to-right
368 str = QChar(0x202D) + str;
371 /* This looks like a cleaner solution, but it has drawbacks
372 * - does not work reliably (Mac OS X, ...)
373 * - it is not really documented
374 * Keep it here for now, in case it can be helpful
376 //This is much stronger than setLayoutDirection.
379 flag = Qt::TextForceRightToLeft;
381 flag = Qt::TextForceLeftToRight;
382 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
383 flag | Qt::TextDontClip,
389 void GuiPainter::text(int x, int y, docstring const & s,
390 FontInfo const & f, Direction const dir,
391 double const wordspacing, double const tw)
393 //LYXERR0("text: x=" << x << ", s=" << s);
394 if (s.empty() || !isDrawingEnabled())
397 /* Caution: The following ucs4 to QString conversions work for symbol fonts
398 only because they are no real conversions but simple casts in reality.
399 When we want to draw a symbol or calculate the metrics we pass the position
400 of the symbol in the font (as given in lib/symbols) as a char_type to the
401 frontend. This is just wrong, because the symbol is no UCS4 character at
402 all. You can think of this number as the code point of the symbol in a
403 custom symbol encoding. It works because this char_type is later on again
404 interpreted as a position in the font.
405 The correct solution would be to have extra functions for symbols, but that
406 would require to duplicate a lot of frontend and mathed support code.
408 QString str = toqstr(s);
411 // HACK: QT3 refuses to show single compose characters
412 // Still needed with Qt4?
413 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
417 QFont ff = getFont(f);
418 ff.setWordSpacing(wordspacing);
419 GuiFontMetrics const & fm = getFontMetrics(f);
423 // Note that we have to take in account space stretching (word spacing)
424 textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
426 textwidth = static_cast<int>(tw);
428 textDecoration(f, x, y, textwidth);
430 if (use_pixmap_cache_) {
432 QString key = generateStringSignature(str, f, wordspacing);
434 // Warning: Left bearing is in general negative! Only the case
435 // where left bearing is negative is of interest WRT the
436 // pixmap width and the text x-position.
437 // Only the left bearing of the first character is important
438 // as we always write from left to right, even for
439 // right-to-left languages.
440 // FIXME: this is probably broken for RTL now that we draw full strings.
441 // Morover the first/last element is possibly not the right one since the glyph may have changed.
442 int const lb = min(fm.lbearing(s[0]), 0);
443 int const mA = fm.maxAscent();
444 if (QPixmapCache::find(key, pm)) {
445 // Draw the cached pixmap.
446 drawPixmap(x + lb, y - mA, pm);
450 // Only the right bearing of the last character is
451 // important as we always write from left to right,
452 // even for right-to-left languages.
453 int const rb = fm.rbearing(s[s.size()-1]);
454 int const w = textwidth + rb - lb;
455 int const mD = fm.maxDescent();
456 int const h = mA + mD;
457 if (w > 0 && h > 0) {
458 pm = QPixmap(static_cast<int>(pixelRatio() * w),
459 static_cast<int>(pixelRatio() * h));
460 #if QT_VERSION >= 0x050000
461 pm.setDevicePixelRatio(pixelRatio());
463 pm.fill(Qt::transparent);
464 GuiPainter p(&pm, pixelRatio());
465 p.do_drawText(-lb, mA, str, dir, f, ff);
466 QPixmapCache::insert(key, pm);
467 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
468 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
471 // Draw the new cached pixmap.
472 drawPixmap(x + lb, y - mA, pm);
473 //rectangle(x-lb, y-mA, w, h, Color_green);
478 // don't use the pixmap cache,
479 do_drawText(x, y, str, dir, f, ff);
480 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
481 // << " at " << x << "," << y);
485 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
486 double const wordspacing, double const tw)
488 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
493 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
494 Color other, size_type const from, size_type const to,
495 double const wordspacing, double const tw)
497 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
498 FontInfo fi = f.fontInfo();
499 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
502 int const ascent = fm.maxAscent();
503 int const height = fm.maxAscent() + fm.maxDescent();
504 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
505 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
509 // First the part in other color
510 Color const orig = fi.realColor();
511 fi.setPaintColor(other);
512 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
514 text(x, y, str, fi, dir, wordspacing, tw);
516 // Then the part in normal color
517 // Note that in Qt5, it is not possible to use Qt::UniteClip,
518 // therefore QRegion is used.
519 fi.setPaintColor(orig);
520 QRegion region(viewport());
521 setClipRegion(region - clip);
522 text(x, y, str, fi, dir, wordspacing, tw);
527 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
529 if (f.underbar() == FONT_ON)
530 underline(f, x, y, width);
531 if (f.strikeout() == FONT_ON)
532 strikeoutLine(f, x, y, width);
533 if (f.uuline() == FONT_ON)
534 doubleUnderline(f, x, y, width);
535 if (f.uwave() == FONT_ON)
536 // f.color() doesn't work on some circumstances
537 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
541 static int max(int a, int b) { return a > b ? a : b; }
544 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
547 fillRectangle(x, y, w, h, Color_buttonhoverbg);
549 fillRectangle(x, y, w, h, Color_buttonbg);
550 buttonFrame(x, y, w, h);
554 void GuiPainter::buttonFrame(int x, int y, int w, int h)
556 line(x, y, x, y + h - 1, Color_buttonframe);
557 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
558 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
559 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
563 void GuiPainter::rectText(int x, int y, docstring const & str,
564 FontInfo const & font, Color back, Color frame)
570 FontMetrics const & fm = theFontMetrics(font);
571 fm.rectText(str, width, ascent, descent);
573 if (back != Color_none)
574 fillRectangle(x + 1, y - ascent + 1, width - 1,
575 ascent + descent - 1, back);
577 if (frame != Color_none)
578 rectangle(x, y - ascent, width, ascent + descent, frame);
580 text(x + 3, y, str, font);
584 void GuiPainter::buttonText(int x, int y, docstring const & str,
585 FontInfo const & font, bool mouseHover)
591 FontMetrics const & fm = theFontMetrics(font);
592 fm.buttonText(str, width, ascent, descent);
594 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
596 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
597 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
601 int GuiPainter::preeditText(int x, int y, char_type c,
602 FontInfo const & font, preedit_style style)
604 FontInfo temp_font = font;
605 FontMetrics const & fm = theFontMetrics(font);
606 int ascent = fm.maxAscent();
607 int descent = fm.maxDescent();
608 int height = ascent + descent;
609 int width = fm.width(c);
612 case preedit_default:
613 // default unselecting mode.
614 fillRectangle(x, y - height + 1, width, height, Color_background);
615 dashedUnderline(font, x, y - descent + 1, width);
617 case preedit_selecting:
618 // We are in selecting mode: white text on black background.
619 fillRectangle(x, y - height + 1, width, height, Color_black);
620 temp_font.setColor(Color_white);
623 // The character comes with a cursor.
624 fillRectangle(x, y - height + 1, width, height, Color_background);
625 underline(font, x, y - descent + 1, width);
628 text(x, y - descent + 1, c, temp_font);
634 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
637 FontMetrics const & fm = theFontMetrics(f);
638 int const pos = fm.underlinePos();
640 line(x, y + pos, x + width, y + pos,
641 f.realColor(), ls, fm.lineWidth());
645 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
647 FontMetrics const & fm = theFontMetrics(f);
648 int const pos = fm.strikeoutPos();
650 line(x, y - pos, x + width, y - pos,
651 f.realColor(), line_solid, fm.lineWidth());
655 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
657 FontMetrics const & fm = theFontMetrics(f);
658 int const pos1 = fm.underlinePos() + fm.lineWidth();
659 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
661 line(x, y + pos1, x + width, y + pos1,
662 f.realColor(), line_solid, fm.lineWidth());
663 line(x, y + pos2, x + width, y + pos2,
664 f.realColor(), line_solid, fm.lineWidth());
668 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
670 FontMetrics const & fm = theFontMetrics(f);
672 int const below = max(fm.maxDescent() / 2, 2);
673 int height = max((fm.maxDescent() / 4) - 1, 1);
678 for (int n = 0; n != height; ++n)
679 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
683 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
685 setQPainterPen(computeColor(col));
687 int const xend = x + width;
689 //FIXME: I am not sure if Antialiasing gives the best effect.
690 //setRenderHint(Antialiasing, true);
693 drawLine(x, y - height, x + step, y + height);
695 drawLine(x, y + height, x + step/2, y + height);
698 //setRenderHint(Antialiasing, false);
701 } // namespace frontend