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"
32 #include <QPixmapCache>
33 #include <QTextLayout>
35 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
36 // drawing text. This is especially useful for older PPC/Mac systems.
37 #if defined(Q_WS_X11) || defined(QPA_XCB)
38 #define USE_PIXMAP_CACHE 0
40 #define USE_PIXMAP_CACHE 1
44 using namespace lyx::support;
49 const int Painter::thin_line = 0;
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52 : QPainter(device), Painter(pixel_ratio),
53 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
55 // new QPainter has default QPen:
56 current_color_ = guiApp->colorCache().get(Color_black);
57 current_ls_ = line_solid;
58 current_lw_ = thin_line;
62 GuiPainter::~GuiPainter()
65 //lyxerr << "GuiPainter::end()" << endl;
69 void GuiPainter::setQPainterPen(QColor const & col,
70 Painter::line_style ls, int lw)
72 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
79 QPen pen = QPainter::pen();
83 case line_solid: pen.setStyle(Qt::SolidLine); break;
84 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
93 QString GuiPainter::generateStringSignature(QString const & str,
98 sig.append(QChar(static_cast<short>(f.family())));
99 sig.append(QChar(static_cast<short>(f.series())));
100 sig.append(QChar(static_cast<short>(f.realShape())));
101 sig.append(QChar(static_cast<short>(f.size())));
102 Color const & color = f.realColor();
103 sig.append(QChar(static_cast<short>(color.baseColor)));
104 sig.append(QChar(static_cast<short>(color.mergeColor)));
105 sig.append(QString::number(wordspacing));
106 if (!monochrome_min_.empty()) {
107 QColor const & min = monochrome_min_.top();
108 QColor const & max = monochrome_max_.top();
109 sig.append(QChar(static_cast<short>(min.red())));
110 sig.append(QChar(static_cast<short>(min.green())));
111 sig.append(QChar(static_cast<short>(min.blue())));
112 sig.append(QChar(static_cast<short>(max.red())));
113 sig.append(QChar(static_cast<short>(max.green())));
114 sig.append(QChar(static_cast<short>(max.blue())));
120 QColor GuiPainter::computeColor(Color col)
122 return filterColor(guiApp->colorCache().get(col));
126 QColor GuiPainter::filterColor(QColor const & col)
128 if (monochrome_min_.empty())
131 // map into [min,max] interval
132 QColor const & min = monochrome_min_.top();
133 QColor const & max = monochrome_max_.top();
135 qreal v = col.valueF();
136 v *= v; // make it a bit steeper (i.e. darker)
138 qreal minr, ming, minb;
139 qreal maxr, maxg, maxb;
140 min.getRgbF(&minr, &ming, &minb);
141 max.getRgbF(&maxr, &maxg, &maxb);
145 v * (minr - maxr) + maxr,
146 v * (ming - maxg) + maxg,
147 v * (minb - maxb) + maxb);
152 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
154 QColor qmin = filterColor(guiApp->colorCache().get(min));
155 QColor qmax = filterColor(guiApp->colorCache().get(max));
156 monochrome_min_.push(qmin);
157 monochrome_max_.push(qmax);
161 void GuiPainter::leaveMonochromeMode()
163 LASSERT(!monochrome_min_.empty(), return);
164 monochrome_min_.pop();
165 monochrome_max_.pop();
169 void GuiPainter::point(int x, int y, Color col)
171 if (!isDrawingEnabled())
174 setQPainterPen(computeColor(col));
179 void GuiPainter::line(int x1, int y1, int x2, int y2,
184 if (!isDrawingEnabled())
187 setQPainterPen(computeColor(col), ls, lw);
188 bool const do_antialiasing = renderHints() & TextAntialiasing
189 && x1 != x2 && y1 != y2;
190 setRenderHint(Antialiasing, do_antialiasing);
191 drawLine(x1, y1, x2, y2);
192 setRenderHint(Antialiasing, false);
196 void GuiPainter::lines(int const * xp, int const * yp, int np,
202 if (!isDrawingEnabled())
205 // double the size if needed
207 static QVector<QPoint> points(32);
208 if (np > points.size())
209 points.resize(2 * np);
211 bool antialias = false;
212 for (int i = 0; i < np; ++i) {
213 points[i].setX(xp[i]);
214 points[i].setY(yp[i]);
216 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
218 QColor const color = computeColor(col);
219 setQPainterPen(color, ls, lw);
220 bool const text_is_antialiased = renderHints() & TextAntialiasing;
221 setRenderHint(Antialiasing, antialias && text_is_antialiased);
222 if (fs == fill_none) {
223 drawPolyline(points.data(), np);
225 QBrush const oldbrush = brush();
226 setBrush(QBrush(color));
227 drawPolygon(points.data(), np, fs == fill_oddeven ?
228 Qt::OddEvenFill : Qt::WindingFill);
231 setRenderHint(Antialiasing, false);
235 void GuiPainter::path(int const * xp, int const * yp,
236 int const * c1x, int const * c1y,
237 int const * c2x, int const * c2y,
244 if (!isDrawingEnabled())
248 // This is the starting point, so its control points are meaningless
249 bpath.moveTo(xp[0], yp[0]);
251 for (int i = 1; i < np; ++i) {
252 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
253 c2x[i] == xp[i] && c2y[i] == yp[i];
255 bpath.lineTo(xp[i], yp[i]);
257 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
259 QColor const color = computeColor(col);
260 setQPainterPen(color, ls, lw);
261 bool const text_is_antialiased = renderHints() & TextAntialiasing;
262 setRenderHint(Antialiasing, text_is_antialiased);
265 fillPath(bpath, QBrush(color));
266 setRenderHint(Antialiasing, false);
270 void GuiPainter::rectangle(int x, int y, int w, int h,
275 if (!isDrawingEnabled())
278 setQPainterPen(computeColor(col), ls, lw);
279 drawRect(x, y, w, h);
283 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
285 if (!isDrawingEnabled())
288 fillRect(x, y, w, h, guiApp->colorCache().get(col));
292 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
293 int a1, int a2, Color col)
295 if (!isDrawingEnabled())
298 // LyX usings 1/64ths degree, Qt usings 1/16th
299 setQPainterPen(computeColor(col));
300 bool const do_antialiasing = renderHints() & TextAntialiasing;
301 setRenderHint(Antialiasing, do_antialiasing);
302 drawArc(x, y, w, h, a1 / 4, a2 / 4);
303 setRenderHint(Antialiasing, false);
307 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
309 graphics::GuiImage const & qlimage =
310 static_cast<graphics::GuiImage const &>(i);
312 fillRectangle(x, y, w, h, Color_graphicsbg);
314 if (!isDrawingEnabled())
317 QImage const image = qlimage.image();
318 QRectF const drect = QRectF(x, y, w, h);
319 QRectF const srect = QRectF(0, 0, image.width(), image.height());
320 drawImage(drect, image, srect);
324 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
326 return text(x, y, docstring(1, c), f);
330 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
332 setQPainterPen(computeColor(f.realColor()));
336 /* In LyX, the character direction is forced by the language.
337 * Therefore, we have to signal that fact to Qt.
340 /* Use unicode override characters to enforce drawing direction
341 * Source: http://www.iamcal.com/understanding-bidirectional-text/
344 // Right-to-left override: forces to draw text right-to-left
345 str = QChar(0x202E) + str;
347 // Left-to-right override: forces to draw text left-to-right
348 str = QChar(0x202D) + str;
351 /* This looks like a cleaner solution, but it has drawbacks
352 * - does not work reliably (Mac OS X, ...)
353 * - it is not really documented
354 * Keep it here for now, in case it can be helpful
356 //This is much stronger than setLayoutDirection.
357 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
358 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
359 flag | Qt::TextDontClip,
365 int GuiPainter::text(int x, int y, docstring const & s,
366 FontInfo const & f, bool const rtl,
367 double const wordspacing)
369 //LYXERR0("text: x=" << x << ", s=" << s);
373 /* Caution: The following ucs4 to QString conversions work for symbol fonts
374 only because they are no real conversions but simple casts in reality.
375 When we want to draw a symbol or calculate the metrics we pass the position
376 of the symbol in the font (as given in lib/symbols) as a char_type to the
377 frontend. This is just wrong, because the symbol is no UCS4 character at
378 all. You can think of this number as the code point of the symbol in a
379 custom symbol encoding. It works because this char_type is lateron again
380 interpreted as a position in the font again.
381 The correct solution would be to have extra functions for symbols, but that
382 would require to duplicate a lot of frontend and mathed support code.
384 QString str = toqstr(s);
387 // HACK: QT3 refuses to show single compose characters
388 // Still needed with Qt4?
389 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
393 QFont ff = getFont(f);
394 ff.setWordSpacing(wordspacing);
395 GuiFontMetrics const & fm = getFontMetrics(f);
397 // Here we use the font width cache instead of
398 // textwidth = fontMetrics().width(str);
399 // because the above is awfully expensive on MacOSX
400 // Note that we have to take in account space stretching (word spacing)
401 int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
403 if (!isDrawingEnabled())
406 textDecoration(f, x, y, textwidth);
408 if (use_pixmap_cache_) {
410 QString key = generateStringSignature(str, f, wordspacing);
412 // Warning: Left bearing is in general negative! Only the case
413 // where left bearing is negative is of interest WRT the
414 // pixmap width and the text x-position.
415 // Only the left bearing of the first character is important
416 // as we always write from left to right, even for
417 // right-to-left languages.
418 // FIXME: this is probably broken for RTL now that we draw full strings.
419 // Morover the first/last element is possibly not the right one since the glyph may have changed.
420 int const lb = min(fm.lbearing(s[0]), 0);
421 int const mA = fm.maxAscent();
422 if (QPixmapCache::find(key, pm)) {
423 // Draw the cached pixmap.
424 drawPixmap(x + lb, y - mA, pm);
428 // Only the right bearing of the last character is
429 // important as we always write from left to right,
430 // even for right-to-left languages.
431 int const rb = fm.rbearing(s[s.size()-1]);
432 int const w = textwidth + rb - lb;
433 int const mD = fm.maxDescent();
434 int const h = mA + mD;
435 if (w > 0 && h > 0) {
436 pm = QPixmap(static_cast<int>(pixelRatio() * w),
437 static_cast<int>(pixelRatio() * h));
438 #if QT_VERSION >= 0x050000
439 pm.setDevicePixelRatio(pixelRatio());
441 pm.fill(Qt::transparent);
442 GuiPainter p(&pm, pixelRatio());
443 p.do_drawText(-lb, mA, str, rtl, f, ff);
444 QPixmapCache::insert(key, pm);
445 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
446 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
449 // Draw the new cached pixmap.
450 drawPixmap(x + lb, y - mA, pm);
451 //rectangle(x-lb, y-mA, w, h, Color_green);
456 // don't use the pixmap cache,
457 do_drawText(x, y, str, rtl, f, ff);
458 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
459 // << " at " << x << "," << y);
464 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
465 double const wordspacing)
467 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
471 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
472 Color other, size_type const from, size_type const to,
473 double const wordspacing)
475 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
476 FontInfo fi = f.fontInfo();
477 bool const rtl = f.isVisibleRightToLeft();
480 int const ascent = fm.maxAscent();
481 int const height = fm.maxAscent() + fm.maxDescent();
482 int xmin = fm.pos2x(str, from, rtl, wordspacing);
483 int xmax = fm.pos2x(str, to, rtl, wordspacing);
487 // First the part in other color
488 Color const orig = fi.realColor();
489 fi.setPaintColor(other);
490 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
492 int const textwidth = text(x, y, str, fi, rtl, wordspacing);
494 // Then the part in normal color
495 // Note that in Qt5, it is not possible to use Qt::UniteClip,
496 // therefore QRegion is used.
497 fi.setPaintColor(orig);
498 QRegion region(viewport());
499 setClipRegion(region - clip);
500 text(x, y, str, fi, rtl, wordspacing);
507 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
509 if (f.underbar() == FONT_ON)
510 underline(f, x, y, width);
511 if (f.strikeout() == FONT_ON)
512 strikeoutLine(f, x, y, width);
513 if (f.uuline() == FONT_ON)
514 doubleUnderline(f, x, y, width);
515 if (f.uwave() == FONT_ON)
516 // f.color() doesn't work on some circumstances
517 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
521 static int max(int a, int b) { return a > b ? a : b; }
524 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
527 fillRectangle(x, y, w, h, Color_buttonhoverbg);
529 fillRectangle(x, y, w, h, Color_buttonbg);
530 buttonFrame(x, y, w, h);
534 void GuiPainter::buttonFrame(int x, int y, int w, int h)
536 line(x, y, x, y + h - 1, Color_buttonframe);
537 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
538 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
539 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
543 void GuiPainter::rectText(int x, int y, docstring const & str,
544 FontInfo const & font, Color back, Color frame)
550 FontMetrics const & fm = theFontMetrics(font);
551 fm.rectText(str, width, ascent, descent);
553 if (back != Color_none)
554 fillRectangle(x + 1, y - ascent + 1, width - 1,
555 ascent + descent - 1, back);
557 if (frame != Color_none)
558 rectangle(x, y - ascent, width, ascent + descent, frame);
560 text(x + 3, y, str, font);
564 void GuiPainter::buttonText(int x, int y, docstring const & str,
565 FontInfo const & font, bool mouseHover)
571 FontMetrics const & fm = theFontMetrics(font);
572 fm.buttonText(str, width, ascent, descent);
574 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
576 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
577 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
581 int GuiPainter::preeditText(int x, int y, char_type c,
582 FontInfo const & font, preedit_style style)
584 FontInfo temp_font = font;
585 FontMetrics const & fm = theFontMetrics(font);
586 int ascent = fm.maxAscent();
587 int descent = fm.maxDescent();
588 int height = ascent + descent;
589 int width = fm.width(c);
592 case preedit_default:
593 // default unselecting mode.
594 fillRectangle(x, y - height + 1, width, height, Color_background);
595 dashedUnderline(font, x, y - descent + 1, width);
597 case preedit_selecting:
598 // We are in selecting mode: white text on black background.
599 fillRectangle(x, y - height + 1, width, height, Color_black);
600 temp_font.setColor(Color_white);
603 // The character comes with a cursor.
604 fillRectangle(x, y - height + 1, width, height, Color_background);
605 underline(font, x, y - descent + 1, width);
608 text(x, y - descent + 1, c, temp_font);
614 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
617 FontMetrics const & fm = theFontMetrics(f);
618 int const pos = fm.underlinePos();
620 line(x, y + pos, x + width, y + pos,
621 f.realColor(), ls, fm.lineWidth());
625 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
627 FontMetrics const & fm = theFontMetrics(f);
628 int const pos = fm.strikeoutPos();
630 line(x, y - pos, x + width, y - pos,
631 f.realColor(), line_solid, fm.lineWidth());
635 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
637 FontMetrics const & fm = theFontMetrics(f);
638 int const pos1 = fm.underlinePos() + fm.lineWidth();
639 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
641 line(x, y + pos1, x + width, y + pos1,
642 f.realColor(), line_solid, fm.lineWidth());
643 line(x, y + pos2, x + width, y + pos2,
644 f.realColor(), line_solid, fm.lineWidth());
648 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
650 FontMetrics const & fm = theFontMetrics(f);
652 int const below = max(fm.maxDescent() / 2, 2);
653 int height = max((fm.maxDescent() / 4) - 1, 1);
658 for (int n = 0; n != height; ++n)
659 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
663 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
665 setQPainterPen(computeColor(col));
667 int const xend = x + width;
669 //FIXME: I am not sure if Antialiasing gives the best effect.
670 //setRenderHint(Antialiasing, true);
673 drawLine(x, y - height, x + step, y + height);
675 drawLine(x, y + height, x + step/2, y + height);
678 //setRenderHint(Antialiasing, false);
681 } // namespace frontend