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"
26 #include "support/debug.h"
27 #include "support/lassert.h"
28 #include "support/lyxlib.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 = 1;
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52 : QPainter(device), Painter(pixel_ratio),
53 use_pixmap_cache_(false)
55 // set cache correctly
56 current_color_ = pen().color();
57 current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
58 current_lw_ = pen().width();
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();
84 case line_solid_aliased:
85 pen.setStyle(Qt::SolidLine); break;
87 pen.setStyle(Qt::DotLine); break;
96 QString GuiPainter::generateStringSignature(QString const & str,
101 sig.append(QChar(static_cast<short>(f.family())));
102 sig.append(QChar(static_cast<short>(f.series())));
103 sig.append(QChar(static_cast<short>(f.realShape())));
104 sig.append(QChar(static_cast<short>(f.size())));
105 Color const & color = f.realColor();
106 sig.append(QChar(static_cast<short>(color.baseColor)));
107 sig.append(QChar(static_cast<short>(color.mergeColor)));
108 sig.append(QString::number(wordspacing));
109 if (!monochrome_min_.empty()) {
110 QColor const & min = monochrome_min_.top();
111 QColor const & max = monochrome_max_.top();
112 sig.append(QChar(static_cast<short>(min.red())));
113 sig.append(QChar(static_cast<short>(min.green())));
114 sig.append(QChar(static_cast<short>(min.blue())));
115 sig.append(QChar(static_cast<short>(max.red())));
116 sig.append(QChar(static_cast<short>(max.green())));
117 sig.append(QChar(static_cast<short>(max.blue())));
123 QColor GuiPainter::computeColor(Color col)
125 return filterColor(guiApp->colorCache().get(col));
129 QColor GuiPainter::filterColor(QColor const & col)
131 if (monochrome_min_.empty())
134 // map into [min,max] interval
135 QColor const & min = monochrome_min_.top();
136 QColor const & max = monochrome_max_.top();
138 qreal v = col.valueF();
139 v *= v; // make it a bit steeper (i.e. darker)
141 qreal minr, ming, minb;
142 qreal maxr, maxg, maxb;
143 min.getRgbF(&minr, &ming, &minb);
144 max.getRgbF(&maxr, &maxg, &maxb);
148 v * (minr - maxr) + maxr,
149 v * (ming - maxg) + maxg,
150 v * (minb - maxb) + maxb);
155 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
157 QColor qmin = filterColor(guiApp->colorCache().get(min));
158 QColor qmax = filterColor(guiApp->colorCache().get(max));
159 monochrome_min_.push(qmin);
160 monochrome_max_.push(qmax);
164 void GuiPainter::leaveMonochromeMode()
166 LASSERT(!monochrome_min_.empty(), return);
167 monochrome_min_.pop();
168 monochrome_max_.pop();
172 void GuiPainter::point(int x, int y, Color col)
174 setQPainterPen(computeColor(col));
179 void GuiPainter::line(int x1, int y1, int x2, int y2,
184 setQPainterPen(computeColor(col), ls, lw);
185 bool const do_antialiasing = renderHints() & TextAntialiasing
186 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
187 setRenderHint(Antialiasing, do_antialiasing);
188 drawLine(x1, y1, x2, y2);
189 setRenderHint(Antialiasing, false);
193 void GuiPainter::lines(int const * xp, int const * yp, int np,
199 // double the size if needed
201 static QVector<QPoint> points(32);
202 if (np > points.size())
203 points.resize(2 * np);
205 // Note: the proper way to not get blurry vertical and horizontal lines is
206 // to add 0.5 to all coordinates.
207 bool antialias = false;
208 for (int i = 0; i < np; ++i) {
209 points[i].setX(xp[i]);
210 points[i].setY(yp[i]);
212 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
214 QColor const color = computeColor(col);
215 setQPainterPen(color, ls, lw);
216 bool const text_is_antialiased = renderHints() & TextAntialiasing;
217 setRenderHint(Antialiasing,
218 antialias && text_is_antialiased && ls != line_solid_aliased);
219 if (fs == fill_none) {
220 drawPolyline(points.data(), np);
222 QBrush const oldbrush = brush();
223 setBrush(QBrush(color));
224 drawPolygon(points.data(), np, fs == fill_oddeven ?
225 Qt::OddEvenFill : Qt::WindingFill);
228 setRenderHint(Antialiasing, false);
232 void GuiPainter::path(int const * xp, int const * yp,
233 int const * c1x, int const * c1y,
234 int const * c2x, int const * c2y,
242 // This is the starting point, so its control points are meaningless
243 bpath.moveTo(xp[0], yp[0]);
245 for (int i = 1; i < np; ++i) {
246 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
247 c2x[i] == xp[i] && c2y[i] == yp[i];
249 bpath.lineTo(xp[i], yp[i]);
251 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
253 QColor const color = computeColor(col);
254 setQPainterPen(color, ls, lw);
255 bool const text_is_antialiased = renderHints() & TextAntialiasing;
256 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
259 fillPath(bpath, QBrush(color));
260 setRenderHint(Antialiasing, false);
264 void GuiPainter::rectangle(int x, int y, int w, int h,
269 setQPainterPen(computeColor(col), ls, lw);
270 drawRect(x, y, w, h);
274 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
276 fillRect(x, y, w, h, guiApp->colorCache().get(col));
280 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
281 int a1, int a2, Color col)
283 // LyX usings 1/64ths degree, Qt usings 1/16th
284 setQPainterPen(computeColor(col));
285 bool const do_antialiasing = renderHints() & TextAntialiasing;
286 setRenderHint(Antialiasing, do_antialiasing);
287 drawArc(x, y, w, h, a1 / 4, a2 / 4);
288 setRenderHint(Antialiasing, false);
292 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
294 graphics::GuiImage const & qlimage =
295 static_cast<graphics::GuiImage const &>(i);
297 fillRectangle(x, y, w, h, Color_graphicsbg);
299 QImage const image = qlimage.image();
300 QRectF const drect = QRectF(x, y, w, h);
301 QRectF const srect = QRectF(0, 0, image.width(), image.height());
302 // Bilinear filtering is needed on a rare occasion for instant previews when
303 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
304 // This filter is optimised by qt on pixel-aligned images, so this does not
305 // affect performances in other cases.
306 setRenderHint(SmoothPixmapTransform);
307 drawImage(drect, image, srect);
308 setRenderHint(SmoothPixmapTransform, false);
312 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
314 text(x, y, docstring(1, c), f);
318 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
320 text(x, y, s, f, Auto, 0.0, 0.0);
324 void GuiPainter::do_drawText(int x, int y, QString str,
325 GuiPainter::Direction const dir,
326 FontInfo const & f, QFont ff)
328 setQPainterPen(computeColor(f.realColor()));
332 /* In LyX, the character direction is forced by the language.
333 * Therefore, we have to signal that fact to Qt.
336 /* Use unicode override characters to enforce drawing direction
337 * Source: http://www.iamcal.com/understanding-bidirectional-text/
340 // Right-to-left override: forces to draw text right-to-left
341 str = QChar(0x202E) + str;
343 // Left-to-right override: forces to draw text left-to-right
344 str = QChar(0x202D) + str;
347 /* This looks like a cleaner solution, but it has drawbacks
348 * - does not work reliably (Mac OS X, ...)
349 * - it is not really documented
350 * Keep it here for now, in case it can be helpful
352 //This is much stronger than setLayoutDirection.
355 flag = Qt::TextForceRightToLeft;
357 flag = Qt::TextForceLeftToRight;
358 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
359 flag | Qt::TextDontClip,
365 void GuiPainter::text(int x, int y, docstring const & s,
366 FontInfo const & f, Direction const dir,
367 double const wordspacing, double const tw)
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 later on again
380 interpreted as a position in the font.
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);
399 // Note that we have to take in account space stretching (word spacing)
400 textwidth = fm.width(s) +
401 static_cast<int>(fm.countExpanders(s) * wordspacing);
403 textwidth = static_cast<int>(tw);
405 textDecoration(f, x, y, textwidth);
407 if (use_pixmap_cache_) {
409 QString key = generateStringSignature(str, f, wordspacing);
411 // Warning: Left bearing is in general negative! Only the case
412 // where left bearing is negative is of interest WRT the
413 // pixmap width and the text x-position.
414 // Only the left bearing of the first character is important
415 // as we always write from left to right, even for
416 // right-to-left languages.
417 // FIXME: this is probably broken for RTL now that we draw full strings.
418 // Morover the first/last element is possibly not the right one since the glyph may have changed.
419 int const lb = min(fm.lbearing(s[0]), 0);
420 int const mA = fm.maxAscent();
421 if (QPixmapCache::find(key, pm)) {
422 // Draw the cached pixmap.
423 drawPixmap(x + lb, y - mA, pm);
427 // Only the right bearing of the last character is
428 // important as we always write from left to right,
429 // even for right-to-left languages.
430 int const rb = fm.rbearing(s[s.size()-1]);
431 int const w = textwidth + rb - lb;
432 int const mD = fm.maxDescent();
433 int const h = mA + mD;
434 if (w > 0 && h > 0) {
435 pm = QPixmap(static_cast<int>(pixelRatio() * w),
436 static_cast<int>(pixelRatio() * h));
437 #if QT_VERSION >= 0x050000
438 pm.setDevicePixelRatio(pixelRatio());
440 pm.fill(Qt::transparent);
441 GuiPainter p(&pm, pixelRatio());
442 p.do_drawText(-lb, mA, str, dir, f, ff);
443 QPixmapCache::insert(key, pm);
444 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
445 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
448 // Draw the new cached pixmap.
449 drawPixmap(x + lb, y - mA, pm);
450 //rectangle(x-lb, y-mA, w, h, Color_green);
455 // don't use the pixmap cache
456 setQPainterPen(computeColor(f.realColor()));
458 shared_ptr<QTextLayout const> ptl =
459 fm.getTextLayout(s, dir == RtL, wordspacing);
460 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
467 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
468 // << " at " << x << "," << y);
472 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
473 double const wordspacing, double const tw)
475 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
480 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
481 Color other, size_type const from, size_type const to,
482 double const wordspacing, double const tw)
484 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
485 FontInfo fi = f.fontInfo();
486 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
489 int const ascent = fm.maxAscent();
490 int const height = fm.maxAscent() + fm.maxDescent();
491 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
492 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
496 // First the part in other color
497 Color const orig = fi.realColor();
498 fi.setPaintColor(other);
499 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
501 text(x, y, str, fi, dir, wordspacing, tw);
503 // Then the part in normal color
504 // Note that in Qt5, it is not possible to use Qt::UniteClip,
505 // therefore QRegion is used.
506 fi.setPaintColor(orig);
507 QRegion region(viewport());
508 setClipRegion(region - clip);
509 text(x, y, str, fi, dir, wordspacing, tw);
514 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
516 if (f.underbar() == FONT_ON)
517 underline(f, x, y, width);
518 if (f.strikeout() == FONT_ON)
519 strikeoutLine(f, x, y, width);
520 if (f.xout() == FONT_ON)
521 crossoutLines(f, x, y, width);
522 if (f.uuline() == FONT_ON)
523 doubleUnderline(f, x, y, width);
524 if (f.uwave() == FONT_ON)
525 // f.color() doesn't work on some circumstances
526 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
530 static int max(int a, int b) { return a > b ? a : b; }
533 void GuiPainter::rectText(int x, int y, docstring const & str,
534 FontInfo const & font, Color back, Color frame)
536 int width, ascent, descent;
538 FontMetrics const & fm = theFontMetrics(font);
539 fm.rectText(str, width, ascent, descent);
541 if (back != Color_none)
542 fillRectangle(x + 1, y - ascent + 1, width - 1,
543 ascent + descent - 1, back);
545 if (frame != Color_none)
546 rectangle(x, y - ascent, width, ascent + descent, frame);
548 // FIXME: let offset depend on font
549 text(x + 3, y, str, font);
553 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
554 FontInfo const & font, Color back, Color frame, int offset)
556 int width, ascent, descent;
558 FontMetrics const & fm = theFontMetrics(font);
559 fm.buttonText(s, offset, width, ascent, descent);
561 static int const d = offset / 2;
563 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
564 ascent + descent - 1, back);
565 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
566 text(x + offset, baseline, s, font);
570 int GuiPainter::preeditText(int x, int y, char_type c,
571 FontInfo const & font, preedit_style style)
573 FontInfo temp_font = font;
574 FontMetrics const & fm = theFontMetrics(font);
575 int ascent = fm.maxAscent();
576 int descent = fm.maxDescent();
577 int height = ascent + descent;
578 int width = fm.width(c);
581 case preedit_default:
582 // default unselecting mode.
583 fillRectangle(x, y - height + 1, width, height, Color_background);
584 dashedUnderline(font, x, y - descent + 1, width);
586 case preedit_selecting:
587 // We are in selecting mode: white text on black background.
588 fillRectangle(x, y - height + 1, width, height, Color_black);
589 temp_font.setColor(Color_white);
592 // The character comes with a cursor.
593 fillRectangle(x, y - height + 1, width, height, Color_background);
594 underline(font, x, y - descent + 1, width);
597 text(x, y - descent + 1, c, temp_font);
603 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
606 FontMetrics const & fm = theFontMetrics(f);
607 int const pos = fm.underlinePos();
609 line(x, y + pos, x + width, y + pos,
610 f.realColor(), ls, fm.lineWidth());
614 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
616 FontMetrics const & fm = theFontMetrics(f);
617 int const pos = fm.strikeoutPos();
619 line(x, y - pos, x + width, y - pos,
620 f.realColor(), line_solid, fm.lineWidth());
624 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
627 tmpf.setXout(FONT_OFF);
629 // the definition of \xout in ulem.sty is
630 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
631 // Let's mimick it somewhat.
632 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
633 for (int i = 0 ; i < iround(width / offset) ; ++i)
634 text(x + iround(i * offset), y, '/', tmpf);
638 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
640 FontMetrics const & fm = theFontMetrics(f);
641 int const pos1 = fm.underlinePos() + fm.lineWidth();
642 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
644 line(x, y + pos1, x + width, y + pos1,
645 f.realColor(), line_solid, fm.lineWidth());
646 line(x, y + pos2, x + width, y + pos2,
647 f.realColor(), line_solid, fm.lineWidth());
651 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
653 FontMetrics const & fm = theFontMetrics(f);
655 int const below = max(fm.maxDescent() / 2, 2);
656 int height = max((fm.maxDescent() / 4) - 1, 1);
661 for (int n = 0; n != height; ++n)
662 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
666 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
668 setQPainterPen(computeColor(col));
670 int const xend = x + width;
672 //FIXME: I am not sure if Antialiasing gives the best effect.
673 //setRenderHint(Antialiasing, true);
676 drawLine(x, y - height, x + step, y + height);
678 drawLine(x, y + height, x + step/2, y + height);
681 //setRenderHint(Antialiasing, false);
684 } // namespace frontend