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, bool devel_mode)
43 : QPainter(device), Painter(pixel_ratio, devel_mode)
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_blend_.empty())
97 QColor const blend = monochrome_blend_.top();
98 return QColor::fromHsv(blend.hue(), blend.saturation(), qGray(col.rgb()));
102 void GuiPainter::enterMonochromeMode(Color const & blend)
104 QColor qblend = filterColor(guiApp->colorCache().get(blend));
105 monochrome_blend_.push(qblend);
109 void GuiPainter::leaveMonochromeMode()
111 LASSERT(!monochrome_blend_.empty(), return);
112 monochrome_blend_.pop();
116 void GuiPainter::point(int x, int y, Color col)
118 setQPainterPen(computeColor(col));
123 void GuiPainter::line(int x1, int y1, int x2, int y2,
128 setQPainterPen(computeColor(col), ls, lw);
129 bool const do_antialiasing = renderHints() & TextAntialiasing
130 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
131 setRenderHint(Antialiasing, do_antialiasing);
132 drawLine(x1, y1, x2, y2);
133 setRenderHint(Antialiasing, false);
137 void GuiPainter::lines(int const * xp, int const * yp, int np,
143 // double the size if needed
145 static QVector<QPoint> points(32);
146 if (np > points.size())
147 points.resize(2 * np);
149 // Note: the proper way to not get blurry vertical and horizontal lines is
150 // to add 0.5 to all coordinates.
151 bool antialias = false;
152 for (int i = 0; i < np; ++i) {
153 points[i].setX(xp[i]);
154 points[i].setY(yp[i]);
156 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
158 QColor const color = computeColor(col);
159 setQPainterPen(color, ls, lw);
160 bool const text_is_antialiased = renderHints() & TextAntialiasing;
161 setRenderHint(Antialiasing,
162 antialias && text_is_antialiased && ls != line_solid_aliased);
163 if (fs == fill_none) {
164 drawPolyline(points.data(), np);
166 QBrush const oldbrush = brush();
167 setBrush(QBrush(color));
168 drawPolygon(points.data(), np, fs == fill_oddeven ?
169 Qt::OddEvenFill : Qt::WindingFill);
172 setRenderHint(Antialiasing, false);
176 void GuiPainter::path(int const * xp, int const * yp,
177 int const * c1x, int const * c1y,
178 int const * c2x, int const * c2y,
186 // This is the starting point, so its control points are meaningless
187 bpath.moveTo(xp[0], yp[0]);
189 for (int i = 1; i < np; ++i) {
190 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
191 c2x[i] == xp[i] && c2y[i] == yp[i];
193 bpath.lineTo(xp[i], yp[i]);
195 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
197 QColor const color = computeColor(col);
198 setQPainterPen(color, ls, lw);
199 bool const text_is_antialiased = renderHints() & TextAntialiasing;
200 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
203 fillPath(bpath, QBrush(color));
204 setRenderHint(Antialiasing, false);
208 void GuiPainter::rectangle(int x, int y, int w, int h,
213 setQPainterPen(computeColor(col), ls, lw);
214 drawRect(x, y, w, h);
218 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
220 fillRect(x, y, w, h, guiApp->colorCache().get(col));
224 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
225 int a1, int a2, Color col)
227 // LyX usings 1/64ths degree, Qt usings 1/16th
228 setQPainterPen(computeColor(col));
229 bool const do_antialiasing = renderHints() & TextAntialiasing;
230 setRenderHint(Antialiasing, do_antialiasing);
231 drawArc(x, y, w, h, a1 / 4, a2 / 4);
232 setRenderHint(Antialiasing, false);
236 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i,
237 bool revert_in_darkmode)
239 graphics::GuiImage const & qlimage =
240 static_cast<graphics::GuiImage const &>(i);
242 fillRectangle(x, y, w, h, Color_graphicsbg);
244 QImage image = qlimage.image();
246 QPalette palette = QPalette();
247 QColor text_color = palette.color(QPalette::Active, QPalette::WindowText);
248 QColor bg_color = palette.color(QPalette::Active, QPalette::Window);
249 // guess whether we are in dark mode
250 bool const in_dark_mode = text_color.black() < bg_color.black();
251 // if we are in dark mode, check whether we have transparent pixels
252 if (in_dark_mode && !revert_in_darkmode) {
253 QImage img = image.convertToFormat(QImage::Format_ARGB32);
254 for (int x = 0 ; x < img.width() ; x++) {
255 if (revert_in_darkmode)
257 for (int y = 0 ; y < img.height() ; y++) {
258 QRgb currentPixel = (img.pixel(x, y));
259 if (qAlpha(currentPixel) == 0) {
260 // we have transparent pixels, revert
261 // this image in dark mode (#12076)
262 revert_in_darkmode = true;
268 if (in_dark_mode && revert_in_darkmode)
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);
319 // HACK: QT3 refuses to show single compose characters
320 // Still needed with Qt4?
321 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
325 QFont ff = getFont(f);
326 ff.setWordSpacing(wordspacing);
327 GuiFontMetrics const & fm = getFontMetrics(f);
331 // Take into account space stretching (word spacing)
332 textwidth = fm.width(s) +
333 static_cast<int>(fm.countExpanders(s) * wordspacing);
335 textwidth = static_cast<int>(tw);
337 textDecoration(f, x, y, textwidth);
339 setQPainterPen(computeColor(f.realColor()));
341 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
342 QTextLine const & tline = ptl->lineForTextPosition(0);
343 ptl->draw(this, QPointF(x, y - tline.ascent()));
349 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
350 // << " at " << x << "," << y);
354 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
355 double const wordspacing, double const tw)
357 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
362 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
363 Color other, size_type const from, size_type const to,
364 double const wordspacing, double const tw)
366 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
367 FontInfo fi = f.fontInfo();
368 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
371 int const ascent = fm.maxAscent();
372 int const height = fm.maxAscent() + fm.maxDescent();
373 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
374 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
375 // Avoid this case, since it would make the `other' text spill in some cases
377 text(x, y, str, fi, dir, wordspacing, tw);
379 } else if (xmin > xmax)
382 // First the part in other color
383 Color const orig = fi.realColor();
384 fi.setPaintColor(other);
385 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
387 text(x, y, str, fi, dir, wordspacing, tw);
389 // Then the part in normal color
390 // Note that in Qt5, it is not possible to use Qt::UniteClip,
391 // therefore QRegion is used.
392 fi.setPaintColor(orig);
393 QRegion region(viewport());
394 setClipRegion(region - clip);
395 text(x, y, str, fi, dir, wordspacing, tw);
400 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
402 if (f.underbar() == FONT_ON)
403 underline(f, x, y, width);
404 if (f.strikeout() == FONT_ON)
405 strikeoutLine(f, x, y, width);
406 if (f.xout() == FONT_ON)
407 crossoutLines(f, x, y, width);
408 if (f.uuline() == FONT_ON)
409 doubleUnderline(f, x, y, width);
410 if (f.uwave() == FONT_ON)
411 // f.color() doesn't work on some circumstances
412 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
416 static int max(int a, int b) { return a > b ? a : b; }
419 void GuiPainter::rectText(int x, int y, docstring const & str,
420 FontInfo const & font, Color back, Color frame)
422 int width, ascent, descent;
424 FontMetrics const & fm = theFontMetrics(font);
425 fm.rectText(str, width, ascent, descent);
427 if (back != Color_none)
428 fillRectangle(x + 1, y - ascent + 1, width - 1,
429 ascent + descent - 1, back);
431 if (frame != Color_none)
432 rectangle(x, y - ascent, width, ascent + descent, frame);
434 // FIXME: let offset depend on font
435 text(x + 3, y, str, font);
439 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
440 FontInfo const & font, Color back, Color frame, int offset)
442 int width, ascent, descent;
444 FontMetrics const & fm = theFontMetrics(font);
445 fm.buttonText(s, offset, width, ascent, descent);
447 int const d = offset / 2;
449 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
450 ascent + descent - 1, back);
451 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
452 text(x + offset, baseline, s, font);
456 int GuiPainter::preeditText(int x, int y, char_type c,
457 FontInfo const & font, preedit_style style)
459 FontInfo temp_font = font;
460 FontMetrics const & fm = theFontMetrics(font);
461 int ascent = fm.maxAscent();
462 int descent = fm.maxDescent();
463 int height = ascent + descent;
464 int width = fm.width(c);
467 case preedit_default:
468 // default unselecting mode.
469 fillRectangle(x, y - height + 1, width, height, Color_background);
470 dashedUnderline(font, x, y - descent + 1, width);
472 case preedit_selecting:
473 // We are in selecting mode: white text on black background.
474 fillRectangle(x, y - height + 1, width, height, Color_black);
475 temp_font.setColor(Color_white);
478 // The character comes with a cursor.
479 fillRectangle(x, y - height + 1, width, height, Color_background);
480 underline(font, x, y - descent + 1, width);
483 text(x, y - descent + 1, c, temp_font);
489 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
492 FontMetrics const & fm = theFontMetrics(f);
493 int const pos = fm.underlinePos();
495 line(x, y + pos, x + width, y + pos,
496 f.realColor(), ls, fm.lineWidth());
500 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
502 FontMetrics const & fm = theFontMetrics(f);
503 int const pos = fm.strikeoutPos();
505 line(x, y - pos, x + width, y - pos,
506 f.realColor(), line_solid, fm.lineWidth());
510 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
513 tmpf.setXout(FONT_OFF);
515 // the definition of \xout in ulem.sty is
516 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
517 // Let's mimic it somewhat.
518 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
519 for (int i = 0 ; i < iround(width / offset) ; ++i)
520 text(x + iround(i * offset), y, '/', tmpf);
524 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
526 FontMetrics const & fm = theFontMetrics(f);
527 int const pos1 = fm.underlinePos() + fm.lineWidth();
528 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
530 line(x, y + pos1, x + width, y + pos1,
531 f.realColor(), line_solid, fm.lineWidth());
532 line(x, y + pos2, x + width, y + pos2,
533 f.realColor(), line_solid, fm.lineWidth());
537 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
539 FontMetrics const & fm = theFontMetrics(f);
541 int const below = max(fm.maxDescent() / 2, 2);
542 int height = max((fm.maxDescent() / 4) - 1, 1);
547 for (int n = 0; n != height; ++n)
548 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
552 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
554 setQPainterPen(computeColor(col));
556 int const xend = x + width;
558 //FIXME: I am not sure if Antialiasing gives the best effect.
559 //setRenderHint(Antialiasing, true);
562 drawLine(x, y - height, x + step, y + height);
564 drawLine(x, y + height, x + step/2, y + height);
567 //setRenderHint(Antialiasing, false);
570 } // namespace frontend