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)
56 : QPainter(device), Painter(),
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,
202 if (!isDrawingEnabled())
205 // double the size if needed
207 static QVector<QPoint> points(32);
208 if (np > points.size())
209 points.resize(2 * np);
211 bool antialias = false;
212 for (int i = 0; i < np; ++i) {
213 points[i].setX(xp[i]);
214 points[i].setY(yp[i]);
216 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
218 setQPainterPen(computeColor(col), ls, lw);
219 bool const text_is_antialiased = renderHints() & TextAntialiasing;
220 setRenderHint(Antialiasing, antialias && text_is_antialiased);
221 drawPolyline(points.data(), np);
222 setRenderHint(Antialiasing, false);
226 void GuiPainter::rectangle(int x, int y, int w, int h,
231 if (!isDrawingEnabled())
234 setQPainterPen(computeColor(col), ls, lw);
235 drawRect(x, y, w, h);
239 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
241 if (!isDrawingEnabled())
244 fillRect(x, y, w, h, guiApp->colorCache().get(col));
248 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
249 int a1, int a2, Color col)
251 if (!isDrawingEnabled())
254 // LyX usings 1/64ths degree, Qt usings 1/16th
255 setQPainterPen(computeColor(col));
256 bool const do_antialiasing = renderHints() & TextAntialiasing;
257 setRenderHint(Antialiasing, do_antialiasing);
258 drawArc(x, y, w, h, a1 / 4, a2 / 4);
259 setRenderHint(Antialiasing, false);
263 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
265 graphics::GuiImage const & qlimage =
266 static_cast<graphics::GuiImage const &>(i);
268 fillRectangle(x, y, w, h, Color_graphicsbg);
270 if (!isDrawingEnabled())
273 drawImage(x, y, qlimage.image(), 0, 0, w, h);
277 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
279 return text(x, y, docstring(1, c), f);
283 int GuiPainter::text(int x, int y, docstring const & s,
284 FontInfo const & f, bool const rtl)
286 //LYXERR0("text: x=" << x << ", s=" << s);
290 /* Caution: The following ucs4 to QString conversions work for symbol fonts
291 only because they are no real conversions but simple casts in reality.
292 When we want to draw a symbol or calculate the metrics we pass the position
293 of the symbol in the font (as given in lib/symbols) as a char_type to the
294 frontend. This is just wrong, because the symbol is no UCS4 character at
295 all. You can think of this number as the code point of the symbol in a
296 custom symbol encoding. It works because this char_type is lateron again
297 interpreted as a position in the font again.
298 The correct solution would be to have extra functions for symbols, but that
299 would require to duplicate a lot of frontend and mathed support code.
301 QString str = toqstr(s);
304 // HACK: QT3 refuses to show single compose characters
305 // Still needed with Qt4?
306 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
310 QFont const & ff = getFont(f);
311 GuiFontMetrics const & fm = getFontMetrics(f);
313 // Here we use the font width cache instead of
314 // textwidth = fontMetrics().width(str);
315 // because the above is awfully expensive on MacOSX
316 int const textwidth = fm.width(s);
318 if (!isDrawingEnabled())
321 textDecoration(f, x, y, textwidth);
323 // Qt4 does not display a glyph whose codepoint is the
324 // same as that of a soft-hyphen (0x00ad), unless it
325 // occurs at a line-break. As a kludge, we force Qt to
326 // render this glyph using a one-column line.
327 // This is needed for some math glyphs.
328 // Should the soft hyphen char be displayed at all?
329 // I don't think so (i.e., Qt is correct as far as
330 // texted is concerned). /spitz
331 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
332 setQPainterPen(computeColor(f.realColor()));
333 QTextLayout adsymbol(str);
334 adsymbol.setFont(ff);
335 adsymbol.beginLayout();
336 QTextLine line = adsymbol.createLine();
337 line.setNumColumns(1);
338 line.setPosition(QPointF(0, -line.ascent()));
339 adsymbol.endLayout();
340 line.draw(this, QPointF(x, y));
344 if (use_pixmap_cache_) {
346 QString key = generateStringSignature(str, f);
348 // Warning: Left bearing is in general negative! Only the case
349 // where left bearing is negative is of interest WRT the
350 // pixmap width and the text x-position.
351 // Only the left bearing of the first character is important
352 // as we always write from left to right, even for
353 // right-to-left languages.
354 int const lb = min(fm.lbearing(s[0]), 0);
355 int const mA = fm.maxAscent();
356 if (QPixmapCache::find(key, pm)) {
357 // Draw the cached pixmap.
358 drawPixmap(x + lb, y - mA, pm);
362 // Only the right bearing of the last character is
363 // important as we always write from left to right,
364 // even for right-to-left languages.
365 int const rb = fm.rbearing(s[s.size()-1]);
366 int const w = textwidth + rb - lb;
367 int const mD = fm.maxDescent();
368 int const h = mA + mD;
369 if (w > 0 && h > 0) {
371 pm.fill(Qt::transparent);
373 p.setQPainterPen(computeColor(f.realColor()));
376 // We need to draw the text as LTR as we use our own bidi code.
377 p.setLayoutDirection(Qt::LeftToRight);
378 p.drawText(-lb, mA, str);
379 QPixmapCache::insert(key, pm);
380 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
381 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
384 // Draw the new cached pixmap.
385 drawPixmap(x + lb, y - mA, pm);
391 // don't use the pixmap cache,
392 // draw directly onto the painting device
393 setQPainterPen(computeColor(f.realColor()));
397 /* In LyX, the character direction is forced by the language.
398 * Therefore, we have to signal that fact to Qt.
400 #ifdef USE_RTL_OVERRIDE
401 /* Use unicode override characters to enforce drawing direction
402 * Source: http://www.iamcal.com/understanding-bidirectional-text/
405 // Right-to-left override: forces to draw text right-to-left
406 str = QChar(0x202E) + str;
408 // Left-to-right override: forces to draw text left-to-right
409 str = QChar(0x202D) + str;
412 /* This is a cleanr solution, but it has two drawbacks
413 * - it seems that it does not work under Mac OS X
414 * - it is not really documented
416 //This is much stronger than setLayoutDirection.
417 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
418 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
419 flag | Qt::TextDontClip,
422 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
423 // << " at " << x << "," << y);
428 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
430 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
434 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
435 Color other, size_type from, size_type to)
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);
445 int xmax = fm.pos2x(str, to, rtl);
449 // First the part in other color
450 Color const orig = fi.realColor();
451 fi.setPaintColor(other);
452 setClipRect(QRect(x + xmin, y - ascent, xmax - xmin, height));
453 int const textwidth = text(x, y, str, fi, rtl);
455 // Then the part in normal color
456 // Note that in Qt5, it is not possible to use Qt::UniteClip
457 fi.setPaintColor(orig);
458 setClipRect(QRect(x, y - ascent, xmin, height));
459 text(x, y, str, fi, rtl);
460 setClipRect(QRect(x + xmax, y - ascent, textwidth - xmax, height));
461 text(x, y, str, fi, rtl);
468 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
470 if (f.underbar() == FONT_ON)
471 underline(f, x, y, width);
472 if (f.strikeout() == FONT_ON)
473 strikeoutLine(f, x, y, width);
474 if (f.uuline() == FONT_ON)
475 doubleUnderline(f, x, y, width);
476 if (f.uwave() == FONT_ON)
477 // f.color() doesn't work on some circumstances
478 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
482 static int max(int a, int b) { return a > b ? a : b; }
485 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
488 fillRectangle(x, y, w, h, Color_buttonhoverbg);
490 fillRectangle(x, y, w, h, Color_buttonbg);
491 buttonFrame(x, y, w, h);
495 void GuiPainter::buttonFrame(int x, int y, int w, int h)
497 line(x, y, x, y + h - 1, Color_buttonframe);
498 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
499 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
500 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
504 void GuiPainter::rectText(int x, int y, docstring const & str,
505 FontInfo const & font, Color back, Color frame)
511 FontMetrics const & fm = theFontMetrics(font);
512 fm.rectText(str, width, ascent, descent);
514 if (back != Color_none)
515 fillRectangle(x + 1, y - ascent + 1, width - 1,
516 ascent + descent - 1, back);
518 if (frame != Color_none)
519 rectangle(x, y - ascent, width, ascent + descent, frame);
521 text(x + 3, y, str, font);
525 void GuiPainter::buttonText(int x, int y, docstring const & str,
526 FontInfo const & font, bool mouseHover)
532 FontMetrics const & fm = theFontMetrics(font);
533 fm.buttonText(str, width, ascent, descent);
535 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
537 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
538 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
542 int GuiPainter::preeditText(int x, int y, char_type c,
543 FontInfo const & font, preedit_style style)
545 FontInfo temp_font = font;
546 FontMetrics const & fm = theFontMetrics(font);
547 int ascent = fm.maxAscent();
548 int descent = fm.maxDescent();
549 int height = ascent + descent;
550 int width = fm.width(c);
553 case preedit_default:
554 // default unselecting mode.
555 fillRectangle(x, y - height + 1, width, height, Color_background);
556 dashedUnderline(font, x, y - descent + 1, width);
558 case preedit_selecting:
559 // We are in selecting mode: white text on black background.
560 fillRectangle(x, y - height + 1, width, height, Color_black);
561 temp_font.setColor(Color_white);
564 // The character comes with a cursor.
565 fillRectangle(x, y - height + 1, width, height, Color_background);
566 underline(font, x, y - descent + 1, width);
569 text(x, y - descent + 1, c, temp_font);
575 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
577 FontMetrics const & fm = theFontMetrics(f);
579 int const below = max(fm.maxDescent() / 2, 2);
581 line(x, y + below, x + width, y + below, f.realColor());
582 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
586 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
588 FontMetrics const & fm = theFontMetrics(f);
590 int const below = max(fm.maxDescent() / 2, 2);
591 int const height = max((fm.maxDescent() / 4) - 1, 1);
594 line(x, y + below, x + width, y + below, f.realColor());
596 fillRectangle(x, y + below, width, below + height, f.realColor());
600 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
602 FontMetrics const & fm = theFontMetrics(f);
604 int const middle = max((fm.maxHeight() / 4), 1);
605 int const height = middle/3;
608 line(x, y - middle, x + width, y - middle, f.realColor());
610 fillRectangle(x, y - middle, width, height, f.realColor());
614 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
616 FontMetrics const & fm = theFontMetrics(f);
618 int const below = max(fm.maxDescent() / 2, 2);
619 int height = max((fm.maxDescent() / 4) - 1, 1);
624 for (int n = 0; n != height; ++n)
625 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
629 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
631 setQPainterPen(computeColor(col));
633 int const xend = x + width;
635 //FIXME: I am not sure if Antialiasing gives the best effect.
636 //setRenderHint(Antialiasing, true);
639 drawLine(x, y - height, x + step, y + height);
641 drawLine(x, y + height, x + step/2, y + height);
644 //setRenderHint(Antialiasing, false);
647 } // namespace frontend