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::ellipse(double x, double y, double rx, double ry,
237 Color col, fill_style fs, line_style ls, int lw)
239 QColor const color = computeColor(col);
240 setQPainterPen(color, ls, lw);
241 bool const do_antialiasing = renderHints() & TextAntialiasing;
242 setRenderHint(Antialiasing, do_antialiasing);
243 if (fs == fill_none) {
244 drawEllipse(QPointF(x, y), rx, ry);
246 QBrush const oldbrush = brush();
247 setBrush(QBrush(color));
248 drawEllipse(QPointF(x, y), rx, ry);
251 setRenderHint(Antialiasing, false);
255 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i,
256 bool const revert_in_darkmode)
258 graphics::GuiImage const & qlimage =
259 static_cast<graphics::GuiImage const &>(i);
261 fillRectangle(x, y, w, h, Color_graphicsbg);
263 QImage image = qlimage.image();
265 if (revert_in_darkmode && guiApp && guiApp->colorCache().isDarkMode())
266 // FIXME this is only a cheap approximation
267 // Ideally, replace colors as in GuiApplication::prepareForDarkmode()
268 image.invertPixels();
270 QRectF const drect = QRectF(x, y, w, h);
271 QRectF const srect = QRectF(0, 0, image.width(), image.height());
272 // Bilinear filtering is needed on a rare occasion for instant previews when
273 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
274 // This filter is optimised by qt on pixel-aligned images, so this does not
275 // affect performances in other cases.
276 setRenderHint(SmoothPixmapTransform);
277 drawImage(drect, image, srect);
278 setRenderHint(SmoothPixmapTransform, false);
282 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
284 text(x, y, docstring(1, c), f);
288 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
290 text(x, y, s, f, Auto, 0.0, 0.0);
294 void GuiPainter::text(int x, int y, docstring const & s,
295 FontInfo const & f, Direction const dir,
296 double const wordspacing, double const tw)
298 //LYXERR0("text: x=" << x << ", s=" << s);
302 /* Caution: The following ucs4 to QString conversions work for symbol fonts
303 only because they are no real conversions but simple casts in reality.
304 When we want to draw a symbol or calculate the metrics we pass the position
305 of the symbol in the font (as given in lib/symbols) as a char_type to the
306 frontend. This is just wrong, because the symbol is no UCS4 character at
307 all. You can think of this number as the code point of the symbol in a
308 custom symbol encoding. It works because this char_type is later on again
309 interpreted as a position in the font.
310 The correct solution would be to have extra functions for symbols, but that
311 would require to duplicate a lot of frontend and mathed support code.
313 QString str = toqstr(s);
316 // HACK: QT3 refuses to show single compose characters
317 // Still needed with Qt4?
318 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
322 QFont ff = getFont(f);
323 ff.setWordSpacing(wordspacing);
324 GuiFontMetrics const & fm = getFontMetrics(f);
328 // Take into account space stretching (word spacing)
329 textwidth = fm.width(s) +
330 static_cast<int>(fm.countExpanders(s) * wordspacing);
332 textwidth = static_cast<int>(tw);
334 textDecoration(f, x, y, textwidth);
336 setQPainterPen(computeColor(f.realColor()));
338 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
339 QTextLine const & tline = ptl->lineForTextPosition(0);
340 ptl->draw(this, QPointF(x, y - tline.ascent()));
346 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
347 // << " at " << x << "," << y);
351 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
352 double const wordspacing, double const tw)
354 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
359 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
360 Color other, size_type const from, size_type const to,
361 double const wordspacing, double const tw)
363 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
364 FontInfo fi = f.fontInfo();
365 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
368 int const ascent = fm.maxAscent();
369 int const height = fm.maxAscent() + fm.maxDescent();
370 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
371 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
372 // Avoid this case, since it would make the `other' text spill in some cases
374 text(x, y, str, fi, dir, wordspacing, tw);
376 } else if (xmin > xmax)
379 // First the part in other color
380 Color const orig = fi.realColor();
381 fi.setPaintColor(other);
382 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
384 text(x, y, str, fi, dir, wordspacing, tw);
386 // Then the part in normal color
387 // Note that in Qt5, it is not possible to use Qt::UniteClip,
388 // therefore QRegion is used.
389 fi.setPaintColor(orig);
390 QRegion region(viewport());
391 setClipRegion(region - clip);
392 text(x, y, str, fi, dir, wordspacing, tw);
397 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
399 if (f.underbar() == FONT_ON)
400 underline(f, x, y, width);
401 if (f.strikeout() == FONT_ON)
402 strikeoutLine(f, x, y, width);
403 if (f.xout() == FONT_ON)
404 crossoutLines(f, x, y, width);
405 if (f.uuline() == FONT_ON)
406 doubleUnderline(f, x, y, width);
407 if (f.uwave() == FONT_ON)
408 // f.color() doesn't work on some circumstances
409 wavyHorizontalLine(f, x, y, width, f.realColor().baseColor);
413 static int max(int a, int b) { return a > b ? a : b; }
416 void GuiPainter::rectText(int x, int y, docstring const & str,
417 FontInfo const & font, Color back, Color frame)
419 int width, ascent, descent;
421 FontMetrics const & fm = theFontMetrics(font);
422 fm.rectText(str, width, ascent, descent);
424 if (back != Color_none)
425 fillRectangle(x + 1, y - ascent + 1, width - 1,
426 ascent + descent - 1, back);
428 if (frame != Color_none)
429 rectangle(x, y - ascent, width, ascent + descent, frame);
431 // FIXME: let offset depend on font
432 text(x + 3, y, str, font);
436 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
437 FontInfo const & font, Color back, Color frame, int offset)
439 int width, ascent, descent;
441 FontMetrics const & fm = theFontMetrics(font);
442 fm.buttonText(s, offset, width, ascent, descent);
444 int const d = offset / 2;
446 fillRectangle(x + d, baseline - ascent, width - offset,
447 ascent + descent, back);
448 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
449 text(x + offset, baseline, s, font);
453 int GuiPainter::preeditText(int x, int y, char_type c,
454 FontInfo const & font, preedit_style style)
456 FontInfo temp_font = font;
457 FontMetrics const & fm = theFontMetrics(font);
458 int ascent = fm.maxAscent();
459 int descent = fm.maxDescent();
460 int height = ascent + descent;
461 int width = fm.width(c);
464 case preedit_default:
465 // default unselecting mode.
466 fillRectangle(x, y - height + 1, width, height, Color_background);
467 dashedUnderline(font, x, y - descent + 1, width);
469 case preedit_selecting:
470 // We are in selecting mode: white text on black background.
471 fillRectangle(x, y - height + 1, width, height, Color_black);
472 temp_font.setColor(Color_white);
475 // The character comes with a cursor.
476 fillRectangle(x, y - height + 1, width, height, Color_background);
477 underline(font, x, y - descent + 1, width);
480 text(x, y - descent + 1, c, temp_font);
486 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
489 FontMetrics const & fm = theFontMetrics(f);
490 int const pos = fm.underlinePos();
492 line(x, y + pos, x + width, y + pos,
493 f.realColor(), ls, fm.lineWidth());
497 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
499 FontMetrics const & fm = theFontMetrics(f);
500 int const pos = fm.strikeoutPos();
502 line(x, y - pos, x + width, y - pos,
503 f.realColor(), line_solid, fm.lineWidth());
507 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
510 tmpf.setXout(FONT_OFF);
512 // the definition of \xout in ulem.sty is
513 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
514 // Let's mimic it somewhat.
515 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
516 for (int i = 0 ; i < iround(width / offset) ; ++i)
517 text(x + iround(i * offset), y, '/', tmpf);
521 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
523 FontMetrics const & fm = theFontMetrics(f);
524 int const pos1 = fm.underlinePos() + fm.lineWidth();
525 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
527 line(x, y + pos1, x + width, y + pos1,
528 f.realColor(), line_solid, fm.lineWidth());
529 line(x, y + pos2, x + width, y + pos2,
530 f.realColor(), line_solid, fm.lineWidth());
534 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
536 FontMetrics const & fm = theFontMetrics(f);
538 int const below = max(fm.maxDescent() / 2, 2);
539 int height = max((fm.maxDescent() / 4) - 1, 1);
544 for (int n = 0; n != height; ++n)
545 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
549 void GuiPainter::wavyHorizontalLine(FontInfo const & f, int x, int y, int width, ColorCode col)
551 FontMetrics const & fm = theFontMetrics(f);
552 int const pos = fm.underlinePos();
554 setQPainterPen(computeColor(col), line_solid, fm.lineWidth());
555 int const step = 2 * fm.lineWidth();
556 int const xend = x + width;
557 int height = 1 * fm.lineWidth();
558 //FIXME: I am not sure if Antialiasing gives the best effect.
559 //setRenderHint(Antialiasing, true);
560 QVector<QPoint> points;
563 points.append(QPoint(x, y + pos - height));
564 points.append(QPoint(x + step, y + pos + height));
566 points.append(QPoint(x, (qreal)y + pos + height));
567 points.append(QPoint(x + step/2, y + pos + height));
570 drawPolyline(points);
571 //setRenderHint(Antialiasing, false);
574 } // namespace frontend