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,
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 QImage const image = qlimage.image();
274 QRectF const drect = QRectF(x, y, w, h);
275 QRectF const srect = QRectF(0, 0, image.width(), image.height());
276 drawImage(drect, image, srect);
280 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
282 return text(x, y, docstring(1, c), f);
286 int GuiPainter::text(int x, int y, docstring const & s,
287 FontInfo const & f, bool const rtl)
289 //LYXERR0("text: x=" << x << ", s=" << s);
293 /* Caution: The following ucs4 to QString conversions work for symbol fonts
294 only because they are no real conversions but simple casts in reality.
295 When we want to draw a symbol or calculate the metrics we pass the position
296 of the symbol in the font (as given in lib/symbols) as a char_type to the
297 frontend. This is just wrong, because the symbol is no UCS4 character at
298 all. You can think of this number as the code point of the symbol in a
299 custom symbol encoding. It works because this char_type is lateron again
300 interpreted as a position in the font again.
301 The correct solution would be to have extra functions for symbols, but that
302 would require to duplicate a lot of frontend and mathed support code.
304 QString str = toqstr(s);
307 // HACK: QT3 refuses to show single compose characters
308 // Still needed with Qt4?
309 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
313 QFont const & ff = getFont(f);
314 GuiFontMetrics const & fm = getFontMetrics(f);
316 // Here we use the font width cache instead of
317 // textwidth = fontMetrics().width(str);
318 // because the above is awfully expensive on MacOSX
319 int const textwidth = fm.width(s);
321 if (!isDrawingEnabled())
324 textDecoration(f, x, y, textwidth);
326 // Qt4 does not display a glyph whose codepoint is the
327 // same as that of a soft-hyphen (0x00ad), unless it
328 // occurs at a line-break. As a kludge, we force Qt to
329 // render this glyph using a one-column line.
330 // This is needed for some math glyphs.
331 // Should the soft hyphen char be displayed at all?
332 // I don't think so (i.e., Qt is correct as far as
333 // texted is concerned). /spitz
334 if (s.size() == 1 && str[0].unicode() == 0x00ad) {
335 setQPainterPen(computeColor(f.realColor()));
336 QTextLayout adsymbol(str);
337 adsymbol.setFont(ff);
338 adsymbol.beginLayout();
339 QTextLine line = adsymbol.createLine();
340 line.setNumColumns(1);
341 line.setPosition(QPointF(0, -line.ascent()));
342 adsymbol.endLayout();
343 line.draw(this, QPointF(x, y));
347 if (use_pixmap_cache_) {
349 QString key = generateStringSignature(str, f);
351 // Warning: Left bearing is in general negative! Only the case
352 // where left bearing is negative is of interest WRT the
353 // pixmap width and the text x-position.
354 // Only the left bearing of the first character is important
355 // as we always write from left to right, even for
356 // right-to-left languages.
357 int const lb = min(fm.lbearing(s[0]), 0);
358 int const mA = fm.maxAscent();
359 if (QPixmapCache::find(key, pm)) {
360 // Draw the cached pixmap.
361 drawPixmap(x + lb, y - mA, pm);
365 // Only the right bearing of the last character is
366 // important as we always write from left to right,
367 // even for right-to-left languages.
368 int const rb = fm.rbearing(s[s.size()-1]);
369 int const w = textwidth + rb - lb;
370 int const mD = fm.maxDescent();
371 int const h = mA + mD;
372 if (w > 0 && h > 0) {
373 pm = QPixmap(pixelRatio() * w , pixelRatio() * h);
374 #if QT_VERSION > 0x050000
375 pm.setDevicePixelRatio(pixelRatio());
377 pm.fill(Qt::transparent);
378 GuiPainter p(&pm, pixelRatio());
379 p.setQPainterPen(computeColor(f.realColor()));
382 // We need to draw the text as LTR as we use our own bidi code.
383 p.setLayoutDirection(Qt::LeftToRight);
384 p.drawText(-lb, mA, str);
385 QPixmapCache::insert(key, pm);
386 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
387 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
390 // Draw the new cached pixmap.
391 drawPixmap(x + lb, y - mA, pm);
397 // don't use the pixmap cache,
398 // draw directly onto the painting device
399 setQPainterPen(computeColor(f.realColor()));
403 /* In LyX, the character direction is forced by the language.
404 * Therefore, we have to signal that fact to Qt.
406 #ifdef USE_RTL_OVERRIDE
407 /* Use unicode override characters to enforce drawing direction
408 * Source: http://www.iamcal.com/understanding-bidirectional-text/
411 // Right-to-left override: forces to draw text right-to-left
412 str = QChar(0x202E) + str;
414 // Left-to-right override: forces to draw text left-to-right
415 str = QChar(0x202D) + str;
418 /* This is a cleanr solution, but it has two drawbacks
419 * - it seems that it does not work under Mac OS X
420 * - it is not really documented
422 //This is much stronger than setLayoutDirection.
423 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
424 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
425 flag | Qt::TextDontClip,
428 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
429 // << " at " << x << "," << y);
434 int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
436 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
440 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
441 Color other, size_type from, size_type to)
443 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
444 FontInfo fi = f.fontInfo();
445 bool const rtl = f.isVisibleRightToLeft();
448 int const ascent = fm.maxAscent();
449 int const height = fm.maxAscent() + fm.maxDescent();
450 int xmin = fm.pos2x(str, from, rtl);
451 int xmax = fm.pos2x(str, to, rtl);
455 // First the part in other color
456 Color const orig = fi.realColor();
457 fi.setPaintColor(other);
458 setClipRect(QRect(x + xmin, y - ascent, xmax - xmin, height));
459 int const textwidth = text(x, y, str, fi, rtl);
461 // Then the part in normal color
462 // Note that in Qt5, it is not possible to use Qt::UniteClip
463 fi.setPaintColor(orig);
464 setClipRect(QRect(x, y - ascent, xmin, height));
465 text(x, y, str, fi, rtl);
466 setClipRect(QRect(x + xmax, y - ascent, textwidth - xmax, height));
467 text(x, y, str, fi, rtl);
474 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
476 if (f.underbar() == FONT_ON)
477 underline(f, x, y, width);
478 if (f.strikeout() == FONT_ON)
479 strikeoutLine(f, x, y, width);
480 if (f.uuline() == FONT_ON)
481 doubleUnderline(f, x, y, width);
482 if (f.uwave() == FONT_ON)
483 // f.color() doesn't work on some circumstances
484 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
488 static int max(int a, int b) { return a > b ? a : b; }
491 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
494 fillRectangle(x, y, w, h, Color_buttonhoverbg);
496 fillRectangle(x, y, w, h, Color_buttonbg);
497 buttonFrame(x, y, w, h);
501 void GuiPainter::buttonFrame(int x, int y, int w, int h)
503 line(x, y, x, y + h - 1, Color_buttonframe);
504 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
505 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
506 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
510 void GuiPainter::rectText(int x, int y, docstring const & str,
511 FontInfo const & font, Color back, Color frame)
517 FontMetrics const & fm = theFontMetrics(font);
518 fm.rectText(str, width, ascent, descent);
520 if (back != Color_none)
521 fillRectangle(x + 1, y - ascent + 1, width - 1,
522 ascent + descent - 1, back);
524 if (frame != Color_none)
525 rectangle(x, y - ascent, width, ascent + descent, frame);
527 text(x + 3, y, str, font);
531 void GuiPainter::buttonText(int x, int y, docstring const & str,
532 FontInfo const & font, bool mouseHover)
538 FontMetrics const & fm = theFontMetrics(font);
539 fm.buttonText(str, width, ascent, descent);
541 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
543 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
544 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
548 int GuiPainter::preeditText(int x, int y, char_type c,
549 FontInfo const & font, preedit_style style)
551 FontInfo temp_font = font;
552 FontMetrics const & fm = theFontMetrics(font);
553 int ascent = fm.maxAscent();
554 int descent = fm.maxDescent();
555 int height = ascent + descent;
556 int width = fm.width(c);
559 case preedit_default:
560 // default unselecting mode.
561 fillRectangle(x, y - height + 1, width, height, Color_background);
562 dashedUnderline(font, x, y - descent + 1, width);
564 case preedit_selecting:
565 // We are in selecting mode: white text on black background.
566 fillRectangle(x, y - height + 1, width, height, Color_black);
567 temp_font.setColor(Color_white);
570 // The character comes with a cursor.
571 fillRectangle(x, y - height + 1, width, height, Color_background);
572 underline(font, x, y - descent + 1, width);
575 text(x, y - descent + 1, c, temp_font);
581 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
583 FontMetrics const & fm = theFontMetrics(f);
585 int const below = max(fm.maxDescent() / 2, 2);
587 line(x, y + below, x + width, y + below, f.realColor());
588 line(x, y + below - 2, x + width, y + below - 2, f.realColor());
592 void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
594 FontMetrics const & fm = theFontMetrics(f);
596 int const below = max(fm.maxDescent() / 2, 2);
597 int const height = max((fm.maxDescent() / 4) - 1, 1);
600 line(x, y + below, x + width, y + below, f.realColor());
602 fillRectangle(x, y + below, width, below + height, f.realColor());
606 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
608 FontMetrics const & fm = theFontMetrics(f);
610 int const middle = max((fm.maxHeight() / 4), 1);
611 int const height = middle/3;
614 line(x, y - middle, x + width, y - middle, f.realColor());
616 fillRectangle(x, y - middle, width, height, f.realColor());
620 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
622 FontMetrics const & fm = theFontMetrics(f);
624 int const below = max(fm.maxDescent() / 2, 2);
625 int height = max((fm.maxDescent() / 4) - 1, 1);
630 for (int n = 0; n != height; ++n)
631 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
635 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
637 setQPainterPen(computeColor(col));
639 int const xend = x + width;
641 //FIXME: I am not sure if Antialiasing gives the best effect.
642 //setRenderHint(Antialiasing, true);
645 drawLine(x, y - height, x + step, y + height);
647 drawLine(x, y + height, x + step/2, y + height);
650 //setRenderHint(Antialiasing, false);
653 } // namespace frontend