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 // 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(static_cast<int>(pixelRatio() * w),
384 static_cast<int>(pixelRatio() * h));
385 #if QT_VERSION >= 0x050000
386 pm.setDevicePixelRatio(pixelRatio());
388 pm.fill(Qt::transparent);
389 GuiPainter p(&pm, pixelRatio());
390 p.setQPainterPen(computeColor(f.realColor()));
393 // We need to draw the text as LTR as we use our own bidi code.
394 p.setLayoutDirection(Qt::LeftToRight);
395 p.drawText(-lb, mA, str);
396 QPixmapCache::insert(key, pm);
397 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
398 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
401 // Draw the new cached pixmap.
402 drawPixmap(x + lb, y - mA, pm);
408 // don't use the pixmap cache,
409 // draw directly onto the painting device
410 setQPainterPen(computeColor(f.realColor()));
414 /* In LyX, the character direction is forced by the language.
415 * Therefore, we have to signal that fact to Qt.
417 #ifdef USE_RTL_OVERRIDE
418 /* Use unicode override characters to enforce drawing direction
419 * Source: http://www.iamcal.com/understanding-bidirectional-text/
422 // Right-to-left override: forces to draw text right-to-left
423 str = QChar(0x202E) + str;
425 // Left-to-right override: forces to draw text left-to-right
426 str = QChar(0x202D) + str;
429 /* This is a cleanr solution, but it has two drawbacks
430 * - it seems that it does not work under Mac OS X
431 * - it is not really documented
433 //This is much stronger than setLayoutDirection.
434 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
435 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
436 flag | Qt::TextDontClip,
439 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
440 // << " at " << x << "," << y);
445 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
447 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
451 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
452 Color other, size_type from, size_type to)
454 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
455 FontInfo fi = f.fontInfo();
456 bool const rtl = f.isVisibleRightToLeft();
459 int const ascent = fm.maxAscent();
460 int const height = fm.maxAscent() + fm.maxDescent();
461 int xmin = fm.pos2x(str, from, rtl);
462 int xmax = fm.pos2x(str, to, rtl);
466 // First the part in other color
467 Color const orig = fi.realColor();
468 fi.setPaintColor(other);
469 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
471 int const textwidth = text(x, y, str, fi, rtl);
473 // Then the part in normal color
474 // Note that in Qt5, it is not possible to use Qt::UniteClip,
475 // therefore QRegion is used.
476 fi.setPaintColor(orig);
477 QRegion region(viewport());
478 setClipRegion(region - clip);
479 text(x, y, str, fi, rtl);
486 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
488 if (f.underbar() == FONT_ON)
489 underline(f, x, y, width);
490 if (f.strikeout() == FONT_ON)
491 strikeoutLine(f, x, y, width);
492 if (f.uuline() == FONT_ON)
493 doubleUnderline(f, x, y, width);
494 if (f.uwave() == FONT_ON)
495 // f.color() doesn't work on some circumstances
496 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
500 static int max(int a, int b) { return a > b ? a : b; }
503 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
506 fillRectangle(x, y, w, h, Color_buttonhoverbg);
508 fillRectangle(x, y, w, h, Color_buttonbg);
509 buttonFrame(x, y, w, h);
513 void GuiPainter::buttonFrame(int x, int y, int w, int h)
515 line(x, y, x, y + h - 1, Color_buttonframe);
516 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
517 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
518 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
522 void GuiPainter::rectText(int x, int y, docstring const & str,
523 FontInfo const & font, Color back, Color frame)
529 FontMetrics const & fm = theFontMetrics(font);
530 fm.rectText(str, width, ascent, descent);
532 if (back != Color_none)
533 fillRectangle(x + 1, y - ascent + 1, width - 1,
534 ascent + descent - 1, back);
536 if (frame != Color_none)
537 rectangle(x, y - ascent, width, ascent + descent, frame);
539 text(x + 3, y, str, font);
543 void GuiPainter::buttonText(int x, int y, docstring const & str,
544 FontInfo const & font, bool mouseHover)
550 FontMetrics const & fm = theFontMetrics(font);
551 fm.buttonText(str, width, ascent, descent);
553 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
555 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
556 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
560 int GuiPainter::preeditText(int x, int y, char_type c,
561 FontInfo const & font, preedit_style style)
563 FontInfo temp_font = font;
564 FontMetrics const & fm = theFontMetrics(font);
565 int ascent = fm.maxAscent();
566 int descent = fm.maxDescent();
567 int height = ascent + descent;
568 int width = fm.width(c);
571 case preedit_default:
572 // default unselecting mode.
573 fillRectangle(x, y - height + 1, width, height, Color_background);
574 dashedUnderline(font, x, y - descent + 1, width);
576 case preedit_selecting:
577 // We are in selecting mode: white text on black background.
578 fillRectangle(x, y - height + 1, width, height, Color_black);
579 temp_font.setColor(Color_white);
582 // The character comes with a cursor.
583 fillRectangle(x, y - height + 1, width, height, Color_background);
584 underline(font, x, y - descent + 1, width);
587 text(x, y - descent + 1, c, temp_font);
593 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
595 FontMetrics const & fm = theFontMetrics(f);
597 int const below = max(fm.maxDescent() / 2, 2);
599 line(x, y + below, x + width, y + below, f.realColor());
600 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
604 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
606 FontMetrics const & fm = theFontMetrics(f);
608 int const below = max(fm.maxDescent() / 2, 2);
609 int const height = max((fm.maxDescent() / 4) - 1, 1);
612 line(x, y + below, x + width, y + below, f.realColor());
614 fillRectangle(x, y + below, width, below + height, f.realColor());
618 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
620 FontMetrics const & fm = theFontMetrics(f);
622 int const middle = max((fm.maxHeight() / 4), 1);
623 int const height = middle/3;
626 line(x, y - middle, x + width, y - middle, f.realColor());
628 fillRectangle(x, y - middle, width, height, f.realColor());
632 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
634 FontMetrics const & fm = theFontMetrics(f);
636 int const below = max(fm.maxDescent() / 2, 2);
637 int height = max((fm.maxDescent() / 4) - 1, 1);
642 for (int n = 0; n != height; ++n)
643 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
647 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
649 setQPainterPen(computeColor(col));
651 int const xend = x + width;
653 //FIXME: I am not sure if Antialiasing gives the best effect.
654 //setRenderHint(Antialiasing, true);
657 drawLine(x, y - height, x + step, y + height);
659 drawLine(x, y + height, x + step/2, y + height);
662 //setRenderHint(Antialiasing, false);
665 } // namespace frontend