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"
26 #include "support/debug.h"
27 #include "support/lassert.h"
28 #include "support/lyxlib.h"
32 #include <QTextLayout>
35 using namespace lyx::support;
40 const int Painter::thin_line = 1;
42 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
43 : QPainter(device), Painter(pixel_ratio)
45 // set cache correctly
46 current_color_ = pen().color();
47 current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
48 current_lw_ = pen().width();
52 GuiPainter::~GuiPainter()
55 //lyxerr << "GuiPainter::end()" << endl;
59 void GuiPainter::setQPainterPen(QColor const & col,
60 Painter::line_style ls, int lw)
62 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
69 QPen pen = QPainter::pen();
74 case line_solid_aliased:
75 pen.setStyle(Qt::SolidLine); break;
77 pen.setStyle(Qt::DotLine); break;
86 QColor GuiPainter::computeColor(Color col)
88 return filterColor(guiApp->colorCache().get(col));
92 QColor GuiPainter::filterColor(QColor const & col)
94 if (monochrome_min_.empty())
97 // map into [min,max] interval
98 QColor const & min = monochrome_min_.top();
99 QColor const & max = monochrome_max_.top();
101 qreal v = col.valueF();
102 v *= v; // make it a bit steeper (i.e. darker)
104 qreal minr, ming, minb;
105 qreal maxr, maxg, maxb;
106 min.getRgbF(&minr, &ming, &minb);
107 max.getRgbF(&maxr, &maxg, &maxb);
111 v * (minr - maxr) + maxr,
112 v * (ming - maxg) + maxg,
113 v * (minb - maxb) + maxb);
118 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
120 QColor qmin = filterColor(guiApp->colorCache().get(min));
121 QColor qmax = filterColor(guiApp->colorCache().get(max));
122 monochrome_min_.push(qmin);
123 monochrome_max_.push(qmax);
127 void GuiPainter::leaveMonochromeMode()
129 LASSERT(!monochrome_min_.empty(), return);
130 monochrome_min_.pop();
131 monochrome_max_.pop();
135 void GuiPainter::point(int x, int y, Color col)
137 setQPainterPen(computeColor(col));
142 void GuiPainter::line(int x1, int y1, int x2, int y2,
147 setQPainterPen(computeColor(col), ls, lw);
148 bool const do_antialiasing = renderHints() & TextAntialiasing
149 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
150 setRenderHint(Antialiasing, do_antialiasing);
151 drawLine(x1, y1, x2, y2);
152 setRenderHint(Antialiasing, false);
156 void GuiPainter::lines(int const * xp, int const * yp, int np,
162 // double the size if needed
164 static QVector<QPoint> points(32);
165 if (np > points.size())
166 points.resize(2 * np);
168 // Note: the proper way to not get blurry vertical and horizontal lines is
169 // to add 0.5 to all coordinates.
170 bool antialias = false;
171 for (int i = 0; i < np; ++i) {
172 points[i].setX(xp[i]);
173 points[i].setY(yp[i]);
175 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
177 QColor const color = computeColor(col);
178 setQPainterPen(color, ls, lw);
179 bool const text_is_antialiased = renderHints() & TextAntialiasing;
180 setRenderHint(Antialiasing,
181 antialias && text_is_antialiased && ls != line_solid_aliased);
182 if (fs == fill_none) {
183 drawPolyline(points.data(), np);
185 QBrush const oldbrush = brush();
186 setBrush(QBrush(color));
187 drawPolygon(points.data(), np, fs == fill_oddeven ?
188 Qt::OddEvenFill : Qt::WindingFill);
191 setRenderHint(Antialiasing, false);
195 void GuiPainter::path(int const * xp, int const * yp,
196 int const * c1x, int const * c1y,
197 int const * c2x, int const * c2y,
205 // This is the starting point, so its control points are meaningless
206 bpath.moveTo(xp[0], yp[0]);
208 for (int i = 1; i < np; ++i) {
209 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
210 c2x[i] == xp[i] && c2y[i] == yp[i];
212 bpath.lineTo(xp[i], yp[i]);
214 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
216 QColor const color = computeColor(col);
217 setQPainterPen(color, ls, lw);
218 bool const text_is_antialiased = renderHints() & TextAntialiasing;
219 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
222 fillPath(bpath, QBrush(color));
223 setRenderHint(Antialiasing, false);
227 void GuiPainter::rectangle(int x, int y, int w, int h,
232 setQPainterPen(computeColor(col), ls, lw);
233 drawRect(x, y, w, h);
237 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
239 fillRect(x, y, w, h, guiApp->colorCache().get(col));
243 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
244 int a1, int a2, Color col)
246 // LyX usings 1/64ths degree, Qt usings 1/16th
247 setQPainterPen(computeColor(col));
248 bool const do_antialiasing = renderHints() & TextAntialiasing;
249 setRenderHint(Antialiasing, do_antialiasing);
250 drawArc(x, y, w, h, a1 / 4, a2 / 4);
251 setRenderHint(Antialiasing, false);
255 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
257 graphics::GuiImage const & qlimage =
258 static_cast<graphics::GuiImage const &>(i);
260 fillRectangle(x, y, w, h, Color_graphicsbg);
262 QImage const image = qlimage.image();
263 QRectF const drect = QRectF(x, y, w, h);
264 QRectF const srect = QRectF(0, 0, image.width(), image.height());
265 // Bilinear filtering is needed on a rare occasion for instant previews when
266 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
267 // This filter is optimised by qt on pixel-aligned images, so this does not
268 // affect performances in other cases.
269 setRenderHint(SmoothPixmapTransform);
270 drawImage(drect, image, srect);
271 setRenderHint(SmoothPixmapTransform, false);
275 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
277 text(x, y, docstring(1, c), f);
281 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
283 text(x, y, s, f, Auto, 0.0, 0.0);
287 void GuiPainter::do_drawText(int x, int y, QString str,
288 GuiPainter::Direction const dir,
289 FontInfo const & f, QFont ff)
291 setQPainterPen(computeColor(f.realColor()));
295 /* In LyX, the character direction is forced by the language.
296 * Therefore, we have to signal that fact to Qt.
299 /* Use unicode override characters to enforce drawing direction
300 * Source: http://www.iamcal.com/understanding-bidirectional-text/
303 // Right-to-left override: forces to draw text right-to-left
304 str = QChar(0x202E) + str;
306 // Left-to-right override: forces to draw text left-to-right
307 str = QChar(0x202D) + str;
310 /* This looks like a cleaner solution, but it has drawbacks
311 * - does not work reliably (Mac OS X, ...)
312 * - it is not really documented
313 * Keep it here for now, in case it can be helpful
315 //This is much stronger than setLayoutDirection.
318 flag = Qt::TextForceRightToLeft;
320 flag = Qt::TextForceLeftToRight;
321 drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
322 flag | Qt::TextDontClip,
328 void GuiPainter::text(int x, int y, docstring const & s,
329 FontInfo const & f, Direction const dir,
330 double const wordspacing, double const tw)
332 //LYXERR0("text: x=" << x << ", s=" << s);
336 /* Caution: The following ucs4 to QString conversions work for symbol fonts
337 only because they are no real conversions but simple casts in reality.
338 When we want to draw a symbol or calculate the metrics we pass the position
339 of the symbol in the font (as given in lib/symbols) as a char_type to the
340 frontend. This is just wrong, because the symbol is no UCS4 character at
341 all. You can think of this number as the code point of the symbol in a
342 custom symbol encoding. It works because this char_type is later on again
343 interpreted as a position in the font.
344 The correct solution would be to have extra functions for symbols, but that
345 would require to duplicate a lot of frontend and mathed support code.
347 QString str = toqstr(s);
350 // HACK: QT3 refuses to show single compose characters
351 // Still needed with Qt4?
352 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
356 QFont ff = getFont(f);
357 ff.setWordSpacing(wordspacing);
358 GuiFontMetrics const & fm = getFontMetrics(f);
362 // Take into account space stretching (word spacing)
363 textwidth = fm.width(s) +
364 static_cast<int>(fm.countExpanders(s) * wordspacing);
366 textwidth = static_cast<int>(tw);
368 textDecoration(f, x, y, textwidth);
370 setQPainterPen(computeColor(f.realColor()));
372 shared_ptr<QTextLayout const> ptl =
373 fm.getTextLayout(s, dir == RtL, wordspacing);
374 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
381 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
382 // << " at " << x << "," << y);
386 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
387 double const wordspacing, double const tw)
389 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
394 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
395 Color other, size_type const from, size_type const to,
396 double const wordspacing, double const tw)
398 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
399 FontInfo fi = f.fontInfo();
400 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
403 int const ascent = fm.maxAscent();
404 int const height = fm.maxAscent() + fm.maxDescent();
405 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
406 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
410 // First the part in other color
411 Color const orig = fi.realColor();
412 fi.setPaintColor(other);
413 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
415 text(x, y, str, fi, dir, wordspacing, tw);
417 // Then the part in normal color
418 // Note that in Qt5, it is not possible to use Qt::UniteClip,
419 // therefore QRegion is used.
420 fi.setPaintColor(orig);
421 QRegion region(viewport());
422 setClipRegion(region - clip);
423 text(x, y, str, fi, dir, wordspacing, tw);
428 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
430 if (f.underbar() == FONT_ON)
431 underline(f, x, y, width);
432 if (f.strikeout() == FONT_ON)
433 strikeoutLine(f, x, y, width);
434 if (f.xout() == FONT_ON)
435 crossoutLines(f, x, y, width);
436 if (f.uuline() == FONT_ON)
437 doubleUnderline(f, x, y, width);
438 if (f.uwave() == FONT_ON)
439 // f.color() doesn't work on some circumstances
440 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
444 static int max(int a, int b) { return a > b ? a : b; }
447 void GuiPainter::rectText(int x, int y, docstring const & str,
448 FontInfo const & font, Color back, Color frame)
450 int width, ascent, descent;
452 FontMetrics const & fm = theFontMetrics(font);
453 fm.rectText(str, width, ascent, descent);
455 if (back != Color_none)
456 fillRectangle(x + 1, y - ascent + 1, width - 1,
457 ascent + descent - 1, back);
459 if (frame != Color_none)
460 rectangle(x, y - ascent, width, ascent + descent, frame);
462 // FIXME: let offset depend on font
463 text(x + 3, y, str, font);
467 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
468 FontInfo const & font, Color back, Color frame, int offset)
470 int width, ascent, descent;
472 FontMetrics const & fm = theFontMetrics(font);
473 fm.buttonText(s, offset, width, ascent, descent);
475 static int const d = offset / 2;
477 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
478 ascent + descent - 1, back);
479 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
480 text(x + offset, baseline, s, font);
484 int GuiPainter::preeditText(int x, int y, char_type c,
485 FontInfo const & font, preedit_style style)
487 FontInfo temp_font = font;
488 FontMetrics const & fm = theFontMetrics(font);
489 int ascent = fm.maxAscent();
490 int descent = fm.maxDescent();
491 int height = ascent + descent;
492 int width = fm.width(c);
495 case preedit_default:
496 // default unselecting mode.
497 fillRectangle(x, y - height + 1, width, height, Color_background);
498 dashedUnderline(font, x, y - descent + 1, width);
500 case preedit_selecting:
501 // We are in selecting mode: white text on black background.
502 fillRectangle(x, y - height + 1, width, height, Color_black);
503 temp_font.setColor(Color_white);
506 // The character comes with a cursor.
507 fillRectangle(x, y - height + 1, width, height, Color_background);
508 underline(font, x, y - descent + 1, width);
511 text(x, y - descent + 1, c, temp_font);
517 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
520 FontMetrics const & fm = theFontMetrics(f);
521 int const pos = fm.underlinePos();
523 line(x, y + pos, x + width, y + pos,
524 f.realColor(), ls, fm.lineWidth());
528 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
530 FontMetrics const & fm = theFontMetrics(f);
531 int const pos = fm.strikeoutPos();
533 line(x, y - pos, x + width, y - pos,
534 f.realColor(), line_solid, fm.lineWidth());
538 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
541 tmpf.setXout(FONT_OFF);
543 // the definition of \xout in ulem.sty is
544 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
545 // Let's mimick it somewhat.
546 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
547 for (int i = 0 ; i < iround(width / offset) ; ++i)
548 text(x + iround(i * offset), y, '/', tmpf);
552 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
554 FontMetrics const & fm = theFontMetrics(f);
555 int const pos1 = fm.underlinePos() + fm.lineWidth();
556 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
558 line(x, y + pos1, x + width, y + pos1,
559 f.realColor(), line_solid, fm.lineWidth());
560 line(x, y + pos2, x + width, y + pos2,
561 f.realColor(), line_solid, fm.lineWidth());
565 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
567 FontMetrics const & fm = theFontMetrics(f);
569 int const below = max(fm.maxDescent() / 2, 2);
570 int height = max((fm.maxDescent() / 4) - 1, 1);
575 for (int n = 0; n != height; ++n)
576 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
580 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
582 setQPainterPen(computeColor(col));
584 int const xend = x + width;
586 //FIXME: I am not sure if Antialiasing gives the best effect.
587 //setRenderHint(Antialiasing, true);
590 drawLine(x, y - height, x + step, y + height);
592 drawLine(x, y + height, x + step/2, y + height);
595 //setRenderHint(Antialiasing, false);
598 } // namespace frontend