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 setClipRect(QRect(x + xmin, y - ascent, xmax - xmin, height));
469 int const textwidth = text(x, y, str, fi, rtl);
471 // Then the part in normal color
472 // Note that in Qt5, it is not possible to use Qt::UniteClip
473 fi.setPaintColor(orig);
474 setClipRect(QRect(x, y - ascent, xmin, height));
475 text(x, y, str, fi, rtl);
476 setClipRect(QRect(x + xmax, y - ascent, textwidth - xmax, height));
477 text(x, y, str, fi, rtl);
484 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
486 if (f.underbar() == FONT_ON)
487 underline(f, x, y, width);
488 if (f.strikeout() == FONT_ON)
489 strikeoutLine(f, x, y, width);
490 if (f.uuline() == FONT_ON)
491 doubleUnderline(f, x, y, width);
492 if (f.uwave() == FONT_ON)
493 // f.color() doesn't work on some circumstances
494 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
498 static int max(int a, int b) { return a > b ? a : b; }
501 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
504 fillRectangle(x, y, w, h, Color_buttonhoverbg);
506 fillRectangle(x, y, w, h, Color_buttonbg);
507 buttonFrame(x, y, w, h);
511 void GuiPainter::buttonFrame(int x, int y, int w, int h)
513 line(x, y, x, y + h - 1, Color_buttonframe);
514 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
515 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
516 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
520 void GuiPainter::rectText(int x, int y, docstring const & str,
521 FontInfo const & font, Color back, Color frame)
527 FontMetrics const & fm = theFontMetrics(font);
528 fm.rectText(str, width, ascent, descent);
530 if (back != Color_none)
531 fillRectangle(x + 1, y - ascent + 1, width - 1,
532 ascent + descent - 1, back);
534 if (frame != Color_none)
535 rectangle(x, y - ascent, width, ascent + descent, frame);
537 text(x + 3, y, str, font);
541 void GuiPainter::buttonText(int x, int y, docstring const & str,
542 FontInfo const & font, bool mouseHover)
548 FontMetrics const & fm = theFontMetrics(font);
549 fm.buttonText(str, width, ascent, descent);
551 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
553 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
554 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
558 int GuiPainter::preeditText(int x, int y, char_type c,
559 FontInfo const & font, preedit_style style)
561 FontInfo temp_font = font;
562 FontMetrics const & fm = theFontMetrics(font);
563 int ascent = fm.maxAscent();
564 int descent = fm.maxDescent();
565 int height = ascent + descent;
566 int width = fm.width(c);
569 case preedit_default:
570 // default unselecting mode.
571 fillRectangle(x, y - height + 1, width, height, Color_background);
572 dashedUnderline(font, x, y - descent + 1, width);
574 case preedit_selecting:
575 // We are in selecting mode: white text on black background.
576 fillRectangle(x, y - height + 1, width, height, Color_black);
577 temp_font.setColor(Color_white);
580 // The character comes with a cursor.
581 fillRectangle(x, y - height + 1, width, height, Color_background);
582 underline(font, x, y - descent + 1, width);
585 text(x, y - descent + 1, c, temp_font);
591 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
593 FontMetrics const & fm = theFontMetrics(f);
595 int const below = max(fm.maxDescent() / 2, 2);
597 line(x, y + below, x + width, y + below, f.realColor());
598 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
602 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
604 FontMetrics const & fm = theFontMetrics(f);
606 int const below = max(fm.maxDescent() / 2, 2);
607 int const height = max((fm.maxDescent() / 4) - 1, 1);
610 line(x, y + below, x + width, y + below, f.realColor());
612 fillRectangle(x, y + below, width, below + height, f.realColor());
616 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
618 FontMetrics const & fm = theFontMetrics(f);
620 int const middle = max((fm.maxHeight() / 4), 1);
621 int const height = middle/3;
624 line(x, y - middle, x + width, y - middle, f.realColor());
626 fillRectangle(x, y - middle, width, height, f.realColor());
630 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
632 FontMetrics const & fm = theFontMetrics(f);
634 int const below = max(fm.maxDescent() / 2, 2);
635 int height = max((fm.maxDescent() / 4) - 1, 1);
640 for (int n = 0; n != height; ++n)
641 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
645 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
647 setQPainterPen(computeColor(col));
649 int const xend = x + width;
651 //FIXME: I am not sure if Antialiasing gives the best effect.
652 //setRenderHint(Antialiasing, true);
655 drawLine(x, y - height, x + step, y + height);
657 drawLine(x, y + height, x + step/2, y + height);
660 //setRenderHint(Antialiasing, false);
663 } // namespace frontend