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.
14 #include "GuiPainter.h"
16 #include "ColorCache.h"
17 #include "GuiApplication.h"
18 #include "GuiFontLoader.h"
19 #include "GuiFontMetrics.h"
21 #include "qt_helpers.h"
27 #include "insets/Inset.h"
29 #include "support/lassert.h"
30 #include "support/debug.h"
32 #include <QPixmapCache>
33 #include <QTextLayout>
35 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
36 // drawing text. This is especially useful for older PPC/Mac systems.
37 #if defined(Q_WS_X11) || defined(QPA_XCB)
38 #define USE_PIXMAP_CACHE 0
40 #define USE_PIXMAP_CACHE 1
44 using namespace lyx::support;
49 const int Painter::thin_line = 0;
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52 : QPainter(device), Painter(pixel_ratio),
53 use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
55 // new QPainter has default QPen:
56 current_color_ = guiApp->colorCache().get(Color_black);
57 current_ls_ = line_solid;
58 current_lw_ = thin_line;
62 GuiPainter::~GuiPainter()
65 //lyxerr << "GuiPainter::end()" << endl;
69 void GuiPainter::setQPainterPen(QColor const & col,
70 Painter::line_style ls, int lw)
72 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
79 QPen pen = QPainter::pen();
83 case line_solid: pen.setStyle(Qt::SolidLine); break;
84 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
93 QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
96 sig.append(QChar(static_cast<short>(f.family())));
97 sig.append(QChar(static_cast<short>(f.series())));
98 sig.append(QChar(static_cast<short>(f.realShape())));
99 sig.append(QChar(static_cast<short>(f.size())));
100 Color const & color = f.realColor();
101 sig.append(QChar(static_cast<short>(color.baseColor)));
102 sig.append(QChar(static_cast<short>(color.mergeColor)));
103 if (!monochrome_min_.empty()) {
104 QColor const & min = monochrome_min_.top();
105 QColor const & max = monochrome_max_.top();
106 sig.append(QChar(static_cast<short>(min.red())));
107 sig.append(QChar(static_cast<short>(min.green())));
108 sig.append(QChar(static_cast<short>(min.blue())));
109 sig.append(QChar(static_cast<short>(max.red())));
110 sig.append(QChar(static_cast<short>(max.green())));
111 sig.append(QChar(static_cast<short>(max.blue())));
117 QColor GuiPainter::computeColor(Color col)
119 return filterColor(guiApp->colorCache().get(col));
123 QColor GuiPainter::filterColor(QColor const & col)
125 if (monochrome_min_.empty())
128 // map into [min,max] interval
129 QColor const & min = monochrome_min_.top();
130 QColor const & max = monochrome_max_.top();
132 qreal v = col.valueF();
133 v *= v; // make it a bit steeper (i.e. darker)
135 qreal minr, ming, minb;
136 qreal maxr, maxg, maxb;
137 min.getRgbF(&minr, &ming, &minb);
138 max.getRgbF(&maxr, &maxg, &maxb);
142 v * (minr - maxr) + maxr,
143 v * (ming - maxg) + maxg,
144 v * (minb - maxb) + maxb);
149 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
151 QColor qmin = filterColor(guiApp->colorCache().get(min));
152 QColor qmax = filterColor(guiApp->colorCache().get(max));
153 monochrome_min_.push(qmin);
154 monochrome_max_.push(qmax);
158 void GuiPainter::leaveMonochromeMode()
160 LASSERT(!monochrome_min_.empty(), return);
161 monochrome_min_.pop();
162 monochrome_max_.pop();
166 void GuiPainter::point(int x, int y, Color col)
168 if (!isDrawingEnabled())
171 setQPainterPen(computeColor(col));
176 void GuiPainter::line(int x1, int y1, int x2, int y2,
181 if (!isDrawingEnabled())
184 setQPainterPen(computeColor(col), ls, lw);
185 bool const do_antialiasing = renderHints() & TextAntialiasing
186 && x1 != x2 && y1 != y2;
187 setRenderHint(Antialiasing, do_antialiasing);
188 drawLine(x1, y1, x2, y2);
189 setRenderHint(Antialiasing, false);
193 void GuiPainter::lines(int const * xp, int const * yp, int np,
199 if (!isDrawingEnabled())
202 // double the size if needed
204 static QVector<QPoint> points(32);
205 if (np > points.size())
206 points.resize(2 * np);
208 bool antialias = false;
209 for (int i = 0; i < np; ++i) {
210 points[i].setX(xp[i]);
211 points[i].setY(yp[i]);
213 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
215 QColor const color = computeColor(col);
216 setQPainterPen(color, ls, lw);
217 bool const text_is_antialiased = renderHints() & TextAntialiasing;
218 setRenderHint(Antialiasing, antialias && text_is_antialiased);
219 if (fs == fill_none) {
220 drawPolyline(points.data(), np);
222 QBrush const oldbrush = brush();
223 setBrush(QBrush(color));
224 drawPolygon(points.data(), np, fs == fill_oddeven ?
225 Qt::OddEvenFill : Qt::WindingFill);
228 setRenderHint(Antialiasing, false);
232 void GuiPainter::rectangle(int x, int y, int w, int h,
237 if (!isDrawingEnabled())
240 setQPainterPen(computeColor(col), ls, lw);
241 drawRect(x, y, w, h);
245 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
247 if (!isDrawingEnabled())
250 fillRect(x, y, w, h, guiApp->colorCache().get(col));
254 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
255 int a1, int a2, Color col)
257 if (!isDrawingEnabled())
260 // LyX usings 1/64ths degree, Qt usings 1/16th
261 setQPainterPen(computeColor(col));
262 bool const do_antialiasing = renderHints() & TextAntialiasing;
263 setRenderHint(Antialiasing, do_antialiasing);
264 drawArc(x, y, w, h, a1 / 4, a2 / 4);
265 setRenderHint(Antialiasing, false);
269 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
271 graphics::GuiImage const & qlimage =
272 static_cast<graphics::GuiImage const &>(i);
274 fillRectangle(x, y, w, h, Color_graphicsbg);
276 if (!isDrawingEnabled())
279 QImage const image = qlimage.image();
280 QRectF const drect = QRectF(x, y, w, h);
281 QRectF const srect = QRectF(0, 0, image.width(), image.height());
282 drawImage(drect, image, srect);
286 int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
288 return text(x, y, docstring(1, c), f);
292 int GuiPainter::text(int x, int y, docstring const & s,
293 FontInfo const & f, bool const rtl,
294 double const wordspacing)
296 //LYXERR0("text: x=" << x << ", s=" << s);
300 /* Caution: The following ucs4 to QString conversions work for symbol fonts
301 only because they are no real conversions but simple casts in reality.
302 When we want to draw a symbol or calculate the metrics we pass the position
303 of the symbol in the font (as given in lib/symbols) as a char_type to the
304 frontend. This is just wrong, because the symbol is no UCS4 character at
305 all. You can think of this number as the code point of the symbol in a
306 custom symbol encoding. It works because this char_type is lateron again
307 interpreted as a position in the font again.
308 The correct solution would be to have extra functions for symbols, but that
309 would require to duplicate a lot of frontend and mathed support code.
311 QString str = toqstr(s);
314 // HACK: QT3 refuses to show single compose characters
315 // Still needed with Qt4?
316 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
320 QFont ff = getFont(f);
321 ff.setWordSpacing(wordspacing);
322 GuiFontMetrics const & fm = getFontMetrics(f);
324 // Here we use the font width cache instead of
325 // textwidth = fontMetrics().width(str);
326 // because the above is awfully expensive on MacOSX
327 int const textwidth = fm.width(s);
329 if (!isDrawingEnabled())
332 textDecoration(f, x, y, textwidth);
334 if (use_pixmap_cache_) {
336 QString key = generateStringSignature(str, f);
338 // Warning: Left bearing is in general negative! Only the case
339 // where left bearing is negative is of interest WRT the
340 // pixmap width and the text x-position.
341 // Only the left bearing of the first character is important
342 // as we always write from left to right, even for
343 // right-to-left languages.
344 int const lb = min(fm.lbearing(s[0]), 0);
345 int const mA = fm.maxAscent();
346 if (QPixmapCache::find(key, pm)) {
347 // Draw the cached pixmap.
348 drawPixmap(x + lb, y - mA, pm);
352 // Only the right bearing of the last character is
353 // important as we always write from left to right,
354 // even for right-to-left languages.
355 int const rb = fm.rbearing(s[s.size()-1]);
356 int const w = textwidth + rb - lb;
357 int const mD = fm.maxDescent();
358 int const h = mA + mD;
359 if (w > 0 && h > 0) {
360 pm = QPixmap(static_cast<int>(pixelRatio() * w),
361 static_cast<int>(pixelRatio() * h));
362 #if QT_VERSION >= 0x050000
363 pm.setDevicePixelRatio(pixelRatio());
365 pm.fill(Qt::transparent);
366 GuiPainter p(&pm, pixelRatio());
367 p.setQPainterPen(computeColor(f.realColor()));
370 // We need to draw the text as LTR as we use our own bidi code.
371 p.setLayoutDirection(Qt::LeftToRight);
372 p.drawText(-lb, mA, str);
373 QPixmapCache::insert(key, pm);
374 //LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
375 // << " w=" << w << " lb=" << lb << " tw=" << textwidth
378 // Draw the new cached pixmap.
379 drawPixmap(x + lb, y - mA, pm);
384 // don't use the pixmap cache,
385 // draw directly onto the painting device
386 setQPainterPen(computeColor(f.realColor()));
390 /* In LyX, the character direction is forced by the language.
391 * Therefore, we have to signal that fact to Qt.
394 /* Use unicode override characters to enforce drawing direction
395 * Source: http://www.iamcal.com/understanding-bidirectional-text/
398 // Right-to-left override: forces to draw text right-to-left
399 str = QChar(0x202E) + str;
401 // Left-to-right override: forces to draw text left-to-right
402 str = QChar(0x202D) + str;
405 /* This looks like a cleaner solution, but it has drawbacks
406 * - does not work reliably (Mac OS X, ...)
407 * - it is not really documented
408 * Keep it here for now, in case it can be helpful
410 //This is much stronger than setLayoutDirection.
411 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
412 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
413 flag | Qt::TextDontClip,
416 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
417 // << " at " << x << "," << y);
422 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
423 double const wordspacing)
425 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
429 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
430 Color other, size_type const from, size_type const to,
431 double const wordspacing)
433 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
434 FontInfo fi = f.fontInfo();
435 bool const rtl = f.isVisibleRightToLeft();
438 int const ascent = fm.maxAscent();
439 int const height = fm.maxAscent() + fm.maxDescent();
440 int xmin = fm.pos2x(str, from, rtl, wordspacing);
441 int xmax = fm.pos2x(str, to, rtl, wordspacing);
445 // First the part in other color
446 Color const orig = fi.realColor();
447 fi.setPaintColor(other);
448 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
450 int const textwidth = text(x, y, str, fi, rtl, wordspacing);
452 // Then the part in normal color
453 // Note that in Qt5, it is not possible to use Qt::UniteClip,
454 // therefore QRegion is used.
455 fi.setPaintColor(orig);
456 QRegion region(viewport());
457 setClipRegion(region - clip);
458 text(x, y, str, fi, rtl, wordspacing);
465 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
467 if (f.underbar() == FONT_ON)
468 underline(f, x, y, width);
469 if (f.strikeout() == FONT_ON)
470 strikeoutLine(f, x, y, width);
471 if (f.uuline() == FONT_ON)
472 doubleUnderline(f, x, y, width);
473 if (f.uwave() == FONT_ON)
474 // f.color() doesn't work on some circumstances
475 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
479 static int max(int a, int b) { return a > b ? a : b; }
482 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
485 fillRectangle(x, y, w, h, Color_buttonhoverbg);
487 fillRectangle(x, y, w, h, Color_buttonbg);
488 buttonFrame(x, y, w, h);
492 void GuiPainter::buttonFrame(int x, int y, int w, int h)
494 line(x, y, x, y + h - 1, Color_buttonframe);
495 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
496 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
497 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
501 void GuiPainter::rectText(int x, int y, docstring const & str,
502 FontInfo const & font, Color back, Color frame)
508 FontMetrics const & fm = theFontMetrics(font);
509 fm.rectText(str, width, ascent, descent);
511 if (back != Color_none)
512 fillRectangle(x + 1, y - ascent + 1, width - 1,
513 ascent + descent - 1, back);
515 if (frame != Color_none)
516 rectangle(x, y - ascent, width, ascent + descent, frame);
518 text(x + 3, y, str, font);
522 void GuiPainter::buttonText(int x, int y, docstring const & str,
523 FontInfo const & font, bool mouseHover)
529 FontMetrics const & fm = theFontMetrics(font);
530 fm.buttonText(str, width, ascent, descent);
532 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
534 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
535 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
539 int GuiPainter::preeditText(int x, int y, char_type c,
540 FontInfo const & font, preedit_style style)
542 FontInfo temp_font = font;
543 FontMetrics const & fm = theFontMetrics(font);
544 int ascent = fm.maxAscent();
545 int descent = fm.maxDescent();
546 int height = ascent + descent;
547 int width = fm.width(c);
550 case preedit_default:
551 // default unselecting mode.
552 fillRectangle(x, y - height + 1, width, height, Color_background);
553 dashedUnderline(font, x, y - descent + 1, width);
555 case preedit_selecting:
556 // We are in selecting mode: white text on black background.
557 fillRectangle(x, y - height + 1, width, height, Color_black);
558 temp_font.setColor(Color_white);
561 // The character comes with a cursor.
562 fillRectangle(x, y - height + 1, width, height, Color_background);
563 underline(font, x, y - descent + 1, width);
566 text(x, y - descent + 1, c, temp_font);
572 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
575 FontMetrics const & fm = theFontMetrics(f);
576 int const pos = fm.underlinePos();
578 line(x, y + pos, x + width, y + pos,
579 f.realColor(), ls, fm.lineWidth());
583 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
585 FontMetrics const & fm = theFontMetrics(f);
586 int const pos = fm.strikeoutPos();
588 line(x, y - pos, x + width, y - pos,
589 f.realColor(), line_solid, fm.lineWidth());
593 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
595 FontMetrics const & fm = theFontMetrics(f);
596 int const pos1 = fm.underlinePos() + fm.lineWidth();
597 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
599 line(x, y + pos1, x + width, y + pos1,
600 f.realColor(), line_solid, fm.lineWidth());
601 line(x, y + pos2, x + width, y + pos2,
602 f.realColor(), line_solid, fm.lineWidth());
606 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
608 FontMetrics const & fm = theFontMetrics(f);
610 int const below = max(fm.maxDescent() / 2, 2);
611 int height = max((fm.maxDescent() / 4) - 1, 1);
616 for (int n = 0; n != height; ++n)
617 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
621 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
623 setQPainterPen(computeColor(col));
625 int const xend = x + width;
627 //FIXME: I am not sure if Antialiasing gives the best effect.
628 //setRenderHint(Antialiasing, true);
631 drawLine(x, y - height, x + step, y + height);
633 drawLine(x, y + height, x + step/2, y + height);
636 //setRenderHint(Antialiasing, false);
639 } // namespace frontend