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, FontInfo const & f)
96 sig.append(QChar(static_cast<short>(f.family())));
97 sig.append(QChar(static_cast<short>(f.series())));
98 sig.append(QChar(static_cast<short>(f.realShape())));
99 sig.append(QChar(static_cast<short>(f.size())));
100 Color const & color = f.realColor();
101 sig.append(QChar(static_cast<short>(color.baseColor)));
102 sig.append(QChar(static_cast<short>(color.mergeColor)));
103 if (!monochrome_min_.empty()) {
104 QColor const & min = monochrome_min_.top();
105 QColor const & max = monochrome_max_.top();
106 sig.append(QChar(static_cast<short>(min.red())));
107 sig.append(QChar(static_cast<short>(min.green())));
108 sig.append(QChar(static_cast<short>(min.blue())));
109 sig.append(QChar(static_cast<short>(max.red())));
110 sig.append(QChar(static_cast<short>(max.green())));
111 sig.append(QChar(static_cast<short>(max.blue())));
117 QColor GuiPainter::computeColor(Color col)
119 return filterColor(guiApp->colorCache().get(col));
123 QColor GuiPainter::filterColor(QColor const & col)
125 if (monochrome_min_.empty())
128 // map into [min,max] interval
129 QColor const & min = monochrome_min_.top();
130 QColor const & max = monochrome_max_.top();
132 qreal v = col.valueF();
133 v *= v; // make it a bit steeper (i.e. darker)
135 qreal minr, ming, minb;
136 qreal maxr, maxg, maxb;
137 min.getRgbF(&minr, &ming, &minb);
138 max.getRgbF(&maxr, &maxg, &maxb);
142 v * (minr - maxr) + maxr,
143 v * (ming - maxg) + maxg,
144 v * (minb - maxb) + maxb);
149 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
151 QColor qmin = filterColor(guiApp->colorCache().get(min));
152 QColor qmax = filterColor(guiApp->colorCache().get(max));
153 monochrome_min_.push(qmin);
154 monochrome_max_.push(qmax);
158 void GuiPainter::leaveMonochromeMode()
160 LASSERT(!monochrome_min_.empty(), return);
161 monochrome_min_.pop();
162 monochrome_max_.pop();
166 void GuiPainter::point(int x, int y, Color col)
168 if (!isDrawingEnabled())
171 setQPainterPen(computeColor(col));
176 void GuiPainter::line(int x1, int y1, int x2, int y2,
181 if (!isDrawingEnabled())
184 setQPainterPen(computeColor(col), ls, lw);
185 bool const do_antialiasing = renderHints() & TextAntialiasing
186 && x1 != x2 && y1 != y2;
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 if (!isDrawingEnabled())
202 // double the size if needed
204 static QVector<QPoint> points(32);
205 if (np > points.size())
206 points.resize(2 * np);
208 bool antialias = false;
209 for (int i = 0; i < np; ++i) {
210 points[i].setX(xp[i]);
211 points[i].setY(yp[i]);
213 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
215 QColor const color = computeColor(col);
216 setQPainterPen(color, ls, lw);
217 bool const text_is_antialiased = renderHints() & TextAntialiasing;
218 setRenderHint(Antialiasing, antialias && text_is_antialiased);
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::rectangle(int x, int y, int w, int h,
237 if (!isDrawingEnabled())
240 setQPainterPen(computeColor(col), ls, lw);
241 drawRect(x, y, w, h);
245 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
247 if (!isDrawingEnabled())
250 fillRect(x, y, w, h, guiApp->colorCache().get(col));
254 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
255 int a1, int a2, Color col)
257 if (!isDrawingEnabled())
260 // LyX usings 1/64ths degree, Qt usings 1/16th
261 setQPainterPen(computeColor(col));
262 bool const do_antialiasing = renderHints() & TextAntialiasing;
263 setRenderHint(Antialiasing, do_antialiasing);
264 drawArc(x, y, w, h, a1 / 4, a2 / 4);
265 setRenderHint(Antialiasing, false);
269 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
271 graphics::GuiImage const & qlimage =
272 static_cast<graphics::GuiImage const &>(i);
274 fillRectangle(x, y, w, h, Color_graphicsbg);
276 if (!isDrawingEnabled())
279 QImage const image = qlimage.image();
280 QRectF const drect = QRectF(x, y, w, h);
281 QRectF const srect = QRectF(0, 0, image.width(), image.height());
282 drawImage(drect, image, srect);
286 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
288 return text(x, y, docstring(1, c), f);
292 int GuiPainter::text(int x, int y, docstring const & s,
293 FontInfo const & f, bool const rtl)
295 //LYXERR0("text: x=" << x << ", s=" << s);
299 /* Caution: The following ucs4 to QString conversions work for symbol fonts
300 only because they are no real conversions but simple casts in reality.
301 When we want to draw a symbol or calculate the metrics we pass the position
302 of the symbol in the font (as given in lib/symbols) as a char_type to the
303 frontend. This is just wrong, because the symbol is no UCS4 character at
304 all. You can think of this number as the code point of the symbol in a
305 custom symbol encoding. It works because this char_type is lateron again
306 interpreted as a position in the font again.
307 The correct solution would be to have extra functions for symbols, but that
308 would require to duplicate a lot of frontend and mathed support code.
310 QString str = toqstr(s);
313 // HACK: QT3 refuses to show single compose characters
314 // Still needed with Qt4?
315 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
319 QFont const & ff = getFont(f);
320 GuiFontMetrics const & fm = getFontMetrics(f);
322 // Here we use the font width cache instead of
323 // textwidth = fontMetrics().width(str);
324 // because the above is awfully expensive on MacOSX
325 int const textwidth = fm.width(s);
327 if (!isDrawingEnabled())
330 textDecoration(f, x, y, textwidth);
332 if (use_pixmap_cache_) {
334 QString key = generateStringSignature(str, f);
336 // Warning: Left bearing is in general negative! Only the case
337 // where left bearing is negative is of interest WRT the
338 // pixmap width and the text x-position.
339 // Only the left bearing of the first character is important
340 // as we always write from left to right, even for
341 // right-to-left languages.
342 int const lb = min(fm.lbearing(s[0]), 0);
343 int const mA = fm.maxAscent();
344 if (QPixmapCache::find(key, pm)) {
345 // Draw the cached pixmap.
346 drawPixmap(x + lb, y - mA, pm);
350 // Only the right bearing of the last character is
351 // important as we always write from left to right,
352 // even for right-to-left languages.
353 int const rb = fm.rbearing(s[s.size()-1]);
354 int const w = textwidth + rb - lb;
355 int const mD = fm.maxDescent();
356 int const h = mA + mD;
357 if (w > 0 && h > 0) {
358 pm = QPixmap(static_cast<int>(pixelRatio() * w),
359 static_cast<int>(pixelRatio() * h));
360 #if QT_VERSION >= 0x050000
361 pm.setDevicePixelRatio(pixelRatio());
363 pm.fill(Qt::transparent);
364 GuiPainter p(&pm, pixelRatio());
365 p.setQPainterPen(computeColor(f.realColor()));
368 // We need to draw the text as LTR as we use our own bidi code.
369 p.setLayoutDirection(Qt::LeftToRight);
370 p.drawText(-lb, mA, str);
371 QPixmapCache::insert(key, pm);
372 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
373 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
376 // Draw the new cached pixmap.
377 drawPixmap(x + lb, y - mA, pm);
383 // don't use the pixmap cache,
384 // draw directly onto the painting device
385 setQPainterPen(computeColor(f.realColor()));
389 /* In LyX, the character direction is forced by the language.
390 * Therefore, we have to signal that fact to Qt.
393 /* Use unicode override characters to enforce drawing direction
394 * Source: http://www.iamcal.com/understanding-bidirectional-text/
397 // Right-to-left override: forces to draw text right-to-left
398 str = QChar(0x202E) + str;
400 // Left-to-right override: forces to draw text left-to-right
401 str = QChar(0x202D) + str;
404 /* This looks like a cleaner solution, but it has drawbacks
405 * - does not work reliably (Mac OS X, ...)
406 * - it is not really documented
407 * Keep it here for now, in case it can be helpful
409 //This is much stronger than setLayoutDirection.
410 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
411 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
412 flag | Qt::TextDontClip,
415 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
416 // << " at " << x << "," << y);
421 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
423 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
427 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
428 Color other, size_type from, size_type to)
430 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
431 FontInfo fi = f.fontInfo();
432 bool const rtl = f.isVisibleRightToLeft();
435 int const ascent = fm.maxAscent();
436 int const height = fm.maxAscent() + fm.maxDescent();
437 int xmin = fm.pos2x(str, from, rtl);
438 int xmax = fm.pos2x(str, to, rtl);
442 // First the part in other color
443 Color const orig = fi.realColor();
444 fi.setPaintColor(other);
445 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
447 int const textwidth = text(x, y, str, fi, rtl);
449 // Then the part in normal color
450 // Note that in Qt5, it is not possible to use Qt::UniteClip,
451 // therefore QRegion is used.
452 fi.setPaintColor(orig);
453 QRegion region(viewport());
454 setClipRegion(region - clip);
455 text(x, y, str, fi, rtl);
462 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
464 if (f.underbar() == FONT_ON)
465 underline(f, x, y, width);
466 if (f.strikeout() == FONT_ON)
467 strikeoutLine(f, x, y, width);
468 if (f.uuline() == FONT_ON)
469 doubleUnderline(f, x, y, width);
470 if (f.uwave() == FONT_ON)
471 // f.color() doesn't work on some circumstances
472 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
476 static int max(int a, int b) { return a > b ? a : b; }
479 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
482 fillRectangle(x, y, w, h, Color_buttonhoverbg);
484 fillRectangle(x, y, w, h, Color_buttonbg);
485 buttonFrame(x, y, w, h);
489 void GuiPainter::buttonFrame(int x, int y, int w, int h)
491 line(x, y, x, y + h - 1, Color_buttonframe);
492 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
493 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
494 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
498 void GuiPainter::rectText(int x, int y, docstring const & str,
499 FontInfo const & font, Color back, Color frame)
505 FontMetrics const & fm = theFontMetrics(font);
506 fm.rectText(str, width, ascent, descent);
508 if (back != Color_none)
509 fillRectangle(x + 1, y - ascent + 1, width - 1,
510 ascent + descent - 1, back);
512 if (frame != Color_none)
513 rectangle(x, y - ascent, width, ascent + descent, frame);
515 text(x + 3, y, str, font);
519 void GuiPainter::buttonText(int x, int y, docstring const & str,
520 FontInfo const & font, bool mouseHover)
526 FontMetrics const & fm = theFontMetrics(font);
527 fm.buttonText(str, width, ascent, descent);
529 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
531 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
532 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
536 int GuiPainter::preeditText(int x, int y, char_type c,
537 FontInfo const & font, preedit_style style)
539 FontInfo temp_font = font;
540 FontMetrics const & fm = theFontMetrics(font);
541 int ascent = fm.maxAscent();
542 int descent = fm.maxDescent();
543 int height = ascent + descent;
544 int width = fm.width(c);
547 case preedit_default:
548 // default unselecting mode.
549 fillRectangle(x, y - height + 1, width, height, Color_background);
550 dashedUnderline(font, x, y - descent + 1, width);
552 case preedit_selecting:
553 // We are in selecting mode: white text on black background.
554 fillRectangle(x, y - height + 1, width, height, Color_black);
555 temp_font.setColor(Color_white);
558 // The character comes with a cursor.
559 fillRectangle(x, y - height + 1, width, height, Color_background);
560 underline(font, x, y - descent + 1, width);
563 text(x, y - descent + 1, c, temp_font);
569 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
571 FontMetrics const & fm = theFontMetrics(f);
573 int const below = max(fm.maxDescent() / 2, 2);
575 line(x, y + below, x + width, y + below, f.realColor());
576 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
580 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
582 FontMetrics const & fm = theFontMetrics(f);
584 int const below = max(fm.maxDescent() / 2, 2);
585 int const height = max((fm.maxDescent() / 4) - 1, 1);
588 line(x, y + below, x + width, y + below, f.realColor());
590 fillRectangle(x, y + below, width, below + height, f.realColor());
594 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
596 FontMetrics const & fm = theFontMetrics(f);
598 int const middle = max((fm.maxHeight() / 4), 1);
599 int const height = middle/3;
602 line(x, y - middle, x + width, y - middle, f.realColor());
604 fillRectangle(x, y - middle, width, height, f.realColor());
608 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
610 FontMetrics const & fm = theFontMetrics(f);
612 int const below = max(fm.maxDescent() / 2, 2);
613 int height = max((fm.maxDescent() / 4) - 1, 1);
618 for (int n = 0; n != height; ++n)
619 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
623 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
625 setQPainterPen(computeColor(col));
627 int const xend = x + width;
629 //FIXME: I am not sure if Antialiasing gives the best effect.
630 //setRenderHint(Antialiasing, true);
633 drawLine(x, y - height, x + step, y + height);
635 drawLine(x, y + height, x + step/2, y + height);
638 //setRenderHint(Antialiasing, false);
641 } // namespace frontend