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, bool const darkmode)
238 graphics::GuiImage const & qlimage =
239 static_cast<graphics::GuiImage const &>(i);
241 fillRectangle(x, y, w, h, Color_graphicsbg);
243 QImage image = qlimage.image();
245 QPalette palette = QPalette();
246 QColor text_color = palette.color(QPalette::Active, QPalette::WindowText);
247 QColor bg_color = palette.color(QPalette::Active, QPalette::Window);
248 // guess whether we are in dark mode
249 if (darkmode && text_color.black() < bg_color.black())
250 // FIXME this is only a cheap approximation
251 // Ideally, replace colors as in GuiApplication::prepareForDarkmode()
252 image.invertPixels();
254 QRectF const drect = QRectF(x, y, w, h);
255 QRectF const srect = QRectF(0, 0, image.width(), image.height());
256 // Bilinear filtering is needed on a rare occasion for instant previews when
257 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
258 // This filter is optimised by qt on pixel-aligned images, so this does not
259 // affect performances in other cases.
260 setRenderHint(SmoothPixmapTransform);
261 drawImage(drect, image, srect);
262 setRenderHint(SmoothPixmapTransform, false);
266 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
268 text(x, y, docstring(1, c), f);
272 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
274 text(x, y, s, f, Auto, 0.0, 0.0);
278 void GuiPainter::text(int x, int y, docstring const & s,
279 FontInfo const & f, Direction const dir,
280 double const wordspacing, double const tw)
282 //LYXERR0("text: x=" << x << ", s=" << s);
286 /* Caution: The following ucs4 to QString conversions work for symbol fonts
287 only because they are no real conversions but simple casts in reality.
288 When we want to draw a symbol or calculate the metrics we pass the position
289 of the symbol in the font (as given in lib/symbols) as a char_type to the
290 frontend. This is just wrong, because the symbol is no UCS4 character at
291 all. You can think of this number as the code point of the symbol in a
292 custom symbol encoding. It works because this char_type is later on again
293 interpreted as a position in the font.
294 The correct solution would be to have extra functions for symbols, but that
295 would require to duplicate a lot of frontend and mathed support code.
297 QString str = toqstr(s);
300 // HACK: QT3 refuses to show single compose characters
301 // Still needed with Qt4?
302 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
306 QFont ff = getFont(f);
307 ff.setWordSpacing(wordspacing);
308 GuiFontMetrics const & fm = getFontMetrics(f);
312 // Take into account space stretching (word spacing)
313 textwidth = fm.width(s) +
314 static_cast<int>(fm.countExpanders(s) * wordspacing);
316 textwidth = static_cast<int>(tw);
318 textDecoration(f, x, y, textwidth);
320 setQPainterPen(computeColor(f.realColor()));
322 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
323 QTextLine const & tline = ptl->lineForTextPosition(0);
324 ptl->draw(this, QPointF(x, y - tline.ascent()));
330 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
331 // << " at " << x << "," << y);
335 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
336 double const wordspacing, double const tw)
338 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
343 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
344 Color other, size_type const from, size_type const to,
345 double const wordspacing, double const tw)
347 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
348 FontInfo fi = f.fontInfo();
349 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
352 int const ascent = fm.maxAscent();
353 int const height = fm.maxAscent() + fm.maxDescent();
354 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
355 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
356 // Avoid this case, since it would make the `other' text spill in some cases
358 text(x, y, str, fi, dir, wordspacing, tw);
360 } else if (xmin > xmax)
363 // First the part in other color
364 Color const orig = fi.realColor();
365 fi.setPaintColor(other);
366 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
368 text(x, y, str, fi, dir, wordspacing, tw);
370 // Then the part in normal color
371 // Note that in Qt5, it is not possible to use Qt::UniteClip,
372 // therefore QRegion is used.
373 fi.setPaintColor(orig);
374 QRegion region(viewport());
375 setClipRegion(region - clip);
376 text(x, y, str, fi, dir, wordspacing, tw);
381 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
383 if (f.underbar() == FONT_ON)
384 underline(f, x, y, width);
385 if (f.strikeout() == FONT_ON)
386 strikeoutLine(f, x, y, width);
387 if (f.xout() == FONT_ON)
388 crossoutLines(f, x, y, width);
389 if (f.uuline() == FONT_ON)
390 doubleUnderline(f, x, y, width);
391 if (f.uwave() == FONT_ON)
392 // f.color() doesn't work on some circumstances
393 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
397 static int max(int a, int b) { return a > b ? a : b; }
400 void GuiPainter::rectText(int x, int y, docstring const & str,
401 FontInfo const & font, Color back, Color frame)
403 int width, ascent, descent;
405 FontMetrics const & fm = theFontMetrics(font);
406 fm.rectText(str, width, ascent, descent);
408 if (back != Color_none)
409 fillRectangle(x + 1, y - ascent + 1, width - 1,
410 ascent + descent - 1, back);
412 if (frame != Color_none)
413 rectangle(x, y - ascent, width, ascent + descent, frame);
415 // FIXME: let offset depend on font
416 text(x + 3, y, str, font);
420 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
421 FontInfo const & font, Color back, Color frame, int offset)
423 int width, ascent, descent;
425 FontMetrics const & fm = theFontMetrics(font);
426 fm.buttonText(s, offset, width, ascent, descent);
428 int const d = offset / 2;
430 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
431 ascent + descent - 1, back);
432 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
433 text(x + offset, baseline, s, font);
437 int GuiPainter::preeditText(int x, int y, char_type c,
438 FontInfo const & font, preedit_style style)
440 FontInfo temp_font = font;
441 FontMetrics const & fm = theFontMetrics(font);
442 int ascent = fm.maxAscent();
443 int descent = fm.maxDescent();
444 int height = ascent + descent;
445 int width = fm.width(c);
448 case preedit_default:
449 // default unselecting mode.
450 fillRectangle(x, y - height + 1, width, height, Color_background);
451 dashedUnderline(font, x, y - descent + 1, width);
453 case preedit_selecting:
454 // We are in selecting mode: white text on black background.
455 fillRectangle(x, y - height + 1, width, height, Color_black);
456 temp_font.setColor(Color_white);
459 // The character comes with a cursor.
460 fillRectangle(x, y - height + 1, width, height, Color_background);
461 underline(font, x, y - descent + 1, width);
464 text(x, y - descent + 1, c, temp_font);
470 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
473 FontMetrics const & fm = theFontMetrics(f);
474 int const pos = fm.underlinePos();
476 line(x, y + pos, x + width, y + pos,
477 f.realColor(), ls, fm.lineWidth());
481 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
483 FontMetrics const & fm = theFontMetrics(f);
484 int const pos = fm.strikeoutPos();
486 line(x, y - pos, x + width, y - pos,
487 f.realColor(), line_solid, fm.lineWidth());
491 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
494 tmpf.setXout(FONT_OFF);
496 // the definition of \xout in ulem.sty is
497 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
498 // Let's mimic it somewhat.
499 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
500 for (int i = 0 ; i < iround(width / offset) ; ++i)
501 text(x + iround(i * offset), y, '/', tmpf);
505 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
507 FontMetrics const & fm = theFontMetrics(f);
508 int const pos1 = fm.underlinePos() + fm.lineWidth();
509 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
511 line(x, y + pos1, x + width, y + pos1,
512 f.realColor(), line_solid, fm.lineWidth());
513 line(x, y + pos2, x + width, y + pos2,
514 f.realColor(), line_solid, fm.lineWidth());
518 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
520 FontMetrics const & fm = theFontMetrics(f);
522 int const below = max(fm.maxDescent() / 2, 2);
523 int height = max((fm.maxDescent() / 4) - 1, 1);
528 for (int n = 0; n != height; ++n)
529 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
533 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
535 setQPainterPen(computeColor(col));
537 int const xend = x + width;
539 //FIXME: I am not sure if Antialiasing gives the best effect.
540 //setRenderHint(Antialiasing, true);
543 drawLine(x, y - height, x + step, y + height);
545 drawLine(x, y + height, x + step/2, y + height);
548 //setRenderHint(Antialiasing, false);
551 } // namespace frontend