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) + count(s.begin(), s.end(), ' ') * wordspacing;
428 textwidth = static_cast<int>(tw);
430 textDecoration(f, x, y, textwidth);
432 if (use_pixmap_cache_) {
434 QString key = generateStringSignature(str, f, wordspacing);
436 // Warning: Left bearing is in general negative! Only the case
437 // where left bearing is negative is of interest WRT the
438 // pixmap width and the text x-position.
439 // Only the left bearing of the first character is important
440 // as we always write from left to right, even for
441 // right-to-left languages.
442 // FIXME: this is probably broken for RTL now that we draw full strings.
443 // Morover the first/last element is possibly not the right one since the glyph may have changed.
444 int const lb = min(fm.lbearing(s[0]), 0);
445 int const mA = fm.maxAscent();
446 if (QPixmapCache::find(key, pm)) {
447 // Draw the cached pixmap.
448 drawPixmap(x + lb, y - mA, pm);
452 // Only the right bearing of the last character is
453 // important as we always write from left to right,
454 // even for right-to-left languages.
455 int const rb = fm.rbearing(s[s.size()-1]);
456 int const w = textwidth + rb - lb;
457 int const mD = fm.maxDescent();
458 int const h = mA + mD;
459 if (w > 0 && h > 0) {
460 pm = QPixmap(static_cast<int>(pixelRatio() * w),
461 static_cast<int>(pixelRatio() * h));
462 #if QT_VERSION >= 0x050000
463 pm.setDevicePixelRatio(pixelRatio());
465 pm.fill(Qt::transparent);
466 GuiPainter p(&pm, pixelRatio());
467 p.do_drawText(-lb, mA, str, dir, f, ff);
468 QPixmapCache::insert(key, pm);
469 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
470 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
473 // Draw the new cached pixmap.
474 drawPixmap(x + lb, y - mA, pm);
475 //rectangle(x-lb, y-mA, w, h, Color_green);
480 // don't use the pixmap cache,
481 do_drawText(x, y, str, dir, f, ff);
482 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
483 // << " at " << x << "," << y);
487 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
488 double const wordspacing, double const tw)
490 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
495 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
496 Color other, size_type const from, size_type const to,
497 double const wordspacing, double const tw)
499 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
500 FontInfo fi = f.fontInfo();
501 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
504 int const ascent = fm.maxAscent();
505 int const height = fm.maxAscent() + fm.maxDescent();
506 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
507 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
511 // First the part in other color
512 Color const orig = fi.realColor();
513 fi.setPaintColor(other);
514 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
516 text(x, y, str, fi, dir, wordspacing, tw);
518 // Then the part in normal color
519 // Note that in Qt5, it is not possible to use Qt::UniteClip,
520 // therefore QRegion is used.
521 fi.setPaintColor(orig);
522 QRegion region(viewport());
523 setClipRegion(region - clip);
524 text(x, y, str, fi, dir, wordspacing, tw);
529 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
531 if (f.underbar() == FONT_ON)
532 underline(f, x, y, width);
533 if (f.strikeout() == FONT_ON)
534 strikeoutLine(f, x, y, width);
535 if (f.uuline() == FONT_ON)
536 doubleUnderline(f, x, y, width);
537 if (f.uwave() == FONT_ON)
538 // f.color() doesn't work on some circumstances
539 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
543 static int max(int a, int b) { return a > b ? a : b; }
546 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
549 fillRectangle(x, y, w, h, Color_buttonhoverbg);
551 fillRectangle(x, y, w, h, Color_buttonbg);
552 buttonFrame(x, y, w, h);
556 void GuiPainter::buttonFrame(int x, int y, int w, int h)
558 line(x, y, x, y + h - 1, Color_buttonframe);
559 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
560 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
561 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
565 void GuiPainter::rectText(int x, int y, docstring const & str,
566 FontInfo const & font, Color back, Color frame)
572 FontMetrics const & fm = theFontMetrics(font);
573 fm.rectText(str, width, ascent, descent);
575 if (back != Color_none)
576 fillRectangle(x + 1, y - ascent + 1, width - 1,
577 ascent + descent - 1, back);
579 if (frame != Color_none)
580 rectangle(x, y - ascent, width, ascent + descent, frame);
582 text(x + 3, y, str, font);
586 void GuiPainter::buttonText(int x, int y, docstring const & str,
587 FontInfo const & font, bool mouseHover)
593 FontMetrics const & fm = theFontMetrics(font);
594 fm.buttonText(str, width, ascent, descent);
596 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
598 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
599 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
603 int GuiPainter::preeditText(int x, int y, char_type c,
604 FontInfo const & font, preedit_style style)
606 FontInfo temp_font = font;
607 FontMetrics const & fm = theFontMetrics(font);
608 int ascent = fm.maxAscent();
609 int descent = fm.maxDescent();
610 int height = ascent + descent;
611 int width = fm.width(c);
614 case preedit_default:
615 // default unselecting mode.
616 fillRectangle(x, y - height + 1, width, height, Color_background);
617 dashedUnderline(font, x, y - descent + 1, width);
619 case preedit_selecting:
620 // We are in selecting mode: white text on black background.
621 fillRectangle(x, y - height + 1, width, height, Color_black);
622 temp_font.setColor(Color_white);
625 // The character comes with a cursor.
626 fillRectangle(x, y - height + 1, width, height, Color_background);
627 underline(font, x, y - descent + 1, width);
630 text(x, y - descent + 1, c, temp_font);
636 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
639 FontMetrics const & fm = theFontMetrics(f);
640 int const pos = fm.underlinePos();
642 line(x, y + pos, x + width, y + pos,
643 f.realColor(), ls, fm.lineWidth());
647 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
649 FontMetrics const & fm = theFontMetrics(f);
650 int const pos = fm.strikeoutPos();
652 line(x, y - pos, x + width, y - pos,
653 f.realColor(), line_solid, fm.lineWidth());
657 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
659 FontMetrics const & fm = theFontMetrics(f);
660 int const pos1 = fm.underlinePos() + fm.lineWidth();
661 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
663 line(x, y + pos1, x + width, y + pos1,
664 f.realColor(), line_solid, fm.lineWidth());
665 line(x, y + pos2, x + width, y + pos2,
666 f.realColor(), line_solid, fm.lineWidth());
670 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
672 FontMetrics const & fm = theFontMetrics(f);
674 int const below = max(fm.maxDescent() / 2, 2);
675 int height = max((fm.maxDescent() / 4) - 1, 1);
680 for (int n = 0; n != height; ++n)
681 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
685 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
687 setQPainterPen(computeColor(col));
689 int const xend = x + width;
691 //FIXME: I am not sure if Antialiasing gives the best effect.
692 //setRenderHint(Antialiasing, true);
695 drawLine(x, y - height, x + step, y + height);
697 drawLine(x, y + height, x + step/2, y + height);
700 //setRenderHint(Antialiasing, false);
703 } // namespace frontend