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 <QTextLayout>
36 using namespace lyx::support;
41 const int Painter::thin_line = 1;
43 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio, bool devel_mode)
44 : QPainter(device), Painter(pixel_ratio, devel_mode)
46 // set cache correctly
47 current_color_ = pen().color();
48 current_ls_ = pen().style() == Qt::DotLine ? line_onoffdash : line_solid;
49 current_lw_ = pen().width();
53 GuiPainter::~GuiPainter()
56 //lyxerr << "GuiPainter::end()" << endl;
60 void GuiPainter::setQPainterPen(QColor const & col,
61 Painter::line_style ls, int lw, Qt::PenJoinStyle js)
63 if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
70 QPen pen = QPainter::pen();
75 case line_solid_aliased:
76 pen.setStyle(Qt::SolidLine); break;
78 pen.setStyle(Qt::DotLine); break;
89 QColor GuiPainter::computeColor(Color col)
91 return filterColor(guiApp->colorCache().get(col));
95 QColor GuiPainter::filterColor(QColor const & col)
97 if (monochrome_blend_.empty())
100 QColor const blend = monochrome_blend_.top();
101 return QColor::fromHsv(blend.hue(), blend.saturation(), qGray(col.rgb()));
105 void GuiPainter::enterMonochromeMode(Color const & blend)
107 QColor qblend = filterColor(guiApp->colorCache().get(blend));
108 monochrome_blend_.push(qblend);
112 void GuiPainter::leaveMonochromeMode()
114 LASSERT(!monochrome_blend_.empty(), return);
115 monochrome_blend_.pop();
119 void GuiPainter::point(int x, int y, Color col)
121 setQPainterPen(computeColor(col));
126 void GuiPainter::line(int x1, int y1, int x2, int y2,
131 setQPainterPen(computeColor(col), ls, lw);
132 bool const do_antialiasing = renderHints() & TextAntialiasing
133 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
134 setRenderHint(Antialiasing, do_antialiasing);
135 drawLine(x1, y1, x2, y2);
136 setRenderHint(Antialiasing, false);
140 void GuiPainter::lines(int const * xp, int const * yp, int np,
146 // double the size if needed
148 static QVector<QPoint> points(32);
149 if (np > points.size())
150 points.resize(2 * np);
152 // Note: the proper way to not get blurry vertical and horizontal lines is
153 // to add 0.5 to all coordinates.
154 bool antialias = false;
155 for (int i = 0; i < np; ++i) {
156 points[i].setX(xp[i]);
157 points[i].setY(yp[i]);
159 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
161 QColor const color = computeColor(col);
162 setQPainterPen(color, ls, lw);
163 bool const text_is_antialiased = renderHints() & TextAntialiasing;
164 setRenderHint(Antialiasing,
165 antialias && text_is_antialiased && ls != line_solid_aliased);
166 if (fs == fill_none) {
167 drawPolyline(points.data(), np);
169 QBrush const oldbrush = brush();
170 setBrush(QBrush(color));
171 drawPolygon(points.data(), np, fs == fill_oddeven ?
172 Qt::OddEvenFill : Qt::WindingFill);
175 setRenderHint(Antialiasing, false);
179 void GuiPainter::path(int const * xp, int const * yp,
180 int const * c1x, int const * c1y,
181 int const * c2x, int const * c2y,
189 // This is the starting point, so its control points are meaningless
190 bpath.moveTo(xp[0], yp[0]);
192 for (int i = 1; i < np; ++i) {
193 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
194 c2x[i] == xp[i] && c2y[i] == yp[i];
196 bpath.lineTo(xp[i], yp[i]);
198 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
200 QColor const color = computeColor(col);
201 setQPainterPen(color, ls, lw);
202 bool const text_is_antialiased = renderHints() & TextAntialiasing;
203 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
206 fillPath(bpath, QBrush(color));
207 setRenderHint(Antialiasing, false);
211 void GuiPainter::rectangle(int x, int y, int w, int h,
216 setQPainterPen(computeColor(col), ls, lw, Qt::MiterJoin);
217 drawRect(x, y, w, h);
221 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
223 fillRect(x, y, w, h, guiApp->colorCache().get(col));
227 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
228 int a1, int a2, Color col)
230 // LyX usings 1/64ths degree, Qt usings 1/16th
231 setQPainterPen(computeColor(col));
232 bool const do_antialiasing = renderHints() & TextAntialiasing;
233 setRenderHint(Antialiasing, do_antialiasing);
234 drawArc(x, y, w, h, a1 / 4, a2 / 4);
235 setRenderHint(Antialiasing, false);
239 void GuiPainter::ellipse(double x, double y, double rx, double ry,
240 Color col, fill_style fs, line_style ls, int lw)
242 QColor const color = computeColor(col);
243 setQPainterPen(color, ls, lw);
244 bool const do_antialiasing = renderHints() & TextAntialiasing;
245 setRenderHint(Antialiasing, do_antialiasing);
246 if (fs == fill_none) {
247 drawEllipse(QPointF(x, y), rx, ry);
249 QBrush const oldbrush = brush();
250 setBrush(QBrush(color));
251 drawEllipse(QPointF(x, y), rx, ry);
254 setRenderHint(Antialiasing, false);
258 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i,
259 bool const revert_in_darkmode)
261 graphics::GuiImage const & qlimage =
262 static_cast<graphics::GuiImage const &>(i);
264 fillRectangle(x, y, w, h, Color_graphicsbg);
266 QImage image = qlimage.image();
268 if (revert_in_darkmode && guiApp && guiApp->colorCache().isDarkMode())
269 // FIXME this is only a cheap approximation
270 // Ideally, replace colors as in GuiApplication::prepareForDarkmode()
271 image.invertPixels();
273 QRectF const drect = QRectF(x, y, w, h);
274 QRectF const srect = QRectF(0, 0, image.width(), image.height());
275 // Bilinear filtering is needed on a rare occasion for instant previews when
276 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
277 // This filter is optimised by qt on pixel-aligned images, so this does not
278 // affect performances in other cases.
279 setRenderHint(SmoothPixmapTransform);
280 drawImage(drect, image, srect);
281 setRenderHint(SmoothPixmapTransform, false);
285 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
287 text(x, y, docstring(1, c), f);
291 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
293 text(x, y, s, f, Auto, 0.0, 0.0);
297 void GuiPainter::text(int x, int y, docstring const & s,
298 FontInfo const & f, Direction const dir,
299 double const wordspacing, double const tw)
301 //LYXERR0("text: x=" << x << ", s=" << s);
305 /* Caution: The following ucs4 to QString conversions work for symbol fonts
306 only because they are no real conversions but simple casts in reality.
307 When we want to draw a symbol or calculate the metrics we pass the position
308 of the symbol in the font (as given in lib/symbols) as a char_type to the
309 frontend. This is just wrong, because the symbol is no UCS4 character at
310 all. You can think of this number as the code point of the symbol in a
311 custom symbol encoding. It works because this char_type is later on again
312 interpreted as a position in the font.
313 The correct solution would be to have extra functions for symbols, but that
314 would require to duplicate a lot of frontend and mathed support code.
316 QString str = toqstr(s);
318 QFont ff = getFont(f);
319 ff.setWordSpacing(wordspacing);
320 GuiFontMetrics const & fm = getFontMetrics(f);
324 // Take into account space stretching (word spacing)
325 textwidth = fm.width(s) +
326 static_cast<int>(countExpanders(s) * wordspacing);
328 textwidth = static_cast<int>(tw);
330 textDecoration(f, x, y, textwidth);
332 setQPainterPen(computeColor(f.realColor()));
334 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
335 QTextLine const & tline = ptl->lineForTextPosition(0);
336 ptl->draw(this, QPointF(x, y - tline.ascent()));
342 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
343 // << " at " << x << "," << y);
347 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
348 double const wordspacing, double const tw)
350 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
355 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
356 Color other, size_type const from, size_type const to,
357 double const wordspacing, double const tw)
359 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
360 FontInfo fi = f.fontInfo();
361 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
364 int const ascent = fm.maxAscent();
365 int const height = fm.maxAscent() + fm.maxDescent();
366 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
367 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
368 // Avoid this case, since it would make the `other' text spill in some cases
370 text(x, y, str, fi, dir, wordspacing, tw);
372 } else if (xmin > xmax)
375 // First the part in other color
376 Color const orig = fi.realColor();
377 fi.setPaintColor(other);
378 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
380 text(x, y, str, fi, dir, wordspacing, tw);
382 // Then the part in normal color
383 // Note that in Qt5, it is not possible to use Qt::UniteClip,
384 // therefore QRegion is used.
385 fi.setPaintColor(orig);
386 QRegion region(viewport());
387 setClipRegion(region - clip);
388 text(x, y, str, fi, dir, wordspacing, tw);
393 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
395 if (f.underbar() == FONT_ON)
396 underline(f, x, y, width);
397 if (f.strikeout() == FONT_ON)
398 strikeoutLine(f, x, y, width);
399 if (f.xout() == FONT_ON)
400 crossoutLines(f, x, y, width);
401 if (f.uuline() == FONT_ON)
402 doubleUnderline(f, x, y, width);
403 if (f.uwave() == FONT_ON)
404 // f.color() doesn't work on some circumstances
405 wavyHorizontalLine(f, x, y, width, f.realColor().baseColor);
409 static int max(int a, int b) { return a > b ? a : b; }
412 void GuiPainter::rectText(int x, int y, docstring const & str,
413 FontInfo const & font, Color back, Color frame)
415 int width, ascent, descent;
417 FontMetrics const & fm = theFontMetrics(font);
418 fm.rectText(str, width, ascent, descent);
420 if (back != Color_none)
421 fillRectangle(x + 1, y - ascent + 1, width - 1,
422 ascent + descent - 1, back);
424 if (frame != Color_none)
425 rectangle(x, y - ascent, width, ascent + descent, frame);
427 // FIXME: let offset depend on font
428 text(x + 3, y, str, font);
432 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
433 FontInfo const & font, Color back, Color frame, int offset)
435 int width, ascent, descent;
437 FontMetrics const & fm = theFontMetrics(font);
438 fm.buttonText(s, offset, width, ascent, descent);
440 int const d = offset / 2;
442 fillRectangle(x + d, baseline - ascent, width - offset,
443 ascent + descent, back);
444 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
445 text(x + offset, baseline, s, font);
449 int GuiPainter::preeditText(int x, int y, char_type c,
450 FontInfo const & font, preedit_style style)
452 FontInfo temp_font = font;
453 FontMetrics const & fm = theFontMetrics(font);
454 int ascent = fm.maxAscent();
455 int descent = fm.maxDescent();
456 int height = ascent + descent;
457 int width = fm.width(c);
460 case preedit_default:
461 // default unselecting mode.
462 fillRectangle(x, y - height + 1, width, height, Color_background);
463 dashedUnderline(font, x, y - descent + 1, width);
465 case preedit_selecting:
466 // We are in selecting mode: white text on black background.
467 fillRectangle(x, y - height + 1, width, height, Color_black);
468 temp_font.setColor(Color_white);
471 // The character comes with a cursor.
472 fillRectangle(x, y - height + 1, width, height, Color_background);
473 underline(font, x, y - descent + 1, width);
476 text(x, y - descent + 1, c, temp_font);
482 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
485 FontMetrics const & fm = theFontMetrics(f);
486 int const pos = fm.underlinePos();
488 line(x, y + pos, x + width, y + pos,
489 f.realColor(), ls, fm.lineWidth());
493 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
495 FontMetrics const & fm = theFontMetrics(f);
496 int const pos = fm.strikeoutPos();
498 line(x, y - pos, x + width, y - pos,
499 f.realColor(), line_solid, fm.lineWidth());
503 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
506 tmpf.setXout(FONT_OFF);
508 // the definition of \xout in ulem.sty is
509 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
510 // Let's mimic it somewhat.
511 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
512 for (int i = 0 ; i < iround(width / offset) ; ++i)
513 text(x + iround(i * offset), y, '/', tmpf);
517 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
519 FontMetrics const & fm = theFontMetrics(f);
520 int const pos1 = fm.underlinePos() + fm.lineWidth();
521 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
523 line(x, y + pos1, x + width, y + pos1,
524 f.realColor(), line_solid, fm.lineWidth());
525 line(x, y + pos2, x + width, y + pos2,
526 f.realColor(), line_solid, fm.lineWidth());
530 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
532 FontMetrics const & fm = theFontMetrics(f);
534 int const below = max(fm.maxDescent() / 2, 2);
535 int height = max((fm.maxDescent() / 4) - 1, 1);
540 for (int n = 0; n != height; ++n)
541 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
545 void GuiPainter::wavyHorizontalLine(FontInfo const & f, int x, int y, int width, ColorCode col)
547 FontMetrics const & fm = theFontMetrics(f);
548 int const pos = fm.underlinePos();
550 setQPainterPen(computeColor(col), line_solid, fm.lineWidth());
551 int const step = 2 * fm.lineWidth();
552 int const xend = x + width;
553 int height = 1 * fm.lineWidth();
554 //FIXME: I am not sure if Antialiasing gives the best effect.
555 //setRenderHint(Antialiasing, true);
556 QVector<QPoint> points;
559 points.append(QPoint(x, y + pos - height));
560 points.append(QPoint(x + step, y + pos + height));
562 points.append(QPoint(x, (qreal)y + pos + height));
563 points.append(QPoint(x + step/2, y + pos + height));
566 drawPolyline(points);
567 //setRenderHint(Antialiasing, false);
570 } // namespace frontend