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"
29 #include "support/lstrings.h"
33 #include <QPainterPath>
34 #include <QTextLayout>
37 using namespace lyx::support;
42 const int Painter::thin_line = 1;
44 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio, bool devel_mode)
45 : QPainter(device), Painter(pixel_ratio, devel_mode)
47 // set cache correctly
48 current_color_ = pen().color();
49 current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
50 current_lw_ = pen().width();
54 GuiPainter::~GuiPainter()
57 //lyxerr << "GuiPainter::end()" << endl;
61 void GuiPainter::setQPainterPen(QColor const & col,
62 Painter::line_style ls, int lw, Qt::PenJoinStyle js)
64 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
71 QPen pen = QPainter::pen();
76 case line_solid_aliased:
77 pen.setStyle(Qt::SolidLine); break;
79 pen.setStyle(Qt::DotLine); break;
90 QColor GuiPainter::computeColor(Color col)
92 return filterColor(guiApp->colorCache().get(col));
96 QColor GuiPainter::filterColor(QColor const & col)
98 if (monochrome_blend_.empty())
101 QColor const blend = monochrome_blend_.top();
102 return QColor::fromHsv(blend.hue(), blend.saturation(), qGray(col.rgb()));
106 void GuiPainter::enterMonochromeMode(Color const & blend)
108 QColor qblend = filterColor(guiApp->colorCache().get(blend));
109 monochrome_blend_.push(qblend);
113 void GuiPainter::leaveMonochromeMode()
115 LASSERT(!monochrome_blend_.empty(), return);
116 monochrome_blend_.pop();
120 void GuiPainter::point(int x, int y, Color col)
122 setQPainterPen(computeColor(col));
127 void GuiPainter::line(int x1, int y1, int x2, int y2,
132 setQPainterPen(computeColor(col), ls, lw);
133 bool const do_antialiasing = renderHints() & TextAntialiasing
134 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
135 setRenderHint(Antialiasing, do_antialiasing);
136 drawLine(x1, y1, x2, y2);
137 setRenderHint(Antialiasing, false);
141 void GuiPainter::lines(int const * xp, int const * yp, int np,
147 // double the size if needed
149 static QVector<QPoint> points(32);
150 if (np > points.size())
151 points.resize(2 * np);
153 // Note: the proper way to not get blurry vertical and horizontal lines is
154 // to add 0.5 to all coordinates.
155 bool antialias = false;
156 for (int i = 0; i < np; ++i) {
157 points[i].setX(xp[i]);
158 points[i].setY(yp[i]);
160 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
162 QColor const color = computeColor(col);
163 setQPainterPen(color, ls, lw);
164 bool const text_is_antialiased = renderHints() & TextAntialiasing;
165 setRenderHint(Antialiasing,
166 antialias && text_is_antialiased && ls != line_solid_aliased);
167 if (fs == fill_none) {
168 drawPolyline(points.data(), np);
170 QBrush const oldbrush = brush();
171 setBrush(QBrush(color));
172 drawPolygon(points.data(), np, fs == fill_oddeven ?
173 Qt::OddEvenFill : Qt::WindingFill);
176 setRenderHint(Antialiasing, false);
180 void GuiPainter::path(int const * xp, int const * yp,
181 int const * c1x, int const * c1y,
182 int const * c2x, int const * c2y,
190 // This is the starting point, so its control points are meaningless
191 bpath.moveTo(xp[0], yp[0]);
193 for (int i = 1; i < np; ++i) {
194 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
195 c2x[i] == xp[i] && c2y[i] == yp[i];
197 bpath.lineTo(xp[i], yp[i]);
199 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
201 QColor const color = computeColor(col);
202 setQPainterPen(color, ls, lw);
203 bool const text_is_antialiased = renderHints() & TextAntialiasing;
204 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
207 fillPath(bpath, QBrush(color));
208 setRenderHint(Antialiasing, false);
212 void GuiPainter::rectangle(int x, int y, int w, int h,
217 setQPainterPen(computeColor(col), ls, lw, Qt::MiterJoin);
218 drawRect(x, y, w, h);
222 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
224 fillRect(x, y, w, h, guiApp->colorCache().get(col));
228 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
229 int a1, int a2, Color col)
231 // LyX usings 1/64ths degree, Qt usings 1/16th
232 setQPainterPen(computeColor(col));
233 bool const do_antialiasing = renderHints() & TextAntialiasing;
234 setRenderHint(Antialiasing, do_antialiasing);
235 drawArc(x, y, w, h, a1 / 4, a2 / 4);
236 setRenderHint(Antialiasing, false);
240 void GuiPainter::ellipse(double x, double y, double rx, double ry,
241 Color col, fill_style fs, line_style ls, int lw)
243 QColor const color = computeColor(col);
244 setQPainterPen(color, ls, lw);
245 bool const do_antialiasing = renderHints() & TextAntialiasing;
246 setRenderHint(Antialiasing, do_antialiasing);
247 if (fs == fill_none) {
248 drawEllipse(QPointF(x, y), rx, ry);
250 QBrush const oldbrush = brush();
251 setBrush(QBrush(color));
252 drawEllipse(QPointF(x, y), rx, ry);
255 setRenderHint(Antialiasing, false);
259 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i,
260 bool const revert_in_darkmode)
262 graphics::GuiImage const & qlimage =
263 static_cast<graphics::GuiImage const &>(i);
265 fillRectangle(x, y, w, h, Color_graphicsbg);
267 QImage image = qlimage.image();
269 if (revert_in_darkmode && guiApp && guiApp->colorCache().isDarkMode())
270 // FIXME this is only a cheap approximation
271 // Ideally, replace colors as in GuiApplication::prepareForDarkmode()
272 image.invertPixels();
274 QRectF const drect = QRectF(x, y, w, h);
275 QRectF const srect = QRectF(0, 0, image.width(), image.height());
276 // Bilinear filtering is needed on a rare occasion for instant previews when
277 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
278 // This filter is optimised by qt on pixel-aligned images, so this does not
279 // affect performances in other cases.
280 setRenderHint(SmoothPixmapTransform);
281 drawImage(drect, image, srect);
282 setRenderHint(SmoothPixmapTransform, false);
286 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f, Direction const dir)
288 text(x, y, docstring(1, c), f, dir);
292 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f, Direction const dir)
294 text(x, y, s, f, dir, 0.0, 0.0);
298 void GuiPainter::text(int x, int y, docstring const & s,
299 FontInfo const & f, Direction const dir,
300 double const wordspacing, double const tw)
302 //LYXERR0("text: x=" << x << ", s=" << s);
306 /* Caution: The following ucs4 to QString conversions work for symbol fonts
307 only because they are no real conversions but simple casts in reality.
308 When we want to draw a symbol or calculate the metrics we pass the position
309 of the symbol in the font (as given in lib/symbols) as a char_type to the
310 frontend. This is just wrong, because the symbol is no UCS4 character at
311 all. You can think of this number as the code point of the symbol in a
312 custom symbol encoding. It works because this char_type is later on again
313 interpreted as a position in the font.
314 The correct solution would be to have extra functions for symbols, but that
315 would require to duplicate a lot of frontend and mathed support code.
317 QString str = toqstr(s);
319 QFont ff = getFont(f);
320 ff.setWordSpacing(wordspacing);
321 GuiFontMetrics const & fm = getFontMetrics(f);
325 // Take into account space stretching (word spacing)
326 textwidth = fm.width(s) +
327 static_cast<int>(countExpanders(s) * wordspacing);
329 textwidth = static_cast<int>(tw);
331 textDecoration(f, x, y, textwidth);
333 setQPainterPen(computeColor(f.realColor()));
335 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
336 QTextLine const & tline = ptl->lineForTextPosition(0);
337 ptl->draw(this, QPointF(x, y - tline.ascent()));
343 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
344 // << " at " << x << "," << y);
348 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
349 double const wordspacing, double const tw)
351 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
356 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
357 Color other, size_type const from, size_type const to,
358 double const wordspacing, double const tw)
360 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
361 FontInfo fi = f.fontInfo();
362 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
365 int const ascent = fm.maxAscent();
366 int const height = fm.maxAscent() + fm.maxDescent();
367 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
368 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
369 // Avoid this case, since it would make the `other' text spill in some cases
371 text(x, y, str, fi, dir, wordspacing, tw);
373 } else if (xmin > xmax)
376 // First the part in other color
377 Color const orig = fi.realColor();
378 fi.setPaintColor(other);
379 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
381 text(x, y, str, fi, dir, wordspacing, tw);
383 // Then the part in normal color
384 // Note that in Qt5, it is not possible to use Qt::UniteClip,
385 // therefore QRegion is used.
386 fi.setPaintColor(orig);
387 QRegion region(viewport());
388 setClipRegion(region - clip);
389 text(x, y, str, fi, dir, wordspacing, tw);
394 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
396 if (f.underbar() == FONT_ON)
397 underline(f, x, y, width);
398 if (f.strikeout() == FONT_ON)
399 strikeoutLine(f, x, y, width);
400 if (f.xout() == FONT_ON)
401 crossoutLines(f, x, y, width);
402 if (f.uuline() == FONT_ON)
403 doubleUnderline(f, x, y, width);
404 if (f.uwave() == FONT_ON)
405 // f.color() doesn't work on some circumstances
406 wavyHorizontalLine(f, x, y, width, f.realColor().baseColor);
410 static int max(int a, int b) { return a > b ? a : b; }
413 void GuiPainter::rectText(int x, int y, docstring const & str,
414 FontInfo const & font, Color back, Color frame)
416 int width, ascent, descent;
418 FontMetrics const & fm = theFontMetrics(font);
419 fm.rectText(str, width, ascent, descent);
421 if (back != Color_none)
422 fillRectangle(x + 1, y - ascent + 1, width - 1,
423 ascent + descent - 1, back);
425 if (frame != Color_none)
426 rectangle(x, y - ascent, width, ascent + descent, frame);
428 // FIXME: let offset depend on font
429 text(x + 3, y, str, font);
433 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
434 FontInfo const & font, Color back, Color frame, int offset)
436 int width, ascent, descent;
438 FontMetrics const & fm = theFontMetrics(font);
439 fm.buttonText(s, offset, width, ascent, descent);
441 int const d = offset / 2;
443 fillRectangle(x + d, baseline - ascent, width - offset,
444 ascent + descent, back);
445 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
446 text(x + offset, baseline, s, font);
450 int GuiPainter::preeditText(int x, int y, char_type c,
451 FontInfo const & font, preedit_style style)
453 FontInfo temp_font = font;
454 FontMetrics const & fm = theFontMetrics(font);
455 int ascent = fm.maxAscent();
456 int descent = fm.maxDescent();
457 int height = ascent + descent;
458 int width = fm.width(c);
461 case preedit_default:
462 // default unselecting mode.
463 fillRectangle(x, y - height + 1, width, height, Color_background);
464 dashedUnderline(font, x, y - descent + 1, width);
466 case preedit_selecting:
467 // We are in selecting mode: white text on black background.
468 fillRectangle(x, y - height + 1, width, height, Color_darkgray);
469 temp_font.setColor(Color_white);
472 // The character comes with a cursor.
473 fillRectangle(x, y - height + 1, width, height, Color_background);
474 underline(font, x, y - descent + 1, width);
477 text(x, y - descent + 1, c, temp_font);
483 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
486 FontMetrics const & fm = theFontMetrics(f);
487 int const pos = fm.underlinePos();
489 line(x, y + pos, x + width, y + pos,
490 f.realColor(), ls, fm.lineWidth());
494 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
496 FontMetrics const & fm = theFontMetrics(f);
497 int const pos = fm.strikeoutPos();
499 line(x, y - pos, x + width, y - pos,
500 f.realColor(), line_solid, fm.lineWidth());
504 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
507 tmpf.setXout(FONT_OFF);
509 // the definition of \xout in ulem.sty is
510 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
511 // Let's mimic it somewhat.
512 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
513 for (int i = 0 ; i < iround(width / offset) ; ++i)
514 text(x + iround(i * offset), y, '/', tmpf);
518 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
520 FontMetrics const & fm = theFontMetrics(f);
521 int const pos1 = fm.underlinePos() + fm.lineWidth();
522 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
524 line(x, y + pos1, x + width, y + pos1,
525 f.realColor(), line_solid, fm.lineWidth());
526 line(x, y + pos2, x + width, y + pos2,
527 f.realColor(), line_solid, fm.lineWidth());
531 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
533 FontMetrics const & fm = theFontMetrics(f);
535 int const below = max(fm.maxDescent() / 2, 2);
536 int height = max((fm.maxDescent() / 4) - 1, 1);
541 for (int n = 0; n != height; ++n)
542 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
546 void GuiPainter::wavyHorizontalLine(FontInfo const & f, int x, int y, int width, ColorCode col)
548 FontMetrics const & fm = theFontMetrics(f);
549 int const pos = fm.underlinePos();
550 int const lw = max(1, fm.lineWidth());
552 setQPainterPen(computeColor(col), line_solid, lw);
553 int const step = 2 * lw;
554 int const xend = x + width;
556 //FIXME: I am not sure if Antialiasing gives the best effect.
557 //setRenderHint(Antialiasing, true);
558 QVector<QPoint> points;
561 points.append(QPoint(x, y + pos - height));
562 points.append(QPoint(x + step, y + pos + height));
564 points.append(QPoint(x, (qreal)y + pos + height));
565 points.append(QPoint(x + lw, y + pos + height));
568 drawPolyline(points);
569 //setRenderHint(Antialiasing, false);
572 } // namespace frontend