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.
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 // Qt4 does not display a glyph whose codepoint is the
337 // same as that of a soft-hyphen (0x00ad), unless it
338 // occurs at a line-break. As a kludge, we force Qt to
339 // render this glyph using a one-column line.
340 // This is needed for some math glyphs.
341 // Should the soft hyphen char be displayed at all?
342 // I don't think so (i.e., Qt is correct as far as
343 // texted is concerned). /spitz
344 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
345 setQPainterPen(computeColor(f.realColor()));
346 QTextLayout adsymbol(str);
347 adsymbol.setFont(ff);
348 adsymbol.beginLayout();
349 QTextLine line = adsymbol.createLine();
350 line.setNumColumns(1);
351 line.setPosition(QPointF(0, -line.ascent()));
352 adsymbol.endLayout();
353 line.draw(this, QPointF(x, y));
357 if (use_pixmap_cache_) {
359 QString key = generateStringSignature(str, f);
361 // Warning: Left bearing is in general negative! Only the case
362 // where left bearing is negative is of interest WRT the
363 // pixmap width and the text x-position.
364 // Only the left bearing of the first character is important
365 // as we always write from left to right, even for
366 // right-to-left languages.
367 int const lb = min(fm.lbearing(s[0]), 0);
368 int const mA = fm.maxAscent();
369 if (QPixmapCache::find(key, pm)) {
370 // Draw the cached pixmap.
371 drawPixmap(x + lb, y - mA, pm);
375 // Only the right bearing of the last character is
376 // important as we always write from left to right,
377 // even for right-to-left languages.
378 int const rb = fm.rbearing(s[s.size()-1]);
379 int const w = textwidth + rb - lb;
380 int const mD = fm.maxDescent();
381 int const h = mA + mD;
382 if (w > 0 && h > 0) {
383 pm = QPixmap(pixelRatio() * w , pixelRatio() * h);
384 #if QT_VERSION >= 0x050000
385 pm.setDevicePixelRatio(pixelRatio());
387 pm.fill(Qt::transparent);
388 GuiPainter p(&pm, pixelRatio());
389 p.setQPainterPen(computeColor(f.realColor()));
392 // We need to draw the text as LTR as we use our own bidi code.
393 p.setLayoutDirection(Qt::LeftToRight);
394 p.drawText(-lb, mA, str);
395 QPixmapCache::insert(key, pm);
396 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
397 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
400 // Draw the new cached pixmap.
401 drawPixmap(x + lb, y - mA, pm);
407 // don't use the pixmap cache,
408 // draw directly onto the painting device
409 setQPainterPen(computeColor(f.realColor()));
413 /* In LyX, the character direction is forced by the language.
414 * Therefore, we have to signal that fact to Qt.
416 #ifdef USE_RTL_OVERRIDE
417 /* Use unicode override characters to enforce drawing direction
418 * Source: http://www.iamcal.com/understanding-bidirectional-text/
421 // Right-to-left override: forces to draw text right-to-left
422 str = QChar(0x202E) + str;
424 // Left-to-right override: forces to draw text left-to-right
425 str = QChar(0x202D) + str;
428 /* This is a cleanr solution, but it has two drawbacks
429 * - it seems that it does not work under Mac OS X
430 * - it is not really documented
432 //This is much stronger than setLayoutDirection.
433 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
434 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
435 flag | Qt::TextDontClip,
438 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
439 // << " at " << x << "," << y);
444 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
446 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
450 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
451 Color other, size_type from, size_type to)
453 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
454 FontInfo fi = f.fontInfo();
455 bool const rtl = f.isVisibleRightToLeft();
458 int const ascent = fm.maxAscent();
459 int const height = fm.maxAscent() + fm.maxDescent();
460 int xmin = fm.pos2x(str, from, rtl);
461 int xmax = fm.pos2x(str, to, rtl);
465 // First the part in other color
466 Color const orig = fi.realColor();
467 fi.setPaintColor(other);
468 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
470 int const textwidth = text(x, y, str, fi, rtl);
472 // Then the part in normal color
473 // Note that in Qt5, it is not possible to use Qt::UniteClip,
474 // therefore QRegion is used.
475 fi.setPaintColor(orig);
476 QRegion region(viewport());
477 setClipRegion(region - clip);
478 text(x, y, str, fi, rtl);
485 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
487 if (f.underbar() == FONT_ON)
488 underline(f, x, y, width);
489 if (f.strikeout() == FONT_ON)
490 strikeoutLine(f, x, y, width);
491 if (f.uuline() == FONT_ON)
492 doubleUnderline(f, x, y, width);
493 if (f.uwave() == FONT_ON)
494 // f.color() doesn't work on some circumstances
495 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
499 static int max(int a, int b) { return a > b ? a : b; }
502 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
505 fillRectangle(x, y, w, h, Color_buttonhoverbg);
507 fillRectangle(x, y, w, h, Color_buttonbg);
508 buttonFrame(x, y, w, h);
512 void GuiPainter::buttonFrame(int x, int y, int w, int h)
514 line(x, y, x, y + h - 1, Color_buttonframe);
515 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
516 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
517 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
521 void GuiPainter::rectText(int x, int y, docstring const & str,
522 FontInfo const & font, Color back, Color frame)
528 FontMetrics const & fm = theFontMetrics(font);
529 fm.rectText(str, width, ascent, descent);
531 if (back != Color_none)
532 fillRectangle(x + 1, y - ascent + 1, width - 1,
533 ascent + descent - 1, back);
535 if (frame != Color_none)
536 rectangle(x, y - ascent, width, ascent + descent, frame);
538 text(x + 3, y, str, font);
542 void GuiPainter::buttonText(int x, int y, docstring const & str,
543 FontInfo const & font, bool mouseHover)
549 FontMetrics const & fm = theFontMetrics(font);
550 fm.buttonText(str, width, ascent, descent);
552 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
554 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
555 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
559 int GuiPainter::preeditText(int x, int y, char_type c,
560 FontInfo const & font, preedit_style style)
562 FontInfo temp_font = font;
563 FontMetrics const & fm = theFontMetrics(font);
564 int ascent = fm.maxAscent();
565 int descent = fm.maxDescent();
566 int height = ascent + descent;
567 int width = fm.width(c);
570 case preedit_default:
571 // default unselecting mode.
572 fillRectangle(x, y - height + 1, width, height, Color_background);
573 dashedUnderline(font, x, y - descent + 1, width);
575 case preedit_selecting:
576 // We are in selecting mode: white text on black background.
577 fillRectangle(x, y - height + 1, width, height, Color_black);
578 temp_font.setColor(Color_white);
581 // The character comes with a cursor.
582 fillRectangle(x, y - height + 1, width, height, Color_background);
583 underline(font, x, y - descent + 1, width);
586 text(x, y - descent + 1, c, temp_font);
592 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
594 FontMetrics const & fm = theFontMetrics(f);
596 int const below = max(fm.maxDescent() / 2, 2);
598 line(x, y + below, x + width, y + below, f.realColor());
599 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
603 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
605 FontMetrics const & fm = theFontMetrics(f);
607 int const below = max(fm.maxDescent() / 2, 2);
608 int const height = max((fm.maxDescent() / 4) - 1, 1);
611 line(x, y + below, x + width, y + below, f.realColor());
613 fillRectangle(x, y + below, width, below + height, f.realColor());
617 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
619 FontMetrics const & fm = theFontMetrics(f);
621 int const middle = max((fm.maxHeight() / 4), 1);
622 int const height = middle/3;
625 line(x, y - middle, x + width, y - middle, f.realColor());
627 fillRectangle(x, y - middle, width, height, f.realColor());
631 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
633 FontMetrics const & fm = theFontMetrics(f);
635 int const below = max(fm.maxDescent() / 2, 2);
636 int height = max((fm.maxDescent() / 4) - 1, 1);
641 for (int n = 0; n != height; ++n)
642 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
646 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
648 setQPainterPen(computeColor(col));
650 int const xend = x + width;
652 //FIXME: I am not sure if Antialiasing gives the best effect.
653 //setRenderHint(Antialiasing, true);
656 drawLine(x, y - height, x + step, y + height);
658 drawLine(x, y + height, x + step/2, y + height);
661 //setRenderHint(Antialiasing, false);
664 } // namespace frontend