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 void GuiPainter::do_drawText(int x, int y, QString str, bool rtl, FontInfo const & f, QFont ff)
294 setQPainterPen(computeColor(f.realColor()));
298 /* In LyX, the character direction is forced by the language.
299 * Therefore, we have to signal that fact to Qt.
302 /* Use unicode override characters to enforce drawing direction
303 * Source: http://www.iamcal.com/understanding-bidirectional-text/
306 // Right-to-left override: forces to draw text right-to-left
307 str = QChar(0x202E) + str;
309 // Left-to-right override: forces to draw text left-to-right
310 str = QChar(0x202D) + str;
313 /* This looks like a cleaner solution, but it has drawbacks
314 * - does not work reliably (Mac OS X, ...)
315 * - it is not really documented
316 * Keep it here for now, in case it can be helpful
318 //This is much stronger than setLayoutDirection.
319 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
320 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
321 flag | Qt::TextDontClip,
327 int GuiPainter::text(int x, int y, docstring const & s,
328 FontInfo const & f, bool const rtl,
329 double const wordspacing)
331 //LYXERR0("text: x=" << x << ", s=" << s);
335 /* Caution: The following ucs4 to QString conversions work for symbol fonts
336 only because they are no real conversions but simple casts in reality.
337 When we want to draw a symbol or calculate the metrics we pass the position
338 of the symbol in the font (as given in lib/symbols) as a char_type to the
339 frontend. This is just wrong, because the symbol is no UCS4 character at
340 all. You can think of this number as the code point of the symbol in a
341 custom symbol encoding. It works because this char_type is lateron again
342 interpreted as a position in the font again.
343 The correct solution would be to have extra functions for symbols, but that
344 would require to duplicate a lot of frontend and mathed support code.
346 QString str = toqstr(s);
349 // HACK: QT3 refuses to show single compose characters
350 // Still needed with Qt4?
351 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
355 QFont ff = getFont(f);
356 ff.setWordSpacing(wordspacing);
357 GuiFontMetrics const & fm = getFontMetrics(f);
359 // Here we use the font width cache instead of
360 // textwidth = fontMetrics().width(str);
361 // because the above is awfully expensive on MacOSX
362 // Note that we have to take in account space stretching (word spacing)
363 int const textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
365 if (!isDrawingEnabled())
368 textDecoration(f, x, y, textwidth);
370 if (use_pixmap_cache_) {
372 QString key = generateStringSignature(str, f);
374 // Warning: Left bearing is in general negative! Only the case
375 // where left bearing is negative is of interest WRT the
376 // pixmap width and the text x-position.
377 // Only the left bearing of the first character is important
378 // as we always write from left to right, even for
379 // right-to-left languages.
380 // FIXME: this is probably broken for RTL now that we draw full strings.
381 // Morover the first/last element is possibly not the right one since the glyph may have changed.
382 int const lb = min(fm.lbearing(s[0]), 0);
383 int const mA = fm.maxAscent();
384 if (QPixmapCache::find(key, pm)) {
385 // Draw the cached pixmap.
386 drawPixmap(x + lb, y - mA, pm);
390 // Only the right bearing of the last character is
391 // important as we always write from left to right,
392 // even for right-to-left languages.
393 int const rb = fm.rbearing(s[s.size()-1]);
394 int const w = textwidth + rb - lb;
395 int const mD = fm.maxDescent();
396 int const h = mA + mD;
397 if (w > 0 && h > 0) {
398 pm = QPixmap(static_cast<int>(pixelRatio() * w),
399 static_cast<int>(pixelRatio() * h));
400 #if QT_VERSION >= 0x050000
401 pm.setDevicePixelRatio(pixelRatio());
403 pm.fill(Qt::transparent);
404 GuiPainter p(&pm, pixelRatio());
405 p.do_drawText(-lb, mA, str, rtl, f, ff);
406 QPixmapCache::insert(key, pm);
407 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
408 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
411 // Draw the new cached pixmap.
412 drawPixmap(x + lb, y - mA, pm);
413 //rectangle(x-lb, y-mA, w, h, Color_green);
418 // don't use the pixmap cache,
419 do_drawText(x, y, str, rtl, f, ff);
420 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
421 // << " at " << x << "," << y);
426 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
427 double const wordspacing)
429 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
433 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
434 Color other, size_type const from, size_type const to,
435 double const wordspacing)
437 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
438 FontInfo fi = f.fontInfo();
439 bool const rtl = f.isVisibleRightToLeft();
442 int const ascent = fm.maxAscent();
443 int const height = fm.maxAscent() + fm.maxDescent();
444 int xmin = fm.pos2x(str, from, rtl, wordspacing);
445 int xmax = fm.pos2x(str, to, rtl, wordspacing);
449 // First the part in other color
450 Color const orig = fi.realColor();
451 fi.setPaintColor(other);
452 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
454 int const textwidth = text(x, y, str, fi, rtl, wordspacing);
456 // Then the part in normal color
457 // Note that in Qt5, it is not possible to use Qt::UniteClip,
458 // therefore QRegion is used.
459 fi.setPaintColor(orig);
460 QRegion region(viewport());
461 setClipRegion(region - clip);
462 text(x, y, str, fi, rtl, wordspacing);
469 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
471 if (f.underbar() == FONT_ON)
472 underline(f, x, y, width);
473 if (f.strikeout() == FONT_ON)
474 strikeoutLine(f, x, y, width);
475 if (f.uuline() == FONT_ON)
476 doubleUnderline(f, x, y, width);
477 if (f.uwave() == FONT_ON)
478 // f.color() doesn't work on some circumstances
479 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
483 static int max(int a, int b) { return a > b ? a : b; }
486 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
489 fillRectangle(x, y, w, h, Color_buttonhoverbg);
491 fillRectangle(x, y, w, h, Color_buttonbg);
492 buttonFrame(x, y, w, h);
496 void GuiPainter::buttonFrame(int x, int y, int w, int h)
498 line(x, y, x, y + h - 1, Color_buttonframe);
499 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
500 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
501 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
505 void GuiPainter::rectText(int x, int y, docstring const & str,
506 FontInfo const & font, Color back, Color frame)
512 FontMetrics const & fm = theFontMetrics(font);
513 fm.rectText(str, width, ascent, descent);
515 if (back != Color_none)
516 fillRectangle(x + 1, y - ascent + 1, width - 1,
517 ascent + descent - 1, back);
519 if (frame != Color_none)
520 rectangle(x, y - ascent, width, ascent + descent, frame);
522 text(x + 3, y, str, font);
526 void GuiPainter::buttonText(int x, int y, docstring const & str,
527 FontInfo const & font, bool mouseHover)
533 FontMetrics const & fm = theFontMetrics(font);
534 fm.buttonText(str, width, ascent, descent);
536 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
538 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
539 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
543 int GuiPainter::preeditText(int x, int y, char_type c,
544 FontInfo const & font, preedit_style style)
546 FontInfo temp_font = font;
547 FontMetrics const & fm = theFontMetrics(font);
548 int ascent = fm.maxAscent();
549 int descent = fm.maxDescent();
550 int height = ascent + descent;
551 int width = fm.width(c);
554 case preedit_default:
555 // default unselecting mode.
556 fillRectangle(x, y - height + 1, width, height, Color_background);
557 dashedUnderline(font, x, y - descent + 1, width);
559 case preedit_selecting:
560 // We are in selecting mode: white text on black background.
561 fillRectangle(x, y - height + 1, width, height, Color_black);
562 temp_font.setColor(Color_white);
565 // The character comes with a cursor.
566 fillRectangle(x, y - height + 1, width, height, Color_background);
567 underline(font, x, y - descent + 1, width);
570 text(x, y - descent + 1, c, temp_font);
576 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
579 FontMetrics const & fm = theFontMetrics(f);
580 int const pos = fm.underlinePos();
582 line(x, y + pos, x + width, y + pos,
583 f.realColor(), ls, fm.lineWidth());
587 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
589 FontMetrics const & fm = theFontMetrics(f);
590 int const pos = fm.strikeoutPos();
592 line(x, y - pos, x + width, y - pos,
593 f.realColor(), line_solid, fm.lineWidth());
597 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
599 FontMetrics const & fm = theFontMetrics(f);
600 int const pos1 = fm.underlinePos() + fm.lineWidth();
601 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
603 line(x, y + pos1, x + width, y + pos1,
604 f.realColor(), line_solid, fm.lineWidth());
605 line(x, y + pos2, x + width, y + pos2,
606 f.realColor(), line_solid, fm.lineWidth());
610 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
612 FontMetrics const & fm = theFontMetrics(f);
614 int const below = max(fm.maxDescent() / 2, 2);
615 int height = max((fm.maxDescent() / 4) - 1, 1);
620 for (int n = 0; n != height; ++n)
621 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
625 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
627 setQPainterPen(computeColor(col));
629 int const xend = x + width;
631 //FIXME: I am not sure if Antialiasing gives the best effect.
632 //setRenderHint(Antialiasing, true);
635 drawLine(x, y - height, x + step, y + height);
637 drawLine(x, y + height, x + step/2, y + height);
640 //setRenderHint(Antialiasing, false);
643 } // namespace frontend