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::text(int x, int y, docstring const & s,
288 FontInfo const & f, Direction const dir,
289 double const wordspacing, double const tw)
291 //LYXERR0("text: x=" << x << ", s=" << s);
295 /* Caution: The following ucs4 to QString conversions work for symbol fonts
296 only because they are no real conversions but simple casts in reality.
297 When we want to draw a symbol or calculate the metrics we pass the position
298 of the symbol in the font (as given in lib/symbols) as a char_type to the
299 frontend. This is just wrong, because the symbol is no UCS4 character at
300 all. You can think of this number as the code point of the symbol in a
301 custom symbol encoding. It works because this char_type is later on again
302 interpreted as a position in the font.
303 The correct solution would be to have extra functions for symbols, but that
304 would require to duplicate a lot of frontend and mathed support code.
306 QString str = toqstr(s);
309 // HACK: QT3 refuses to show single compose characters
310 // Still needed with Qt4?
311 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
315 QFont ff = getFont(f);
316 ff.setWordSpacing(wordspacing);
317 GuiFontMetrics const & fm = getFontMetrics(f);
321 // Take into account space stretching (word spacing)
322 textwidth = fm.width(s) +
323 static_cast<int>(fm.countExpanders(s) * wordspacing);
325 textwidth = static_cast<int>(tw);
327 textDecoration(f, x, y, textwidth);
329 setQPainterPen(computeColor(f.realColor()));
331 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
332 ptl->draw(this, QPointF(x, y - fm.maxAscent()));
338 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
339 // << " at " << x << "," << y);
343 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
344 double const wordspacing, double const tw)
346 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
351 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
352 Color other, size_type const from, size_type const to,
353 double const wordspacing, double const tw)
355 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
356 FontInfo fi = f.fontInfo();
357 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
360 int const ascent = fm.maxAscent();
361 int const height = fm.maxAscent() + fm.maxDescent();
362 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
363 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
367 // First the part in other color
368 Color const orig = fi.realColor();
369 fi.setPaintColor(other);
370 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
372 text(x, y, str, fi, dir, wordspacing, tw);
374 // Then the part in normal color
375 // Note that in Qt5, it is not possible to use Qt::UniteClip,
376 // therefore QRegion is used.
377 fi.setPaintColor(orig);
378 QRegion region(viewport());
379 setClipRegion(region - clip);
380 text(x, y, str, fi, dir, wordspacing, tw);
385 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
387 if (f.underbar() == FONT_ON)
388 underline(f, x, y, width);
389 if (f.strikeout() == FONT_ON)
390 strikeoutLine(f, x, y, width);
391 if (f.xout() == FONT_ON)
392 crossoutLines(f, x, y, width);
393 if (f.uuline() == FONT_ON)
394 doubleUnderline(f, x, y, width);
395 if (f.uwave() == FONT_ON)
396 // f.color() doesn't work on some circumstances
397 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
401 static int max(int a, int b) { return a > b ? a : b; }
404 void GuiPainter::rectText(int x, int y, docstring const & str,
405 FontInfo const & font, Color back, Color frame)
407 int width, ascent, descent;
409 FontMetrics const & fm = theFontMetrics(font);
410 fm.rectText(str, width, ascent, descent);
412 if (back != Color_none)
413 fillRectangle(x + 1, y - ascent + 1, width - 1,
414 ascent + descent - 1, back);
416 if (frame != Color_none)
417 rectangle(x, y - ascent, width, ascent + descent, frame);
419 // FIXME: let offset depend on font
420 text(x + 3, y, str, font);
424 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
425 FontInfo const & font, Color back, Color frame, int offset)
427 int width, ascent, descent;
429 FontMetrics const & fm = theFontMetrics(font);
430 fm.buttonText(s, offset, width, ascent, descent);
432 static int const d = offset / 2;
434 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
435 ascent + descent - 1, back);
436 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
437 text(x + offset, baseline, s, font);
441 int GuiPainter::preeditText(int x, int y, char_type c,
442 FontInfo const & font, preedit_style style)
444 FontInfo temp_font = font;
445 FontMetrics const & fm = theFontMetrics(font);
446 int ascent = fm.maxAscent();
447 int descent = fm.maxDescent();
448 int height = ascent + descent;
449 int width = fm.width(c);
452 case preedit_default:
453 // default unselecting mode.
454 fillRectangle(x, y - height + 1, width, height, Color_background);
455 dashedUnderline(font, x, y - descent + 1, width);
457 case preedit_selecting:
458 // We are in selecting mode: white text on black background.
459 fillRectangle(x, y - height + 1, width, height, Color_black);
460 temp_font.setColor(Color_white);
463 // The character comes with a cursor.
464 fillRectangle(x, y - height + 1, width, height, Color_background);
465 underline(font, x, y - descent + 1, width);
468 text(x, y - descent + 1, c, temp_font);
474 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
477 FontMetrics const & fm = theFontMetrics(f);
478 int const pos = fm.underlinePos();
480 line(x, y + pos, x + width, y + pos,
481 f.realColor(), ls, fm.lineWidth());
485 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
487 FontMetrics const & fm = theFontMetrics(f);
488 int const pos = fm.strikeoutPos();
490 line(x, y - pos, x + width, y - pos,
491 f.realColor(), line_solid, fm.lineWidth());
495 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
498 tmpf.setXout(FONT_OFF);
500 // the definition of \xout in ulem.sty is
501 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
502 // Let's mimick it somewhat.
503 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
504 for (int i = 0 ; i < iround(width / offset) ; ++i)
505 text(x + iround(i * offset), y, '/', tmpf);
509 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
511 FontMetrics const & fm = theFontMetrics(f);
512 int const pos1 = fm.underlinePos() + fm.lineWidth();
513 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
515 line(x, y + pos1, x + width, y + pos1,
516 f.realColor(), line_solid, fm.lineWidth());
517 line(x, y + pos2, x + width, y + pos2,
518 f.realColor(), line_solid, fm.lineWidth());
522 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
524 FontMetrics const & fm = theFontMetrics(f);
526 int const below = max(fm.maxDescent() / 2, 2);
527 int height = max((fm.maxDescent() / 4) - 1, 1);
532 for (int n = 0; n != height; ++n)
533 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
537 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
539 setQPainterPen(computeColor(col));
541 int const xend = x + width;
543 //FIXME: I am not sure if Antialiasing gives the best effect.
544 //setRenderHint(Antialiasing, true);
547 drawLine(x, y - height, x + step, y + height);
549 drawLine(x, y + height, x + step/2, y + height);
552 //setRenderHint(Antialiasing, false);
555 } // namespace frontend