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);
368 // First the part in other color
369 Color const orig = fi.realColor();
370 fi.setPaintColor(other);
371 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
373 text(x, y, str, fi, dir, wordspacing, tw);
375 // Then the part in normal color
376 // Note that in Qt5, it is not possible to use Qt::UniteClip,
377 // therefore QRegion is used.
378 fi.setPaintColor(orig);
379 QRegion region(viewport());
380 setClipRegion(region - clip);
381 text(x, y, str, fi, dir, wordspacing, tw);
386 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
388 if (f.underbar() == FONT_ON)
389 underline(f, x, y, width);
390 if (f.strikeout() == FONT_ON)
391 strikeoutLine(f, x, y, width);
392 if (f.xout() == FONT_ON)
393 crossoutLines(f, x, y, width);
394 if (f.uuline() == FONT_ON)
395 doubleUnderline(f, x, y, width);
396 if (f.uwave() == FONT_ON)
397 // f.color() doesn't work on some circumstances
398 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
402 static int max(int a, int b) { return a > b ? a : b; }
405 void GuiPainter::rectText(int x, int y, docstring const & str,
406 FontInfo const & font, Color back, Color frame)
408 int width, ascent, descent;
410 FontMetrics const & fm = theFontMetrics(font);
411 fm.rectText(str, width, ascent, descent);
413 if (back != Color_none)
414 fillRectangle(x + 1, y - ascent + 1, width - 1,
415 ascent + descent - 1, back);
417 if (frame != Color_none)
418 rectangle(x, y - ascent, width, ascent + descent, frame);
420 // FIXME: let offset depend on font
421 text(x + 3, y, str, font);
425 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
426 FontInfo const & font, Color back, Color frame, int offset)
428 int width, ascent, descent;
430 FontMetrics const & fm = theFontMetrics(font);
431 fm.buttonText(s, offset, width, ascent, descent);
433 static int const d = offset / 2;
435 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
436 ascent + descent - 1, back);
437 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
438 text(x + offset, baseline, s, font);
442 int GuiPainter::preeditText(int x, int y, char_type c,
443 FontInfo const & font, preedit_style style)
445 FontInfo temp_font = font;
446 FontMetrics const & fm = theFontMetrics(font);
447 int ascent = fm.maxAscent();
448 int descent = fm.maxDescent();
449 int height = ascent + descent;
450 int width = fm.width(c);
453 case preedit_default:
454 // default unselecting mode.
455 fillRectangle(x, y - height + 1, width, height, Color_background);
456 dashedUnderline(font, x, y - descent + 1, width);
458 case preedit_selecting:
459 // We are in selecting mode: white text on black background.
460 fillRectangle(x, y - height + 1, width, height, Color_black);
461 temp_font.setColor(Color_white);
464 // The character comes with a cursor.
465 fillRectangle(x, y - height + 1, width, height, Color_background);
466 underline(font, x, y - descent + 1, width);
469 text(x, y - descent + 1, c, temp_font);
475 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
478 FontMetrics const & fm = theFontMetrics(f);
479 int const pos = fm.underlinePos();
481 line(x, y + pos, x + width, y + pos,
482 f.realColor(), ls, fm.lineWidth());
486 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
488 FontMetrics const & fm = theFontMetrics(f);
489 int const pos = fm.strikeoutPos();
491 line(x, y - pos, x + width, y - pos,
492 f.realColor(), line_solid, fm.lineWidth());
496 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
499 tmpf.setXout(FONT_OFF);
501 // the definition of \xout in ulem.sty is
502 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
503 // Let's mimick it somewhat.
504 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
505 for (int i = 0 ; i < iround(width / offset) ; ++i)
506 text(x + iround(i * offset), y, '/', tmpf);
510 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
512 FontMetrics const & fm = theFontMetrics(f);
513 int const pos1 = fm.underlinePos() + fm.lineWidth();
514 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
516 line(x, y + pos1, x + width, y + pos1,
517 f.realColor(), line_solid, fm.lineWidth());
518 line(x, y + pos2, x + width, y + pos2,
519 f.realColor(), line_solid, fm.lineWidth());
523 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
525 FontMetrics const & fm = theFontMetrics(f);
527 int const below = max(fm.maxDescent() / 2, 2);
528 int height = max((fm.maxDescent() / 4) - 1, 1);
533 for (int n = 0; n != height; ++n)
534 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
538 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
540 setQPainterPen(computeColor(col));
542 int const xend = x + width;
544 //FIXME: I am not sure if Antialiasing gives the best effect.
545 //setRenderHint(Antialiasing, true);
548 drawLine(x, y - height, x + step, y + height);
550 drawLine(x, y + height, x + step/2, y + height);
553 //setRenderHint(Antialiasing, false);
556 } // namespace frontend