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)
238 graphics::GuiImage const & qlimage =
239 static_cast<graphics::GuiImage const &>(i);
241 fillRectangle(x, y, w, h, Color_graphicsbg);
243 QImage const & image = qlimage.image();
244 QRectF const drect = QRectF(x, y, w, h);
245 QRectF const srect = QRectF(0, 0, image.width(), image.height());
246 // Bilinear filtering is needed on a rare occasion for instant previews when
247 // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
248 // This filter is optimised by qt on pixel-aligned images, so this does not
249 // affect performances in other cases.
250 setRenderHint(SmoothPixmapTransform);
251 drawImage(drect, image, srect);
252 setRenderHint(SmoothPixmapTransform, false);
256 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
258 text(x, y, docstring(1, c), f);
262 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
264 text(x, y, s, f, Auto, 0.0, 0.0);
268 void GuiPainter::text(int x, int y, docstring const & s,
269 FontInfo const & f, Direction const dir,
270 double const wordspacing, double const tw)
272 //LYXERR0("text: x=" << x << ", s=" << s);
276 /* Caution: The following ucs4 to QString conversions work for symbol fonts
277 only because they are no real conversions but simple casts in reality.
278 When we want to draw a symbol or calculate the metrics we pass the position
279 of the symbol in the font (as given in lib/symbols) as a char_type to the
280 frontend. This is just wrong, because the symbol is no UCS4 character at
281 all. You can think of this number as the code point of the symbol in a
282 custom symbol encoding. It works because this char_type is later on again
283 interpreted as a position in the font.
284 The correct solution would be to have extra functions for symbols, but that
285 would require to duplicate a lot of frontend and mathed support code.
287 QString str = toqstr(s);
290 // HACK: QT3 refuses to show single compose characters
291 // Still needed with Qt4?
292 if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
296 QFont ff = getFont(f);
297 ff.setWordSpacing(wordspacing);
298 GuiFontMetrics const & fm = getFontMetrics(f);
302 // Take into account space stretching (word spacing)
303 textwidth = fm.width(s) +
304 static_cast<int>(fm.countExpanders(s) * wordspacing);
306 textwidth = static_cast<int>(tw);
308 textDecoration(f, x, y, textwidth);
310 setQPainterPen(computeColor(f.realColor()));
312 auto ptl = fm.getTextLayout(s, dir == RtL, wordspacing);
313 QTextLine const & tline = ptl->lineForTextPosition(0);
314 ptl->draw(this, QPointF(x, y - tline.ascent()));
320 //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
321 // << " at " << x << "," << y);
325 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
326 double const wordspacing, double const tw)
328 text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
333 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
334 Color other, size_type const from, size_type const to,
335 double const wordspacing, double const tw)
337 GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
338 FontInfo fi = f.fontInfo();
339 Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
342 int const ascent = fm.maxAscent();
343 int const height = fm.maxAscent() + fm.maxDescent();
344 int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
345 int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
346 // Avoid this case, since it would make the `other' text spill in some cases
348 text(x, y, str, fi, dir, wordspacing, tw);
350 } else if (xmin > xmax)
353 // First the part in other color
354 Color const orig = fi.realColor();
355 fi.setPaintColor(other);
356 QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
358 text(x, y, str, fi, dir, wordspacing, tw);
360 // Then the part in normal color
361 // Note that in Qt5, it is not possible to use Qt::UniteClip,
362 // therefore QRegion is used.
363 fi.setPaintColor(orig);
364 QRegion region(viewport());
365 setClipRegion(region - clip);
366 text(x, y, str, fi, dir, wordspacing, tw);
371 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
373 if (f.underbar() == FONT_ON)
374 underline(f, x, y, width);
375 if (f.strikeout() == FONT_ON)
376 strikeoutLine(f, x, y, width);
377 if (f.xout() == FONT_ON)
378 crossoutLines(f, x, y, width);
379 if (f.uuline() == FONT_ON)
380 doubleUnderline(f, x, y, width);
381 if (f.uwave() == FONT_ON)
382 // f.color() doesn't work on some circumstances
383 wavyHorizontalLine(x, y, width, f.realColor().baseColor);
387 static int max(int a, int b) { return a > b ? a : b; }
390 void GuiPainter::rectText(int x, int y, docstring const & str,
391 FontInfo const & font, Color back, Color frame)
393 int width, ascent, descent;
395 FontMetrics const & fm = theFontMetrics(font);
396 fm.rectText(str, width, ascent, descent);
398 if (back != Color_none)
399 fillRectangle(x + 1, y - ascent + 1, width - 1,
400 ascent + descent - 1, back);
402 if (frame != Color_none)
403 rectangle(x, y - ascent, width, ascent + descent, frame);
405 // FIXME: let offset depend on font
406 text(x + 3, y, str, font);
410 void GuiPainter::buttonText(int x, int baseline, docstring const & s,
411 FontInfo const & font, Color back, Color frame, int offset)
413 int width, ascent, descent;
415 FontMetrics const & fm = theFontMetrics(font);
416 fm.buttonText(s, offset, width, ascent, descent);
418 static int const d = offset / 2;
420 fillRectangle(x + d + 1, baseline - ascent + 1, width - offset - 1,
421 ascent + descent - 1, back);
422 rectangle(x + d, baseline - ascent, width - offset, ascent + descent, frame);
423 text(x + offset, baseline, s, font);
427 int GuiPainter::preeditText(int x, int y, char_type c,
428 FontInfo const & font, preedit_style style)
430 FontInfo temp_font = font;
431 FontMetrics const & fm = theFontMetrics(font);
432 int ascent = fm.maxAscent();
433 int descent = fm.maxDescent();
434 int height = ascent + descent;
435 int width = fm.width(c);
438 case preedit_default:
439 // default unselecting mode.
440 fillRectangle(x, y - height + 1, width, height, Color_background);
441 dashedUnderline(font, x, y - descent + 1, width);
443 case preedit_selecting:
444 // We are in selecting mode: white text on black background.
445 fillRectangle(x, y - height + 1, width, height, Color_black);
446 temp_font.setColor(Color_white);
449 // The character comes with a cursor.
450 fillRectangle(x, y - height + 1, width, height, Color_background);
451 underline(font, x, y - descent + 1, width);
454 text(x, y - descent + 1, c, temp_font);
460 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
463 FontMetrics const & fm = theFontMetrics(f);
464 int const pos = fm.underlinePos();
466 line(x, y + pos, x + width, y + pos,
467 f.realColor(), ls, fm.lineWidth());
471 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
473 FontMetrics const & fm = theFontMetrics(f);
474 int const pos = fm.strikeoutPos();
476 line(x, y - pos, x + width, y - pos,
477 f.realColor(), line_solid, fm.lineWidth());
481 void GuiPainter::crossoutLines(FontInfo const & f, int x, int y, int width)
484 tmpf.setXout(FONT_OFF);
486 // the definition of \xout in ulem.sty is
487 // \def\xout{\bgroup \markoverwith{\hbox to.35em{\hss/\hss}}\ULon}
488 // Let's mimic it somewhat.
489 double offset = max(0.35 * theFontMetrics(tmpf).em(), 1);
490 for (int i = 0 ; i < iround(width / offset) ; ++i)
491 text(x + iround(i * offset), y, '/', tmpf);
495 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
497 FontMetrics const & fm = theFontMetrics(f);
498 int const pos1 = fm.underlinePos() + fm.lineWidth();
499 int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
501 line(x, y + pos1, x + width, y + pos1,
502 f.realColor(), line_solid, fm.lineWidth());
503 line(x, y + pos2, x + width, y + pos2,
504 f.realColor(), line_solid, fm.lineWidth());
508 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
510 FontMetrics const & fm = theFontMetrics(f);
512 int const below = max(fm.maxDescent() / 2, 2);
513 int height = max((fm.maxDescent() / 4) - 1, 1);
518 for (int n = 0; n != height; ++n)
519 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
523 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
525 setQPainterPen(computeColor(col));
527 int const xend = x + width;
529 //FIXME: I am not sure if Antialiasing gives the best effect.
530 //setRenderHint(Antialiasing, true);
533 drawLine(x, y - height, x + step, y + height);
535 drawLine(x, y + height, x + step/2, y + height);
538 //setRenderHint(Antialiasing, false);
541 } // namespace frontend