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)
43 : QPainter(device), Painter(pixel_ratio)
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_min_.empty())
97 // map into [min,max] interval
98 QColor const & min = monochrome_min_.top();
99 QColor const & max = monochrome_max_.top();
101 qreal v = col.valueF();
102 v *= v; // make it a bit steeper (i.e. darker)
104 qreal minr, ming, minb;
105 qreal maxr, maxg, maxb;
106 min.getRgbF(&minr, &ming, &minb);
107 max.getRgbF(&maxr, &maxg, &maxb);
111 v * (minr - maxr) + maxr,
112 v * (ming - maxg) + maxg,
113 v * (minb - maxb) + maxb);
118 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
120 QColor qmin = filterColor(guiApp->colorCache().get(min));
121 QColor qmax = filterColor(guiApp->colorCache().get(max));
122 monochrome_min_.push(qmin);
123 monochrome_max_.push(qmax);
127 void GuiPainter::leaveMonochromeMode()
129 LASSERT(!monochrome_min_.empty(), return);
130 monochrome_min_.pop();
131 monochrome_max_.pop();
135 void GuiPainter::point(int x, int y, Color col)
137 setQPainterPen(computeColor(col));
142 void GuiPainter::line(int x1, int y1, int x2, int y2,
147 setQPainterPen(computeColor(col), ls, lw);
148 bool const do_antialiasing = renderHints() & TextAntialiasing
149 && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
150 setRenderHint(Antialiasing, do_antialiasing);
151 drawLine(x1, y1, x2, y2);
152 setRenderHint(Antialiasing, false);
156 void GuiPainter::lines(int const * xp, int const * yp, int np,
162 // double the size if needed
164 static QVector<QPoint> points(32);
165 if (np > points.size())
166 points.resize(2 * np);
168 // Note: the proper way to not get blurry vertical and horizontal lines is
169 // to add 0.5 to all coordinates.
170 bool antialias = false;
171 for (int i = 0; i < np; ++i) {
172 points[i].setX(xp[i]);
173 points[i].setY(yp[i]);
175 antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
177 QColor const color = computeColor(col);
178 setQPainterPen(color, ls, lw);
179 bool const text_is_antialiased = renderHints() & TextAntialiasing;
180 setRenderHint(Antialiasing,
181 antialias && text_is_antialiased && ls != line_solid_aliased);
182 if (fs == fill_none) {
183 drawPolyline(points.data(), np);
185 QBrush const oldbrush = brush();
186 setBrush(QBrush(color));
187 drawPolygon(points.data(), np, fs == fill_oddeven ?
188 Qt::OddEvenFill : Qt::WindingFill);
191 setRenderHint(Antialiasing, false);
195 void GuiPainter::path(int const * xp, int const * yp,
196 int const * c1x, int const * c1y,
197 int const * c2x, int const * c2y,
205 // This is the starting point, so its control points are meaningless
206 bpath.moveTo(xp[0], yp[0]);
208 for (int i = 1; i < np; ++i) {
209 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
210 c2x[i] == xp[i] && c2y[i] == yp[i];
212 bpath.lineTo(xp[i], yp[i]);
214 bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
216 QColor const color = computeColor(col);
217 setQPainterPen(color, ls, lw);
218 bool const text_is_antialiased = renderHints() & TextAntialiasing;
219 setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
222 fillPath(bpath, QBrush(color));
223 setRenderHint(Antialiasing, false);
227 void GuiPainter::rectangle(int x, int y, int w, int h,
232 setQPainterPen(computeColor(col), ls, lw);
233 drawRect(x, y, w, h);
237 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
239 fillRect(x, y, w, h, guiApp->colorCache().get(col));
243 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
244 int a1, int a2, Color col)
246 // LyX usings 1/64ths degree, Qt usings 1/16th
247 setQPainterPen(computeColor(col));
248 bool const do_antialiasing = renderHints() & TextAntialiasing;
249 setRenderHint(Antialiasing, do_antialiasing);
250 drawArc(x, y, w, h, a1 / 4, a2 / 4);
251 setRenderHint(Antialiasing, false);
255 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
257 graphics::GuiImage const & qlimage =
258 static_cast<graphics::GuiImage const &>(i);
260 fillRectangle(x, y, w, h, Color_graphicsbg);
262 QImage const image = qlimage.image();
263 QRectF const drect = QRectF(x, y, w, h);
264 QRectF const srect = QRectF(0, 0, image.width(), image.height());
265 // Bilinear filtering is needed on a rare occasion for instant previews when
266 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
267 // This filter is optimised by qt on pixel-aligned images, so this does not
268 // affect performances in other cases.
269 setRenderHint(SmoothPixmapTransform);
270 drawImage(drect, image, srect);
271 setRenderHint(SmoothPixmapTransform, false);
275 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
277 text(x, y, docstring(1, c), f);
281 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
283 text(x, y, s, f, Auto, 0.0, 0.0);
287 void GuiPainter::text(int x, int y, docstring const & s,
288 FontInfo const & f, Direction const dir,
289 double const wordspacing, double const tw)
291 //LYXERR0("text: x=" << x << ", s=" << s);
295 /* Caution: The following ucs4 to QString conversions work for symbol fonts
296 only because they are no real conversions but simple casts in reality.
297 When we want to draw a symbol or calculate the metrics we pass the position
298 of the symbol in the font (as given in lib/symbols) as a char_type to the
299 frontend. This is just wrong, because the symbol is no UCS4 character at
300 all. You can think of this number as the code point of the symbol in a
301 custom symbol encoding. It works because this char_type is later on again
302 interpreted as a position in the font.
303 The correct solution would be to have extra functions for symbols, but that
304 would require to duplicate a lot of frontend and mathed support code.
306 QString str = toqstr(s);
309 // HACK: QT3 refuses to show single compose characters
310 // Still needed with Qt4?
311 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
315 QFont ff = getFont(f);
316 ff.setWordSpacing(wordspacing);
317 GuiFontMetrics const & fm = getFontMetrics(f);
321 // Take into account space stretching (word spacing)
322 textwidth = fm.width(s) +
323 static_cast<int>(fm.countExpanders(s) * wordspacing);
325 textwidth = static_cast<int>(tw);
327 textDecoration(f, x, y, textwidth);
329 setQPainterPen(computeColor(f.realColor()));
331 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
332 QTextLine const & tline = ptl->lineForTextPosition(0);
333 ptl->draw(this, QPointF(x, y - tline.ascent()));
339 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
340 // << " at " << x << "," << y);
344 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
345 double const wordspacing, double const tw)
347 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
352 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
353 Color other, size_type const from, size_type const to,
354 double const wordspacing, double const tw)
356 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
357 FontInfo fi = f.fontInfo();
358 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
361 int const ascent = fm.maxAscent();
362 int const height = fm.maxAscent() + fm.maxDescent();
363 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
364 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
365 // Avoid this case, since it would make the `other' text spill in some cases
367 text(x, y, str, fi, dir, wordspacing, tw);
369 } else if (xmin > xmax)
372 // First the part in other color
373 Color const orig = fi.realColor();
374 fi.setPaintColor(other);
375 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
377 text(x, y, str, fi, dir, wordspacing, tw);
379 // Then the part in normal color
380 // Note that in Qt5, it is not possible to use Qt::UniteClip,
381 // therefore QRegion is used.
382 fi.setPaintColor(orig);
383 QRegion region(viewport());
384 setClipRegion(region - clip);
385 text(x, y, str, fi, dir, wordspacing, tw);
390 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
392 if (f.underbar() == FONT_ON)
393 underline(f, x, y, width);
394 if (f.strikeout() == FONT_ON)
395 strikeoutLine(f, x, y, width);
396 if (f.xout() == FONT_ON)
397 crossoutLines(f, x, y, width);
398 if (f.uuline() == FONT_ON)
399 doubleUnderline(f, x, y, width);
400 if (f.uwave() == FONT_ON)
401 // f.color() doesn't work on some circumstances
402 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
406 static int max(int a, int b) { return a > b ? a : b; }
409 void GuiPainter::rectText(int x, int y, docstring const & str,
410 FontInfo const & font, Color back, Color frame)
412 int width, ascent, descent;
414 FontMetrics const & fm = theFontMetrics(font);
415 fm.rectText(str, width, ascent, descent);
417 if (back != Color_none)
418 fillRectangle(x + 1, y - ascent + 1, width - 1,
419 ascent + descent - 1, back);
421 if (frame != Color_none)
422 rectangle(x, y - ascent, width, ascent + descent, frame);
424 // FIXME: let offset depend on font
425 text(x + 3, y, str, font);
429 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
430 FontInfo const & font, Color back, Color frame, int offset)
432 int width, ascent, descent;
434 FontMetrics const & fm = theFontMetrics(font);
435 fm.buttonText(s, offset, width, ascent, descent);
437 static int const d = offset / 2;
439 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
440 ascent + descent - 1, back);
441 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
442 text(x + offset, baseline, s, font);
446 int GuiPainter::preeditText(int x, int y, char_type c,
447 FontInfo const & font, preedit_style style)
449 FontInfo temp_font = font;
450 FontMetrics const & fm = theFontMetrics(font);
451 int ascent = fm.maxAscent();
452 int descent = fm.maxDescent();
453 int height = ascent + descent;
454 int width = fm.width(c);
457 case preedit_default:
458 // default unselecting mode.
459 fillRectangle(x, y - height + 1, width, height, Color_background);
460 dashedUnderline(font, x, y - descent + 1, width);
462 case preedit_selecting:
463 // We are in selecting mode: white text on black background.
464 fillRectangle(x, y - height + 1, width, height, Color_black);
465 temp_font.setColor(Color_white);
468 // The character comes with a cursor.
469 fillRectangle(x, y - height + 1, width, height, Color_background);
470 underline(font, x, y - descent + 1, width);
473 text(x, y - descent + 1, c, temp_font);
479 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
482 FontMetrics const & fm = theFontMetrics(f);
483 int const pos = fm.underlinePos();
485 line(x, y + pos, x + width, y + pos,
486 f.realColor(), ls, fm.lineWidth());
490 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
492 FontMetrics const & fm = theFontMetrics(f);
493 int const pos = fm.strikeoutPos();
495 line(x, y - pos, x + width, y - pos,
496 f.realColor(), line_solid, fm.lineWidth());
500 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
503 tmpf.setXout(FONT_OFF);
505 // the definition of \xout in ulem.sty is
506 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
507 // Let's mimick it somewhat.
508 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
509 for (int i = 0 ; i < iround(width / offset) ; ++i)
510 text(x + iround(i * offset), y, '/', tmpf);
514 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
516 FontMetrics const & fm = theFontMetrics(f);
517 int const pos1 = fm.underlinePos() + fm.lineWidth();
518 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
520 line(x, y + pos1, x + width, y + pos1,
521 f.realColor(), line_solid, fm.lineWidth());
522 line(x, y + pos2, x + width, y + pos2,
523 f.realColor(), line_solid, fm.lineWidth());
527 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
529 FontMetrics const & fm = theFontMetrics(f);
531 int const below = max(fm.maxDescent() / 2, 2);
532 int height = max((fm.maxDescent() / 4) - 1, 1);
537 for (int n = 0; n != height; ++n)
538 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
542 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
544 setQPainterPen(computeColor(col));
546 int const xend = x + width;
548 //FIXME: I am not sure if Antialiasing gives the best effect.
549 //setRenderHint(Antialiasing, true);
552 drawLine(x, y - height, x + step, y + height);
554 drawLine(x, y + height, x + step/2, y + height);
557 //setRenderHint(Antialiasing, false);
560 } // namespace frontend