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, Qt::PenJoinStyle js)
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;
88 QColor GuiPainter::computeColor(Color col)
90 return filterColor(guiApp->colorCache().get(col));
94 QColor GuiPainter::filterColor(QColor const & col)
96 if (monochrome_blend_.empty())
99 QColor const blend = monochrome_blend_.top();
100 return QColor::fromHsv(blend.hue(), blend.saturation(), qGray(col.rgb()));
104 void GuiPainter::enterMonochromeMode(Color const & blend)
106 QColor qblend = filterColor(guiApp->colorCache().get(blend));
107 monochrome_blend_.push(qblend);
111 void GuiPainter::leaveMonochromeMode()
113 LASSERT(!monochrome_blend_.empty(), return);
114 monochrome_blend_.pop();
118 void GuiPainter::point(int x, int y, Color col)
120 setQPainterPen(computeColor(col));
125 void GuiPainter::line(int x1, int y1, int x2, int y2,
130 setQPainterPen(computeColor(col), ls, lw);
131 bool const do_antialiasing = renderHints() & TextAntialiasing
132 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
133 setRenderHint(Antialiasing, do_antialiasing);
134 drawLine(x1, y1, x2, y2);
135 setRenderHint(Antialiasing, false);
139 void GuiPainter::lines(int const * xp, int const * yp, int np,
145 // double the size if needed
147 static QVector<QPoint> points(32);
148 if (np > points.size())
149 points.resize(2 * np);
151 // Note: the proper way to not get blurry vertical and horizontal lines is
152 // to add 0.5 to all coordinates.
153 bool antialias = false;
154 for (int i = 0; i < np; ++i) {
155 points[i].setX(xp[i]);
156 points[i].setY(yp[i]);
158 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
160 QColor const color = computeColor(col);
161 setQPainterPen(color, ls, lw);
162 bool const text_is_antialiased = renderHints() & TextAntialiasing;
163 setRenderHint(Antialiasing,
164 antialias && text_is_antialiased && ls != line_solid_aliased);
165 if (fs == fill_none) {
166 drawPolyline(points.data(), np);
168 QBrush const oldbrush = brush();
169 setBrush(QBrush(color));
170 drawPolygon(points.data(), np, fs == fill_oddeven ?
171 Qt::OddEvenFill : Qt::WindingFill);
174 setRenderHint(Antialiasing, false);
178 void GuiPainter::path(int const * xp, int const * yp,
179 int const * c1x, int const * c1y,
180 int const * c2x, int const * c2y,
188 // This is the starting point, so its control points are meaningless
189 bpath.moveTo(xp[0], yp[0]);
191 for (int i = 1; i < np; ++i) {
192 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
193 c2x[i] == xp[i] && c2y[i] == yp[i];
195 bpath.lineTo(xp[i], yp[i]);
197 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
199 QColor const color = computeColor(col);
200 setQPainterPen(color, ls, lw);
201 bool const text_is_antialiased = renderHints() & TextAntialiasing;
202 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
205 fillPath(bpath, QBrush(color));
206 setRenderHint(Antialiasing, false);
210 void GuiPainter::rectangle(int x, int y, int w, int h,
215 setQPainterPen(computeColor(col), ls, lw, Qt::MiterJoin);
216 drawRect(x, y, w, h);
220 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
222 fillRect(x, y, w, h, guiApp->colorCache().get(col));
226 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
227 int a1, int a2, Color col)
229 // LyX usings 1/64ths degree, Qt usings 1/16th
230 setQPainterPen(computeColor(col));
231 bool const do_antialiasing = renderHints() & TextAntialiasing;
232 setRenderHint(Antialiasing, do_antialiasing);
233 drawArc(x, y, w, h, a1 / 4, a2 / 4);
234 setRenderHint(Antialiasing, false);
238 void GuiPainter::ellipse(double x, double y, double rx, double ry,
239 Color col, fill_style fs, line_style ls, int lw)
241 QColor const color = computeColor(col);
242 setQPainterPen(color, ls, lw);
243 bool const do_antialiasing = renderHints() & TextAntialiasing;
244 setRenderHint(Antialiasing, do_antialiasing);
245 if (fs == fill_none) {
246 drawEllipse(QPointF(x, y), rx, ry);
248 QBrush const oldbrush = brush();
249 setBrush(QBrush(color));
250 drawEllipse(QPointF(x, y), rx, ry);
253 setRenderHint(Antialiasing, false);
257 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i,
258 bool const revert_in_darkmode)
260 graphics::GuiImage const & qlimage =
261 static_cast<graphics::GuiImage const &>(i);
263 fillRectangle(x, y, w, h, Color_graphicsbg);
265 QImage image = qlimage.image();
267 if (revert_in_darkmode && guiApp && guiApp->colorCache().isDarkMode())
268 // FIXME this is only a cheap approximation
269 // Ideally, replace colors as in GuiApplication::prepareForDarkmode()
270 image.invertPixels();
272 QRectF const drect = QRectF(x, y, w, h);
273 QRectF const srect = QRectF(0, 0, image.width(), image.height());
274 // Bilinear filtering is needed on a rare occasion for instant previews when
275 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
276 // This filter is optimised by qt on pixel-aligned images, so this does not
277 // affect performances in other cases.
278 setRenderHint(SmoothPixmapTransform);
279 drawImage(drect, image, srect);
280 setRenderHint(SmoothPixmapTransform, false);
284 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
286 text(x, y, docstring(1, c), f);
290 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
292 text(x, y, s, f, Auto, 0.0, 0.0);
296 void GuiPainter::text(int x, int y, docstring const & s,
297 FontInfo const & f, Direction const dir,
298 double const wordspacing, double const tw)
300 //LYXERR0("text: x=" << x << ", s=" << s);
304 /* Caution: The following ucs4 to QString conversions work for symbol fonts
305 only because they are no real conversions but simple casts in reality.
306 When we want to draw a symbol or calculate the metrics we pass the position
307 of the symbol in the font (as given in lib/symbols) as a char_type to the
308 frontend. This is just wrong, because the symbol is no UCS4 character at
309 all. You can think of this number as the code point of the symbol in a
310 custom symbol encoding. It works because this char_type is later on again
311 interpreted as a position in the font.
312 The correct solution would be to have extra functions for symbols, but that
313 would require to duplicate a lot of frontend and mathed support code.
315 QString str = toqstr(s);
318 // HACK: QT3 refuses to show single compose characters
319 // Still needed with Qt4?
320 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
324 QFont ff = getFont(f);
325 ff.setWordSpacing(wordspacing);
326 GuiFontMetrics const & fm = getFontMetrics(f);
330 // Take into account space stretching (word spacing)
331 textwidth = fm.width(s) +
332 static_cast<int>(fm.countExpanders(s) * wordspacing);
334 textwidth = static_cast<int>(tw);
336 textDecoration(f, x, y, textwidth);
338 setQPainterPen(computeColor(f.realColor()));
340 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
341 QTextLine const & tline = ptl->lineForTextPosition(0);
342 ptl->draw(this, QPointF(x, y - tline.ascent()));
348 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
349 // << " at " << x << "," << y);
353 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
354 double const wordspacing, double const tw)
356 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
361 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
362 Color other, size_type const from, size_type const to,
363 double const wordspacing, double const tw)
365 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
366 FontInfo fi = f.fontInfo();
367 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
370 int const ascent = fm.maxAscent();
371 int const height = fm.maxAscent() + fm.maxDescent();
372 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
373 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
374 // Avoid this case, since it would make the `other' text spill in some cases
376 text(x, y, str, fi, dir, wordspacing, tw);
378 } else if (xmin > xmax)
381 // First the part in other color
382 Color const orig = fi.realColor();
383 fi.setPaintColor(other);
384 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
386 text(x, y, str, fi, dir, wordspacing, tw);
388 // Then the part in normal color
389 // Note that in Qt5, it is not possible to use Qt::UniteClip,
390 // therefore QRegion is used.
391 fi.setPaintColor(orig);
392 QRegion region(viewport());
393 setClipRegion(region - clip);
394 text(x, y, str, fi, dir, wordspacing, tw);
399 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
401 if (f.underbar() == FONT_ON)
402 underline(f, x, y, width);
403 if (f.strikeout() == FONT_ON)
404 strikeoutLine(f, x, y, width);
405 if (f.xout() == FONT_ON)
406 crossoutLines(f, x, y, width);
407 if (f.uuline() == FONT_ON)
408 doubleUnderline(f, x, y, width);
409 if (f.uwave() == FONT_ON)
410 // f.color() doesn't work on some circumstances
411 wavyHorizontalLine(f, x, y, width, f.realColor().baseColor);
415 static int max(int a, int b) { return a > b ? a : b; }
418 void GuiPainter::rectText(int x, int y, docstring const & str,
419 FontInfo const & font, Color back, Color frame)
421 int width, ascent, descent;
423 FontMetrics const & fm = theFontMetrics(font);
424 fm.rectText(str, width, ascent, descent);
426 if (back != Color_none)
427 fillRectangle(x + 1, y - ascent + 1, width - 1,
428 ascent + descent - 1, back);
430 if (frame != Color_none)
431 rectangle(x, y - ascent, width, ascent + descent, frame);
433 // FIXME: let offset depend on font
434 text(x + 3, y, str, font);
438 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
439 FontInfo const & font, Color back, Color frame, int offset)
441 int width, ascent, descent;
443 FontMetrics const & fm = theFontMetrics(font);
444 fm.buttonText(s, offset, width, ascent, descent);
446 int const d = offset / 2;
448 fillRectangle(x + d, baseline - ascent, width - offset,
449 ascent + descent, back);
450 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
451 text(x + offset, baseline, s, font);
455 int GuiPainter::preeditText(int x, int y, char_type c,
456 FontInfo const & font, preedit_style style)
458 FontInfo temp_font = font;
459 FontMetrics const & fm = theFontMetrics(font);
460 int ascent = fm.maxAscent();
461 int descent = fm.maxDescent();
462 int height = ascent + descent;
463 int width = fm.width(c);
466 case preedit_default:
467 // default unselecting mode.
468 fillRectangle(x, y - height + 1, width, height, Color_background);
469 dashedUnderline(font, x, y - descent + 1, width);
471 case preedit_selecting:
472 // We are in selecting mode: white text on black background.
473 fillRectangle(x, y - height + 1, width, height, Color_black);
474 temp_font.setColor(Color_white);
477 // The character comes with a cursor.
478 fillRectangle(x, y - height + 1, width, height, Color_background);
479 underline(font, x, y - descent + 1, width);
482 text(x, y - descent + 1, c, temp_font);
488 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
491 FontMetrics const & fm = theFontMetrics(f);
492 int const pos = fm.underlinePos();
494 line(x, y + pos, x + width, y + pos,
495 f.realColor(), ls, fm.lineWidth());
499 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
501 FontMetrics const & fm = theFontMetrics(f);
502 int const pos = fm.strikeoutPos();
504 line(x, y - pos, x + width, y - pos,
505 f.realColor(), line_solid, fm.lineWidth());
509 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
512 tmpf.setXout(FONT_OFF);
514 // the definition of \xout in ulem.sty is
515 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
516 // Let's mimic it somewhat.
517 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
518 for (int i = 0 ; i < iround(width / offset) ; ++i)
519 text(x + iround(i * offset), y, '/', tmpf);
523 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
525 FontMetrics const & fm = theFontMetrics(f);
526 int const pos1 = fm.underlinePos() + fm.lineWidth();
527 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
529 line(x, y + pos1, x + width, y + pos1,
530 f.realColor(), line_solid, fm.lineWidth());
531 line(x, y + pos2, x + width, y + pos2,
532 f.realColor(), line_solid, fm.lineWidth());
536 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
538 FontMetrics const & fm = theFontMetrics(f);
540 int const below = max(fm.maxDescent() / 2, 2);
541 int height = max((fm.maxDescent() / 4) - 1, 1);
546 for (int n = 0; n != height; ++n)
547 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
551 void GuiPainter::wavyHorizontalLine(FontInfo const & f, int x, int y, int width, ColorCode col)
553 FontMetrics const & fm = theFontMetrics(f);
554 int const pos = fm.underlinePos();
556 setQPainterPen(computeColor(col), line_solid, fm.lineWidth());
557 int const step = 2 * fm.lineWidth();
558 int const xend = x + width;
559 int height = 1 * fm.lineWidth();
560 //FIXME: I am not sure if Antialiasing gives the best effect.
561 //setRenderHint(Antialiasing, true);
562 QVector<QPoint> points;
565 points.append(QPoint(x, y + pos - height));
566 points.append(QPoint(x + step, y + pos + height));
568 points.append(QPoint(x, (qreal)y + pos + height));
569 points.append(QPoint(x + step/2, y + pos + height));
572 drawPolyline(points);
573 //setRenderHint(Antialiasing, false);
576 } // namespace frontend