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 // Note: the proper way to not get blurry vertical and horizontal lines is
217 // to add 0.5 to all coordinates.
218 bool antialias = false;
219 for (int i = 0; i < np; ++i) {
220 points[i].setX(xp[i]);
221 points[i].setY(yp[i]);
223 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
225 QColor const color = computeColor(col);
226 setQPainterPen(color, ls, lw);
227 bool const text_is_antialiased = renderHints() & TextAntialiasing;
228 setRenderHint(Antialiasing,
229 antialias && text_is_antialiased && ls != line_solid_aliased);
230 if (fs == fill_none) {
231 drawPolyline(points.data(), np);
233 QBrush const oldbrush = brush();
234 setBrush(QBrush(color));
235 drawPolygon(points.data(), np, fs == fill_oddeven ?
236 Qt::OddEvenFill : Qt::WindingFill);
239 setRenderHint(Antialiasing, false);
243 void GuiPainter::path(int const * xp, int const * yp,
244 int const * c1x, int const * c1y,
245 int const * c2x, int const * c2y,
252 if (!isDrawingEnabled())
256 // This is the starting point, so its control points are meaningless
257 bpath.moveTo(xp[0], yp[0]);
259 for (int i = 1; i < np; ++i) {
260 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
261 c2x[i] == xp[i] && c2y[i] == yp[i];
263 bpath.lineTo(xp[i], yp[i]);
265 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
267 QColor const color = computeColor(col);
268 setQPainterPen(color, ls, lw);
269 bool const text_is_antialiased = renderHints() & TextAntialiasing;
270 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
273 fillPath(bpath, QBrush(color));
274 setRenderHint(Antialiasing, false);
278 void GuiPainter::rectangle(int x, int y, int w, int h,
283 if (!isDrawingEnabled())
286 setQPainterPen(computeColor(col), ls, lw);
287 drawRect(x, y, w, h);
291 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
293 if (!isDrawingEnabled())
296 fillRect(x, y, w, h, guiApp->colorCache().get(col));
300 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
301 int a1, int a2, Color col)
303 if (!isDrawingEnabled())
306 // LyX usings 1/64ths degree, Qt usings 1/16th
307 setQPainterPen(computeColor(col));
308 bool const do_antialiasing = renderHints() & TextAntialiasing;
309 setRenderHint(Antialiasing, do_antialiasing);
310 drawArc(x, y, w, h, a1 / 4, a2 / 4);
311 setRenderHint(Antialiasing, false);
315 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
317 graphics::GuiImage const & qlimage =
318 static_cast<graphics::GuiImage const &>(i);
320 fillRectangle(x, y, w, h, Color_graphicsbg);
322 if (!isDrawingEnabled())
325 QImage const image = qlimage.image();
326 QRectF const drect = QRectF(x, y, w, h);
327 QRectF const srect = QRectF(0, 0, image.width(), image.height());
328 // Bilinear filtering is needed on a rare occasion for instant previews when
329 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
330 // This filter is optimised by qt on pixel-aligned images, so this does not
331 // affect performances in other cases.
332 setRenderHint(SmoothPixmapTransform);
333 drawImage(drect, image, srect);
334 setRenderHint(SmoothPixmapTransform, false);
338 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
340 text(x, y, docstring(1, c), f);
344 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
346 text(x, y, s, f, Auto, 0.0, 0.0);
350 void GuiPainter::do_drawText(int x, int y, QString str,
351 GuiPainter::Direction const dir,
352 FontInfo const & f, QFont ff)
354 setQPainterPen(computeColor(f.realColor()));
358 /* In LyX, the character direction is forced by the language.
359 * Therefore, we have to signal that fact to Qt.
362 /* Use unicode override characters to enforce drawing direction
363 * Source: http://www.iamcal.com/understanding-bidirectional-text/
366 // Right-to-left override: forces to draw text right-to-left
367 str = QChar(0x202E) + str;
369 // Left-to-right override: forces to draw text left-to-right
370 str = QChar(0x202D) + str;
373 /* This looks like a cleaner solution, but it has drawbacks
374 * - does not work reliably (Mac OS X, ...)
375 * - it is not really documented
376 * Keep it here for now, in case it can be helpful
378 //This is much stronger than setLayoutDirection.
381 flag = Qt::TextForceRightToLeft;
383 flag = Qt::TextForceLeftToRight;
384 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
385 flag | Qt::TextDontClip,
391 void GuiPainter::text(int x, int y, docstring const & s,
392 FontInfo const & f, Direction const dir,
393 double const wordspacing, double const tw)
395 //LYXERR0("text: x=" << x << ", s=" << s);
396 if (s.empty() || !isDrawingEnabled())
399 /* Caution: The following ucs4 to QString conversions work for symbol fonts
400 only because they are no real conversions but simple casts in reality.
401 When we want to draw a symbol or calculate the metrics we pass the position
402 of the symbol in the font (as given in lib/symbols) as a char_type to the
403 frontend. This is just wrong, because the symbol is no UCS4 character at
404 all. You can think of this number as the code point of the symbol in a
405 custom symbol encoding. It works because this char_type is later on again
406 interpreted as a position in the font.
407 The correct solution would be to have extra functions for symbols, but that
408 would require to duplicate a lot of frontend and mathed support code.
410 QString str = toqstr(s);
413 // HACK: QT3 refuses to show single compose characters
414 // Still needed with Qt4?
415 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
419 QFont ff = getFont(f);
420 ff.setWordSpacing(wordspacing);
421 GuiFontMetrics const & fm = getFontMetrics(f);
425 // Note that we have to take in account space stretching (word spacing)
426 textwidth = fm.width(s) +
427 static_cast<int>(fm.countExpanders(s) * wordspacing);
429 textwidth = static_cast<int>(tw);
431 textDecoration(f, x, y, textwidth);
433 if (use_pixmap_cache_) {
435 QString key = generateStringSignature(str, f, wordspacing);
437 // Warning: Left bearing is in general negative! Only the case
438 // where left bearing is negative is of interest WRT the
439 // pixmap width and the text x-position.
440 // Only the left bearing of the first character is important
441 // as we always write from left to right, even for
442 // right-to-left languages.
443 // FIXME: this is probably broken for RTL now that we draw full strings.
444 // Morover the first/last element is possibly not the right one since the glyph may have changed.
445 int const lb = min(fm.lbearing(s[0]), 0);
446 int const mA = fm.maxAscent();
447 if (QPixmapCache::find(key, pm)) {
448 // Draw the cached pixmap.
449 drawPixmap(x + lb, y - mA, pm);
453 // Only the right bearing of the last character is
454 // important as we always write from left to right,
455 // even for right-to-left languages.
456 int const rb = fm.rbearing(s[s.size()-1]);
457 int const w = textwidth + rb - lb;
458 int const mD = fm.maxDescent();
459 int const h = mA + mD;
460 if (w > 0 && h > 0) {
461 pm = QPixmap(static_cast<int>(pixelRatio() * w),
462 static_cast<int>(pixelRatio() * h));
463 #if QT_VERSION >= 0x050000
464 pm.setDevicePixelRatio(pixelRatio());
466 pm.fill(Qt::transparent);
467 GuiPainter p(&pm, pixelRatio());
468 p.do_drawText(-lb, mA, str, dir, f, ff);
469 QPixmapCache::insert(key, pm);
470 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
471 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
474 // Draw the new cached pixmap.
475 drawPixmap(x + lb, y - mA, pm);
476 //rectangle(x-lb, y-mA, w, h, Color_green);
481 // don't use the pixmap cache
482 setQPainterPen(computeColor(f.realColor()));
484 QTextLayout const * ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
485 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
492 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
493 // << " at " << x << "," << y);
497 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
498 double const wordspacing, double const tw)
500 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
505 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
506 Color other, size_type const from, size_type const to,
507 double const wordspacing, double const tw)
509 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
510 FontInfo fi = f.fontInfo();
511 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
514 int const ascent = fm.maxAscent();
515 int const height = fm.maxAscent() + fm.maxDescent();
516 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
517 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
521 // First the part in other color
522 Color const orig = fi.realColor();
523 fi.setPaintColor(other);
524 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
526 text(x, y, str, fi, dir, wordspacing, tw);
528 // Then the part in normal color
529 // Note that in Qt5, it is not possible to use Qt::UniteClip,
530 // therefore QRegion is used.
531 fi.setPaintColor(orig);
532 QRegion region(viewport());
533 setClipRegion(region - clip);
534 text(x, y, str, fi, dir, wordspacing, tw);
539 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
541 if (f.underbar() == FONT_ON)
542 underline(f, x, y, width);
543 if (f.strikeout() == FONT_ON)
544 strikeoutLine(f, x, y, width);
545 if (f.uuline() == FONT_ON)
546 doubleUnderline(f, x, y, width);
547 if (f.uwave() == FONT_ON)
548 // f.color() doesn't work on some circumstances
549 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
553 static int max(int a, int b) { return a > b ? a : b; }
556 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
559 fillRectangle(x, y, w, h, Color_buttonhoverbg);
561 fillRectangle(x, y, w, h, Color_buttonbg);
562 buttonFrame(x, y, w, h);
566 void GuiPainter::buttonFrame(int x, int y, int w, int h)
568 line(x, y, x, y + h - 1, Color_buttonframe);
569 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
570 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
571 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
575 void GuiPainter::rectText(int x, int y, docstring const & str,
576 FontInfo const & font, Color back, Color frame)
582 FontMetrics const & fm = theFontMetrics(font);
583 fm.rectText(str, width, ascent, descent);
585 if (back != Color_none)
586 fillRectangle(x + 1, y - ascent + 1, width - 1,
587 ascent + descent - 1, back);
589 if (frame != Color_none)
590 rectangle(x, y - ascent, width, ascent + descent, frame);
592 text(x + 3, y, str, font);
596 void GuiPainter::buttonText(int x, int y, docstring const & str,
597 FontInfo const & font, bool mouseHover)
603 FontMetrics const & fm = theFontMetrics(font);
604 fm.buttonText(str, width, ascent, descent);
606 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
608 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
609 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
613 int GuiPainter::preeditText(int x, int y, char_type c,
614 FontInfo const & font, preedit_style style)
616 FontInfo temp_font = font;
617 FontMetrics const & fm = theFontMetrics(font);
618 int ascent = fm.maxAscent();
619 int descent = fm.maxDescent();
620 int height = ascent + descent;
621 int width = fm.width(c);
624 case preedit_default:
625 // default unselecting mode.
626 fillRectangle(x, y - height + 1, width, height, Color_background);
627 dashedUnderline(font, x, y - descent + 1, width);
629 case preedit_selecting:
630 // We are in selecting mode: white text on black background.
631 fillRectangle(x, y - height + 1, width, height, Color_black);
632 temp_font.setColor(Color_white);
635 // The character comes with a cursor.
636 fillRectangle(x, y - height + 1, width, height, Color_background);
637 underline(font, x, y - descent + 1, width);
640 text(x, y - descent + 1, c, temp_font);
646 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
649 FontMetrics const & fm = theFontMetrics(f);
650 int const pos = fm.underlinePos();
652 line(x, y + pos, x + width, y + pos,
653 f.realColor(), ls, fm.lineWidth());
657 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
659 FontMetrics const & fm = theFontMetrics(f);
660 int const pos = fm.strikeoutPos();
662 line(x, y - pos, x + width, y - pos,
663 f.realColor(), line_solid, fm.lineWidth());
667 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
669 FontMetrics const & fm = theFontMetrics(f);
670 int const pos1 = fm.underlinePos() + fm.lineWidth();
671 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
673 line(x, y + pos1, x + width, y + pos1,
674 f.realColor(), line_solid, fm.lineWidth());
675 line(x, y + pos2, x + width, y + pos2,
676 f.realColor(), line_solid, fm.lineWidth());
680 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
682 FontMetrics const & fm = theFontMetrics(f);
684 int const below = max(fm.maxDescent() / 2, 2);
685 int height = max((fm.maxDescent() / 4) - 1, 1);
690 for (int n = 0; n != height; ++n)
691 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
695 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
697 setQPainterPen(computeColor(col));
699 int const xend = x + width;
701 //FIXME: I am not sure if Antialiasing gives the best effect.
702 //setRenderHint(Antialiasing, true);
705 drawLine(x, y - height, x + step, y + height);
707 drawLine(x, y + height, x + step/2, y + height);
710 //setRenderHint(Antialiasing, false);
713 } // namespace frontend