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);
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>(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(f, 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, baseline - ascent, width - offset,
450 ascent + descent, 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(FontInfo const & f, int x, int y, int width, ColorCode col)
554 FontMetrics const & fm = theFontMetrics(f);
555 int const pos = fm.underlinePos();
557 setQPainterPen(computeColor(col), line_solid, fm.lineWidth());
558 int const step = 2 * fm.lineWidth();
559 int const xend = x + width;
560 int height = 1 * fm.lineWidth();
561 //FIXME: I am not sure if Antialiasing gives the best effect.
562 //setRenderHint(Antialiasing, true);
563 QVector<QPoint> points;
566 points.append(QPoint(x, y + pos - height));
567 points.append(QPoint(x + step, y + pos + height));
569 points.append(QPoint(x, (qreal)y + pos + height));
570 points.append(QPoint(x + step/2, y + pos + height));
573 drawPolyline(points);
574 //setRenderHint(Antialiasing, false);
577 } // namespace frontend