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);
385 // don't use the pixmap cache,
386 // draw directly onto the painting device
387 setQPainterPen(computeColor(f.realColor()));
391 /* In LyX, the character direction is forced by the language.
392 * Therefore, we have to signal that fact to Qt.
395 /* Use unicode override characters to enforce drawing direction
396 * Source: http://www.iamcal.com/understanding-bidirectional-text/
399 // Right-to-left override: forces to draw text right-to-left
400 str = QChar(0x202E) + str;
402 // Left-to-right override: forces to draw text left-to-right
403 str = QChar(0x202D) + str;
406 /* This looks like a cleaner solution, but it has drawbacks
407 * - does not work reliably (Mac OS X, ...)
408 * - it is not really documented
409 * Keep it here for now, in case it can be helpful
411 //This is much stronger than setLayoutDirection.
412 int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
413 drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
414 flag | Qt::TextDontClip,
417 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
418 // << " at " << x << "," << y);
423 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
424 double const wordspacing)
426 return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
430 int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
431 Color other, size_type const from, size_type const to,
432 double const wordspacing)
434 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
435 FontInfo fi = f.fontInfo();
436 bool const rtl = f.isVisibleRightToLeft();
439 int const ascent = fm.maxAscent();
440 int const height = fm.maxAscent() + fm.maxDescent();
441 int xmin = fm.pos2x(str, from, rtl, wordspacing);
442 int xmax = fm.pos2x(str, to, rtl, wordspacing);
446 // First the part in other color
447 Color const orig = fi.realColor();
448 fi.setPaintColor(other);
449 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
451 int const textwidth = text(x, y, str, fi, rtl, wordspacing);
453 // Then the part in normal color
454 // Note that in Qt5, it is not possible to use Qt::UniteClip,
455 // therefore QRegion is used.
456 fi.setPaintColor(orig);
457 QRegion region(viewport());
458 setClipRegion(region - clip);
459 text(x, y, str, fi, rtl, wordspacing);
466 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
468 if (f.underbar() == FONT_ON)
469 underline(f, x, y, width);
470 if (f.strikeout() == FONT_ON)
471 strikeoutLine(f, x, y, width);
472 if (f.uuline() == FONT_ON)
473 doubleUnderline(f, x, y, width);
474 if (f.uwave() == FONT_ON)
475 // f.color() doesn't work on some circumstances
476 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
480 static int max(int a, int b) { return a > b ? a : b; }
483 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
486 fillRectangle(x, y, w, h, Color_buttonhoverbg);
488 fillRectangle(x, y, w, h, Color_buttonbg);
489 buttonFrame(x, y, w, h);
493 void GuiPainter::buttonFrame(int x, int y, int w, int h)
495 line(x, y, x, y + h - 1, Color_buttonframe);
496 line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
497 line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
498 line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
502 void GuiPainter::rectText(int x, int y, docstring const & str,
503 FontInfo const & font, Color back, Color frame)
509 FontMetrics const & fm = theFontMetrics(font);
510 fm.rectText(str, width, ascent, descent);
512 if (back != Color_none)
513 fillRectangle(x + 1, y - ascent + 1, width - 1,
514 ascent + descent - 1, back);
516 if (frame != Color_none)
517 rectangle(x, y - ascent, width, ascent + descent, frame);
519 text(x + 3, y, str, font);
523 void GuiPainter::buttonText(int x, int y, docstring const & str,
524 FontInfo const & font, bool mouseHover)
530 FontMetrics const & fm = theFontMetrics(font);
531 fm.buttonText(str, width, ascent, descent);
533 static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
535 button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
536 text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
540 int GuiPainter::preeditText(int x, int y, char_type c,
541 FontInfo const & font, preedit_style style)
543 FontInfo temp_font = font;
544 FontMetrics const & fm = theFontMetrics(font);
545 int ascent = fm.maxAscent();
546 int descent = fm.maxDescent();
547 int height = ascent + descent;
548 int width = fm.width(c);
551 case preedit_default:
552 // default unselecting mode.
553 fillRectangle(x, y - height + 1, width, height, Color_background);
554 dashedUnderline(font, x, y - descent + 1, width);
556 case preedit_selecting:
557 // We are in selecting mode: white text on black background.
558 fillRectangle(x, y - height + 1, width, height, Color_black);
559 temp_font.setColor(Color_white);
562 // The character comes with a cursor.
563 fillRectangle(x, y - height + 1, width, height, Color_background);
564 underline(font, x, y - descent + 1, width);
567 text(x, y - descent + 1, c, temp_font);
573 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
576 FontMetrics const & fm = theFontMetrics(f);
577 int const pos = fm.underlinePos();
579 line(x, y + pos, x + width, y + pos,
580 f.realColor(), ls, fm.lineWidth());
584 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
586 FontMetrics const & fm = theFontMetrics(f);
587 int const pos = fm.strikeoutPos();
589 line(x, y - pos, x + width, y - pos,
590 f.realColor(), line_solid, fm.lineWidth());
594 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
596 FontMetrics const & fm = theFontMetrics(f);
597 int const pos1 = fm.underlinePos() + fm.lineWidth();
598 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
600 line(x, y + pos1, x + width, y + pos1,
601 f.realColor(), line_solid, fm.lineWidth());
602 line(x, y + pos2, x + width, y + pos2,
603 f.realColor(), line_solid, fm.lineWidth());
607 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
609 FontMetrics const & fm = theFontMetrics(f);
611 int const below = max(fm.maxDescent() / 2, 2);
612 int height = max((fm.maxDescent() / 4) - 1, 1);
617 for (int n = 0; n != height; ++n)
618 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
622 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
624 setQPainterPen(computeColor(col));
626 int const xend = x + width;
628 //FIXME: I am not sure if Antialiasing gives the best effect.
629 //setRenderHint(Antialiasing, true);
632 drawLine(x, y - height, x + step, y + height);
634 drawLine(x, y + height, x + step/2, y + height);
637 //setRenderHint(Antialiasing, false);
640 } // namespace frontend