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.
13 #define USE_RTL_OVERRIDE 1
18 #include "GuiPainter.h"
20 #include "ColorCache.h"
21 #include "GuiApplication.h"
22 #include "GuiFontLoader.h"
23 #include "GuiFontMetrics.h"
25 #include "qt_helpers.h"
31 #include "insets/Inset.h"
33 #include "support/lassert.h"
34 #include "support/debug.h"
36 #include <QPixmapCache>
37 #include <QTextLayout>
39 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
40 // drawing text. This is especially useful for older PPC/Mac systems.
41 #if defined(Q_WS_X11) || defined(QPA_XCB)
42 #define USE_PIXMAP_CACHE 0
44 #define USE_PIXMAP_CACHE 1
48 using namespace lyx::support;
53 const float Painter::thin_line = 0.0;
55 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
56 : QPainter(device), Painter(pixel_ratio),
57 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
59 // new QPainter has default QPen:
60 current_color_ = guiApp->colorCache().get(Color_black);
61 current_ls_ = line_solid;
62 current_lw_ = thin_line;
66 GuiPainter::~GuiPainter()
69 //lyxerr << "GuiPainter::end()" << endl;
73 void GuiPainter::setQPainterPen(QColor const & col,
74 Painter::line_style ls, float lw)
76 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
83 QPen pen = QPainter::pen();
87 case line_solid: pen.setStyle(Qt::SolidLine); break;
88 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
97 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
100 sig.append(QChar(static_cast<short>(f.family())));
101 sig.append(QChar(static_cast<short>(f.series())));
102 sig.append(QChar(static_cast<short>(f.realShape())));
103 sig.append(QChar(static_cast<short>(f.size())));
104 Color const & color = f.realColor();
105 sig.append(QChar(static_cast<short>(color.baseColor)));
106 sig.append(QChar(static_cast<short>(color.mergeColor)));
107 if (!monochrome_min_.empty()) {
108 QColor const & min = monochrome_min_.top();
109 QColor const & max = monochrome_max_.top();
110 sig.append(QChar(static_cast<short>(min.red())));
111 sig.append(QChar(static_cast<short>(min.green())));
112 sig.append(QChar(static_cast<short>(min.blue())));
113 sig.append(QChar(static_cast<short>(max.red())));
114 sig.append(QChar(static_cast<short>(max.green())));
115 sig.append(QChar(static_cast<short>(max.blue())));
121 QColor GuiPainter::computeColor(Color col)
123 return filterColor(guiApp->colorCache().get(col));
127 QColor GuiPainter::filterColor(QColor const & col)
129 if (monochrome_min_.empty())
132 // map into [min,max] interval
133 QColor const & min = monochrome_min_.top();
134 QColor const & max = monochrome_max_.top();
136 qreal v = col.valueF();
137 v *= v; // make it a bit steeper (i.e. darker)
139 qreal minr, ming, minb;
140 qreal maxr, maxg, maxb;
141 min.getRgbF(&minr, &ming, &minb);
142 max.getRgbF(&maxr, &maxg, &maxb);
146 v * (minr - maxr) + maxr,
147 v * (ming - maxg) + maxg,
148 v * (minb - maxb) + maxb);
153 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
155 QColor qmin = filterColor(guiApp->colorCache().get(min));
156 QColor qmax = filterColor(guiApp->colorCache().get(max));
157 monochrome_min_.push(qmin);
158 monochrome_max_.push(qmax);
162 void GuiPainter::leaveMonochromeMode()
164 LASSERT(!monochrome_min_.empty(), return);
165 monochrome_min_.pop();
166 monochrome_max_.pop();
170 void GuiPainter::point(int x, int y, Color col)
172 if (!isDrawingEnabled())
175 setQPainterPen(computeColor(col));
180 void GuiPainter::line(int x1, int y1, int x2, int y2,
185 if (!isDrawingEnabled())
188 setQPainterPen(computeColor(col), ls, lw);
189 bool const do_antialiasing = renderHints() & TextAntialiasing
190 && x1 != x2 && y1 != y2;
191 setRenderHint(Antialiasing, do_antialiasing);
192 drawLine(x1, y1, x2, y2);
193 setRenderHint(Antialiasing, false);
197 void GuiPainter::lines(int const * xp, int const * yp, int np,
203 if (!isDrawingEnabled())
206 // double the size if needed
208 static QVector<QPoint> points(32);
209 if (np > points.size())
210 points.resize(2 * np);
212 bool antialias = false;
213 for (int i = 0; i < np; ++i) {
214 points[i].setX(xp[i]);
215 points[i].setY(yp[i]);
217 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
219 QColor const color = computeColor(col);
220 setQPainterPen(color, ls, lw);
221 bool const text_is_antialiased = renderHints() & TextAntialiasing;
222 setRenderHint(Antialiasing, antialias && text_is_antialiased);
223 if (fs == fill_none) {
224 drawPolyline(points.data(), np);
226 QBrush const oldbrush = brush();
227 setBrush(QBrush(color));
228 drawPolygon(points.data(), np, fs == fill_oddeven ?
229 Qt::OddEvenFill : Qt::WindingFill);
232 setRenderHint(Antialiasing, false);
236 void GuiPainter::rectangle(int x, int y, int w, int h,
241 if (!isDrawingEnabled())
244 setQPainterPen(computeColor(col), ls, lw);
245 drawRect(x, y, w, h);
249 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
251 if (!isDrawingEnabled())
254 fillRect(x, y, w, h, guiApp->colorCache().get(col));
258 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
259 int a1, int a2, Color col)
261 if (!isDrawingEnabled())
264 // LyX usings 1/64ths degree, Qt usings 1/16th
265 setQPainterPen(computeColor(col));
266 bool const do_antialiasing = renderHints() & TextAntialiasing;
267 setRenderHint(Antialiasing, do_antialiasing);
268 drawArc(x, y, w, h, a1 / 4, a2 / 4);
269 setRenderHint(Antialiasing, false);
273 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
275 graphics::GuiImage const & qlimage =
276 static_cast<graphics::GuiImage const &>(i);
278 fillRectangle(x, y, w, h, Color_graphicsbg);
280 if (!isDrawingEnabled())
283 QImage const image = qlimage.image();
284 QRectF const drect = QRectF(x, y, w, h);
285 QRectF const srect = QRectF(0, 0, image.width(), image.height());
286 drawImage(drect, image, srect);
290 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
292 return text(x, y, docstring(1, c), f);
296 int GuiPainter::text(int x, int y, docstring const & s,
297 FontInfo const & f, bool const rtl)
299 //LYXERR0("text: x=" << x << ", s=" << s);
303 /* Caution: The following ucs4 to QString conversions work for symbol fonts
304 only because they are no real conversions but simple casts in reality.
305 When we want to draw a symbol or calculate the metrics we pass the position
306 of the symbol in the font (as given in lib/symbols) as a char_type to the
307 frontend. This is just wrong, because the symbol is no UCS4 character at
308 all. You can think of this number as the code point of the symbol in a
309 custom symbol encoding. It works because this char_type is lateron again
310 interpreted as a position in the font again.
311 The correct solution would be to have extra functions for symbols, but that
312 would require to duplicate a lot of frontend and mathed support code.
314 QString str = toqstr(s);
317 // HACK: QT3 refuses to show single compose characters
318 // Still needed with Qt4?
319 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
323 QFont const & ff = getFont(f);
324 GuiFontMetrics const & fm = getFontMetrics(f);
326 // Here we use the font width cache instead of
327 // textwidth = fontMetrics().width(str);
328 // because the above is awfully expensive on MacOSX
329 int const textwidth = fm.width(s);
331 if (!isDrawingEnabled())
334 textDecoration(f, x, y, textwidth);
336 if (use_pixmap_cache_) {
338 QString key = generateStringSignature(str, f);
340 // Warning: Left bearing is in general negative! Only the case
341 // where left bearing is negative is of interest WRT the
342 // pixmap width and the text x-position.
343 // Only the left bearing of the first character is important
344 // as we always write from left to right, even for
345 // right-to-left languages.
346 int const lb = min(fm.lbearing(s[0]), 0);
347 int const mA = fm.maxAscent();
348 if (QPixmapCache::find(key, pm)) {
349 // Draw the cached pixmap.
350 drawPixmap(x + lb, y - mA, pm);
354 // Only the right bearing of the last character is
355 // important as we always write from left to right,
356 // even for right-to-left languages.
357 int const rb = fm.rbearing(s[s.size()-1]);
358 int const w = textwidth + rb - lb;
359 int const mD = fm.maxDescent();
360 int const h = mA + mD;
361 if (w > 0 && h > 0) {
362 pm = QPixmap(static_cast<int>(pixelRatio() * w),
363 static_cast<int>(pixelRatio() * h));
364 #if QT_VERSION >= 0x050000
365 pm.setDevicePixelRatio(pixelRatio());
367 pm.fill(Qt::transparent);
368 GuiPainter p(&pm, pixelRatio());
369 p.setQPainterPen(computeColor(f.realColor()));
372 // We need to draw the text as LTR as we use our own bidi code.
373 p.setLayoutDirection(Qt::LeftToRight);
374 p.drawText(-lb, mA, str);
375 QPixmapCache::insert(key, pm);
376 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
377 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
380 // Draw the new cached pixmap.
381 drawPixmap(x + lb, y - mA, pm);
387 // don't use the pixmap cache,
388 // draw directly onto the painting device
389 setQPainterPen(computeColor(f.realColor()));
393 /* In LyX, the character direction is forced by the language.
394 * Therefore, we have to signal that fact to Qt.
396 #ifdef USE_RTL_OVERRIDE
397 /* Use unicode override characters to enforce drawing direction
398 * Source: http://www.iamcal.com/understanding-bidirectional-text/
401 // Right-to-left override: forces to draw text right-to-left
402 str = QChar(0x202E) + str;
404 // Left-to-right override: forces to draw text left-to-right
405 str = QChar(0x202D) + str;
408 /* This is a cleanr solution, but it has two drawbacks
409 * - it seems that it does not work under Mac OS X
410 * - it is not really documented
412 //This is much stronger than setLayoutDirection.
413 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
414 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
415 flag | Qt::TextDontClip,
418 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
419 // << " at " << x << "," << y);
424 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
426 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
430 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
431 Color other, size_type from, size_type to)
433 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
434 FontInfo fi = f.fontInfo();
435 bool const rtl = f.isVisibleRightToLeft();
438 int const ascent = fm.maxAscent();
439 int const height = fm.maxAscent() + fm.maxDescent();
440 int xmin = fm.pos2x(str, from, rtl);
441 int xmax = fm.pos2x(str, to, rtl);
445 // First the part in other color
446 Color const orig = fi.realColor();
447 fi.setPaintColor(other);
448 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
450 int const textwidth = text(x, y, str, fi, rtl);
452 // Then the part in normal color
453 // Note that in Qt5, it is not possible to use Qt::UniteClip,
454 // therefore QRegion is used.
455 fi.setPaintColor(orig);
456 QRegion region(viewport());
457 setClipRegion(region - clip);
458 text(x, y, str, fi, rtl);
465 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
467 if (f.underbar() == FONT_ON)
468 underline(f, x, y, width);
469 if (f.strikeout() == FONT_ON)
470 strikeoutLine(f, x, y, width);
471 if (f.uuline() == FONT_ON)
472 doubleUnderline(f, x, y, width);
473 if (f.uwave() == FONT_ON)
474 // f.color() doesn't work on some circumstances
475 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
479 static int max(int a, int b) { return a > b ? a : b; }
482 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
485 fillRectangle(x, y, w, h, Color_buttonhoverbg);
487 fillRectangle(x, y, w, h, Color_buttonbg);
488 buttonFrame(x, y, w, h);
492 void GuiPainter::buttonFrame(int x, int y, int w, int h)
494 line(x, y, x, y + h - 1, Color_buttonframe);
495 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
496 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
497 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
501 void GuiPainter::rectText(int x, int y, docstring const & str,
502 FontInfo const & font, Color back, Color frame)
508 FontMetrics const & fm = theFontMetrics(font);
509 fm.rectText(str, width, ascent, descent);
511 if (back != Color_none)
512 fillRectangle(x + 1, y - ascent + 1, width - 1,
513 ascent + descent - 1, back);
515 if (frame != Color_none)
516 rectangle(x, y - ascent, width, ascent + descent, frame);
518 text(x + 3, y, str, font);
522 void GuiPainter::buttonText(int x, int y, docstring const & str,
523 FontInfo const & font, bool mouseHover)
529 FontMetrics const & fm = theFontMetrics(font);
530 fm.buttonText(str, width, ascent, descent);
532 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
534 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
535 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
539 int GuiPainter::preeditText(int x, int y, char_type c,
540 FontInfo const & font, preedit_style style)
542 FontInfo temp_font = font;
543 FontMetrics const & fm = theFontMetrics(font);
544 int ascent = fm.maxAscent();
545 int descent = fm.maxDescent();
546 int height = ascent + descent;
547 int width = fm.width(c);
550 case preedit_default:
551 // default unselecting mode.
552 fillRectangle(x, y - height + 1, width, height, Color_background);
553 dashedUnderline(font, x, y - descent + 1, width);
555 case preedit_selecting:
556 // We are in selecting mode: white text on black background.
557 fillRectangle(x, y - height + 1, width, height, Color_black);
558 temp_font.setColor(Color_white);
561 // The character comes with a cursor.
562 fillRectangle(x, y - height + 1, width, height, Color_background);
563 underline(font, x, y - descent + 1, width);
566 text(x, y - descent + 1, c, temp_font);
572 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
574 FontMetrics const & fm = theFontMetrics(f);
576 int const below = max(fm.maxDescent() / 2, 2);
578 line(x, y + below, x + width, y + below, f.realColor());
579 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
583 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
585 FontMetrics const & fm = theFontMetrics(f);
587 int const below = max(fm.maxDescent() / 2, 2);
588 int const height = max((fm.maxDescent() / 4) - 1, 1);
591 line(x, y + below, x + width, y + below, f.realColor());
593 fillRectangle(x, y + below, width, below + height, f.realColor());
597 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
599 FontMetrics const & fm = theFontMetrics(f);
601 int const middle = max((fm.maxHeight() / 4), 1);
602 int const height = middle/3;
605 line(x, y - middle, x + width, y - middle, f.realColor());
607 fillRectangle(x, y - middle, width, height, f.realColor());
611 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
613 FontMetrics const & fm = theFontMetrics(f);
615 int const below = max(fm.maxDescent() / 2, 2);
616 int height = max((fm.maxDescent() / 4) - 1, 1);
621 for (int n = 0; n != height; ++n)
622 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
626 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
628 setQPainterPen(computeColor(col));
630 int const xend = x + width;
632 //FIXME: I am not sure if Antialiasing gives the best effect.
633 //setRenderHint(Antialiasing, true);
636 drawLine(x, y - height, x + step, y + height);
638 drawLine(x, y + height, x + step/2, y + height);
641 //setRenderHint(Antialiasing, false);
644 } // namespace frontend