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 shared_ptr<QTextLayout const> ptl =
485 fm.getTextLayout(s, dir == RtL, wordspacing);
486 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
493 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
494 // << " at " << x << "," << y);
498 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
499 double const wordspacing, double const tw)
501 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
506 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
507 Color other, size_type const from, size_type const to,
508 double const wordspacing, double const tw)
510 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
511 FontInfo fi = f.fontInfo();
512 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
515 int const ascent = fm.maxAscent();
516 int const height = fm.maxAscent() + fm.maxDescent();
517 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
518 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
522 // First the part in other color
523 Color const orig = fi.realColor();
524 fi.setPaintColor(other);
525 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
527 text(x, y, str, fi, dir, wordspacing, tw);
529 // Then the part in normal color
530 // Note that in Qt5, it is not possible to use Qt::UniteClip,
531 // therefore QRegion is used.
532 fi.setPaintColor(orig);
533 QRegion region(viewport());
534 setClipRegion(region - clip);
535 text(x, y, str, fi, dir, wordspacing, tw);
540 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
542 if (f.underbar() == FONT_ON)
543 underline(f, x, y, width);
544 if (f.strikeout() == FONT_ON)
545 strikeoutLine(f, x, y, width);
546 if (f.uuline() == FONT_ON)
547 doubleUnderline(f, x, y, width);
548 if (f.uwave() == FONT_ON)
549 // f.color() doesn't work on some circumstances
550 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
554 static int max(int a, int b) { return a > b ? a : b; }
557 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
560 fillRectangle(x, y, w, h, Color_buttonhoverbg);
562 fillRectangle(x, y, w, h, Color_buttonbg);
563 buttonFrame(x, y, w, h);
567 void GuiPainter::buttonFrame(int x, int y, int w, int h)
569 line(x, y, x, y + h - 1, Color_buttonframe);
570 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
571 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
572 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
576 void GuiPainter::rectText(int x, int y, docstring const & str,
577 FontInfo const & font, Color back, Color frame)
583 FontMetrics const & fm = theFontMetrics(font);
584 fm.rectText(str, width, ascent, descent);
586 if (back != Color_none)
587 fillRectangle(x + 1, y - ascent + 1, width - 1,
588 ascent + descent - 1, back);
590 if (frame != Color_none)
591 rectangle(x, y - ascent, width, ascent + descent, frame);
593 text(x + 3, y, str, font);
597 void GuiPainter::buttonText(int x, int y, docstring const & str,
598 FontInfo const & font, bool mouseHover)
604 FontMetrics const & fm = theFontMetrics(font);
605 fm.buttonText(str, width, ascent, descent);
607 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
609 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
610 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
614 int GuiPainter::preeditText(int x, int y, char_type c,
615 FontInfo const & font, preedit_style style)
617 FontInfo temp_font = font;
618 FontMetrics const & fm = theFontMetrics(font);
619 int ascent = fm.maxAscent();
620 int descent = fm.maxDescent();
621 int height = ascent + descent;
622 int width = fm.width(c);
625 case preedit_default:
626 // default unselecting mode.
627 fillRectangle(x, y - height + 1, width, height, Color_background);
628 dashedUnderline(font, x, y - descent + 1, width);
630 case preedit_selecting:
631 // We are in selecting mode: white text on black background.
632 fillRectangle(x, y - height + 1, width, height, Color_black);
633 temp_font.setColor(Color_white);
636 // The character comes with a cursor.
637 fillRectangle(x, y - height + 1, width, height, Color_background);
638 underline(font, x, y - descent + 1, width);
641 text(x, y - descent + 1, c, temp_font);
647 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
650 FontMetrics const & fm = theFontMetrics(f);
651 int const pos = fm.underlinePos();
653 line(x, y + pos, x + width, y + pos,
654 f.realColor(), ls, fm.lineWidth());
658 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
660 FontMetrics const & fm = theFontMetrics(f);
661 int const pos = fm.strikeoutPos();
663 line(x, y - pos, x + width, y - pos,
664 f.realColor(), line_solid, fm.lineWidth());
668 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
670 FontMetrics const & fm = theFontMetrics(f);
671 int const pos1 = fm.underlinePos() + fm.lineWidth();
672 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
674 line(x, y + pos1, x + width, y + pos1,
675 f.realColor(), line_solid, fm.lineWidth());
676 line(x, y + pos2, x + width, y + pos2,
677 f.realColor(), line_solid, fm.lineWidth());
681 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
683 FontMetrics const & fm = theFontMetrics(f);
685 int const below = max(fm.maxDescent() / 2, 2);
686 int height = max((fm.maxDescent() / 4) - 1, 1);
691 for (int n = 0; n != height; ++n)
692 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
696 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
698 setQPainterPen(computeColor(col));
700 int const xend = x + width;
702 //FIXME: I am not sure if Antialiasing gives the best effect.
703 //setRenderHint(Antialiasing, true);
706 drawLine(x, y - height, x + step, y + height);
708 drawLine(x, y + height, x + step/2, y + height);
711 //setRenderHint(Antialiasing, false);
714 } // namespace frontend