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 do_drawText(x, y, str, dir, f, ff);
483 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
484 // << " at " << x << "," << y);
488 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
489 double const wordspacing, double const tw)
491 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
496 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
497 Color other, size_type const from, size_type const to,
498 double const wordspacing, double const tw)
500 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
501 FontInfo fi = f.fontInfo();
502 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
505 int const ascent = fm.maxAscent();
506 int const height = fm.maxAscent() + fm.maxDescent();
507 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
508 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
512 // First the part in other color
513 Color const orig = fi.realColor();
514 fi.setPaintColor(other);
515 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
517 text(x, y, str, fi, dir, wordspacing, tw);
519 // Then the part in normal color
520 // Note that in Qt5, it is not possible to use Qt::UniteClip,
521 // therefore QRegion is used.
522 fi.setPaintColor(orig);
523 QRegion region(viewport());
524 setClipRegion(region - clip);
525 text(x, y, str, fi, dir, wordspacing, tw);
530 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
532 if (f.underbar() == FONT_ON)
533 underline(f, x, y, width);
534 if (f.strikeout() == FONT_ON)
535 strikeoutLine(f, x, y, width);
536 if (f.uuline() == FONT_ON)
537 doubleUnderline(f, x, y, width);
538 if (f.uwave() == FONT_ON)
539 // f.color() doesn't work on some circumstances
540 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
544 static int max(int a, int b) { return a > b ? a : b; }
547 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
550 fillRectangle(x, y, w, h, Color_buttonhoverbg);
552 fillRectangle(x, y, w, h, Color_buttonbg);
553 buttonFrame(x, y, w, h);
557 void GuiPainter::buttonFrame(int x, int y, int w, int h)
559 line(x, y, x, y + h - 1, Color_buttonframe);
560 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
561 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
562 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
566 void GuiPainter::rectText(int x, int y, docstring const & str,
567 FontInfo const & font, Color back, Color frame)
573 FontMetrics const & fm = theFontMetrics(font);
574 fm.rectText(str, width, ascent, descent);
576 if (back != Color_none)
577 fillRectangle(x + 1, y - ascent + 1, width - 1,
578 ascent + descent - 1, back);
580 if (frame != Color_none)
581 rectangle(x, y - ascent, width, ascent + descent, frame);
583 text(x + 3, y, str, font);
587 void GuiPainter::buttonText(int x, int y, docstring const & str,
588 FontInfo const & font, bool mouseHover)
594 FontMetrics const & fm = theFontMetrics(font);
595 fm.buttonText(str, width, ascent, descent);
597 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
599 button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
600 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
604 int GuiPainter::preeditText(int x, int y, char_type c,
605 FontInfo const & font, preedit_style style)
607 FontInfo temp_font = font;
608 FontMetrics const & fm = theFontMetrics(font);
609 int ascent = fm.maxAscent();
610 int descent = fm.maxDescent();
611 int height = ascent + descent;
612 int width = fm.width(c);
615 case preedit_default:
616 // default unselecting mode.
617 fillRectangle(x, y - height + 1, width, height, Color_background);
618 dashedUnderline(font, x, y - descent + 1, width);
620 case preedit_selecting:
621 // We are in selecting mode: white text on black background.
622 fillRectangle(x, y - height + 1, width, height, Color_black);
623 temp_font.setColor(Color_white);
626 // The character comes with a cursor.
627 fillRectangle(x, y - height + 1, width, height, Color_background);
628 underline(font, x, y - descent + 1, width);
631 text(x, y - descent + 1, c, temp_font);
637 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
640 FontMetrics const & fm = theFontMetrics(f);
641 int const pos = fm.underlinePos();
643 line(x, y + pos, x + width, y + pos,
644 f.realColor(), ls, fm.lineWidth());
648 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
650 FontMetrics const & fm = theFontMetrics(f);
651 int const pos = fm.strikeoutPos();
653 line(x, y - pos, x + width, y - pos,
654 f.realColor(), line_solid, fm.lineWidth());
658 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
660 FontMetrics const & fm = theFontMetrics(f);
661 int const pos1 = fm.underlinePos() + fm.lineWidth();
662 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
664 line(x, y + pos1, x + width, y + pos1,
665 f.realColor(), line_solid, fm.lineWidth());
666 line(x, y + pos2, x + width, y + pos2,
667 f.realColor(), line_solid, fm.lineWidth());
671 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
673 FontMetrics const & fm = theFontMetrics(f);
675 int const below = max(fm.maxDescent() / 2, 2);
676 int height = max((fm.maxDescent() / 4) - 1, 1);
681 for (int n = 0; n != height; ++n)
682 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
686 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
688 setQPainterPen(computeColor(col));
690 int const xend = x + width;
692 //FIXME: I am not sure if Antialiasing gives the best effect.
693 //setRenderHint(Antialiasing, true);
696 drawLine(x, y - height, x + step, y + height);
698 drawLine(x, y + height, x + step/2, y + height);
701 //setRenderHint(Antialiasing, false);
704 } // namespace frontend