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.xout() == FONT_ON)
547 crossoutLines(f, x, y, width);
548 if (f.uuline() == FONT_ON)
549 doubleUnderline(f, x, y, width);
550 if (f.uwave() == FONT_ON)
551 // f.color() doesn't work on some circumstances
552 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
556 static int max(int a, int b) { return a > b ? a : b; }
559 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
562 fillRectangle(x, y, w, h, Color_buttonhoverbg);
564 fillRectangle(x, y, w, h, Color_buttonbg);
565 buttonFrame(x, y, w, h);
569 void GuiPainter::buttonFrame(int x, int y, int w, int h)
571 line(x, y, x, y + h - 1, Color_buttonframe);
572 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
573 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
574 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
578 void GuiPainter::rectText(int x, int y, docstring const & str,
579 FontInfo const & font, Color back, Color frame)
585 FontMetrics const & fm = theFontMetrics(font);
586 fm.rectText(str, width, ascent, descent);
588 if (back != Color_none)
589 fillRectangle(x + 1, y - ascent + 1, width - 1,
590 ascent + descent - 1, back);
592 if (frame != Color_none)
593 rectangle(x, y - ascent, width, ascent + descent, frame);
595 text(x + 3, y, str, font);
599 void GuiPainter::buttonText(int x, int y, docstring const & str,
600 FontInfo const & font, bool mouseHover)
606 FontMetrics const & fm = theFontMetrics(font);
607 fm.buttonText(str, width, ascent, descent);
609 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
611 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
612 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
616 int GuiPainter::preeditText(int x, int y, char_type c,
617 FontInfo const & font, preedit_style style)
619 FontInfo temp_font = font;
620 FontMetrics const & fm = theFontMetrics(font);
621 int ascent = fm.maxAscent();
622 int descent = fm.maxDescent();
623 int height = ascent + descent;
624 int width = fm.width(c);
627 case preedit_default:
628 // default unselecting mode.
629 fillRectangle(x, y - height + 1, width, height, Color_background);
630 dashedUnderline(font, x, y - descent + 1, width);
632 case preedit_selecting:
633 // We are in selecting mode: white text on black background.
634 fillRectangle(x, y - height + 1, width, height, Color_black);
635 temp_font.setColor(Color_white);
638 // The character comes with a cursor.
639 fillRectangle(x, y - height + 1, width, height, Color_background);
640 underline(font, x, y - descent + 1, width);
643 text(x, y - descent + 1, c, temp_font);
649 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
652 FontMetrics const & fm = theFontMetrics(f);
653 int const pos = fm.underlinePos();
655 line(x, y + pos, x + width, y + pos,
656 f.realColor(), ls, fm.lineWidth());
660 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
662 FontMetrics const & fm = theFontMetrics(f);
663 int const pos = fm.strikeoutPos();
665 line(x, y - pos, x + width, y - pos,
666 f.realColor(), line_solid, fm.lineWidth());
670 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
672 FontMetrics const & fm = theFontMetrics(f);
673 int const bottom = fm.underlinePos();
674 int const middle = fm.strikeoutPos();
676 // we draw several lines to fill the whole selection with strokes
677 // use 5 as diagonal width since this is close to the PDf output
678 // with normal font zoom levels
679 for(int x_current = x; x_current < x + width - 5; x_current = x_current + 5) {
680 line(x_current, y + bottom, x_current + 5, y - 2 * middle - 2 * bottom,
681 f.realColor(), line_solid, fm.lineWidth());
686 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
688 FontMetrics const & fm = theFontMetrics(f);
689 int const pos1 = fm.underlinePos() + fm.lineWidth();
690 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
692 line(x, y + pos1, x + width, y + pos1,
693 f.realColor(), line_solid, fm.lineWidth());
694 line(x, y + pos2, x + width, y + pos2,
695 f.realColor(), line_solid, fm.lineWidth());
699 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
701 FontMetrics const & fm = theFontMetrics(f);
703 int const below = max(fm.maxDescent() / 2, 2);
704 int height = max((fm.maxDescent() / 4) - 1, 1);
709 for (int n = 0; n != height; ++n)
710 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
714 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
716 setQPainterPen(computeColor(col));
718 int const xend = x + width;
720 //FIXME: I am not sure if Antialiasing gives the best effect.
721 //setRenderHint(Antialiasing, true);
724 drawLine(x, y - height, x + step, y + height);
726 drawLine(x, y + height, x + step/2, y + height);
729 //setRenderHint(Antialiasing, false);
732 } // namespace frontend