]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiPainter.cpp
Introduce basic support for microtype.sty.
[lyx.git] / src / frontends / qt4 / GuiPainter.cpp
1 /**
2  * \file GuiPainter.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Abdelrazak Younes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "GuiPainter.h"
15
16 #include "ColorCache.h"
17 #include "GuiApplication.h"
18 #include "GuiFontLoader.h"
19 #include "GuiFontMetrics.h"
20 #include "GuiImage.h"
21 #include "qt_helpers.h"
22
23 #include "Font.h"
24 #include "Language.h"
25 #include "LyXRC.h"
26
27 #include "insets/Inset.h"
28
29 #include "support/lassert.h"
30 #include "support/debug.h"
31
32 #include <QPixmapCache>
33 #include <QTextLayout>
34
35 // Set USE_PIXMAP_CACHE to 1 for enabling the use of a Pixmap cache when
36 // drawing text. This is especially useful for older PPC/Mac systems.
37 #if defined(Q_WS_X11) || defined(QPA_XCB)
38 #define USE_PIXMAP_CACHE 0
39 #else
40 #define USE_PIXMAP_CACHE 1
41 #endif
42
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47 namespace frontend {
48
49 const int Painter::thin_line = 1;
50
51 GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
52         : QPainter(device), Painter(pixel_ratio),
53           use_pixmap_cache_(lyxrc.use_pixmap_cache && USE_PIXMAP_CACHE)
54 {
55         // new QPainter has default QPen:
56         current_color_ = guiApp->colorCache().get(Color_black);
57         current_ls_ = line_solid;
58         current_lw_ = thin_line;
59 }
60
61
62 GuiPainter::~GuiPainter()
63 {
64         QPainter::end();
65         //lyxerr << "GuiPainter::end()" << endl;
66 }
67
68
69 void GuiPainter::setQPainterPen(QColor const & col,
70         Painter::line_style ls, int lw)
71 {
72         if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
73                 return;
74
75         current_color_ = col;
76         current_ls_ = ls;
77         current_lw_ = lw;
78
79         QPen pen = QPainter::pen();
80         pen.setColor(col);
81
82         switch (ls) {
83                 case line_solid: pen.setStyle(Qt::SolidLine); break;
84                 case line_onoffdash: pen.setStyle(Qt::DotLine); break;
85         }
86
87         pen.setWidth(lw);
88
89         setPen(pen);
90 }
91
92
93 QString GuiPainter::generateStringSignature(QString const & str,
94                                             FontInfo const & f,
95                                             double wordspacing)
96 {
97         QString sig = str;
98         sig.append(QChar(static_cast<short>(f.family())));
99         sig.append(QChar(static_cast<short>(f.series())));
100         sig.append(QChar(static_cast<short>(f.realShape())));
101         sig.append(QChar(static_cast<short>(f.size())));
102         Color const & color = f.realColor();
103         sig.append(QChar(static_cast<short>(color.baseColor)));
104         sig.append(QChar(static_cast<short>(color.mergeColor)));
105         sig.append(QString::number(wordspacing));
106         if (!monochrome_min_.empty()) {
107                 QColor const & min = monochrome_min_.top();
108                 QColor const & max = monochrome_max_.top();
109                 sig.append(QChar(static_cast<short>(min.red())));
110                 sig.append(QChar(static_cast<short>(min.green())));
111                 sig.append(QChar(static_cast<short>(min.blue())));
112                 sig.append(QChar(static_cast<short>(max.red())));
113                 sig.append(QChar(static_cast<short>(max.green())));
114                 sig.append(QChar(static_cast<short>(max.blue())));
115         }
116         return sig;
117 }
118
119
120 QColor GuiPainter::computeColor(Color col)
121 {
122         return filterColor(guiApp->colorCache().get(col));
123 }
124
125
126 QColor GuiPainter::filterColor(QColor const & col)
127 {
128         if (monochrome_min_.empty())
129                 return col;
130
131         // map into [min,max] interval
132         QColor const & min = monochrome_min_.top();
133         QColor const & max = monochrome_max_.top();
134                         
135         qreal v = col.valueF();
136         v *= v; // make it a bit steeper (i.e. darker)
137                 
138         qreal minr, ming, minb;
139         qreal maxr, maxg, maxb;
140         min.getRgbF(&minr, &ming, &minb);
141         max.getRgbF(&maxr, &maxg, &maxb);
142                         
143         QColor c;
144         c.setRgbF(
145                 v * (minr - maxr) + maxr,
146                 v * (ming - maxg) + maxg,
147                 v * (minb - maxb) + maxb);
148         return c;
149 }
150
151
152 void GuiPainter::enterMonochromeMode(Color const & min, Color const & max)
153 {
154         QColor qmin = filterColor(guiApp->colorCache().get(min));
155         QColor qmax = filterColor(guiApp->colorCache().get(max));
156         monochrome_min_.push(qmin);
157         monochrome_max_.push(qmax);
158 }
159
160
161 void GuiPainter::leaveMonochromeMode()
162 {
163         LASSERT(!monochrome_min_.empty(), return);
164         monochrome_min_.pop();
165         monochrome_max_.pop();
166 }
167
168
169 void GuiPainter::point(int x, int y, Color col)
170 {
171         if (!isDrawingEnabled())
172                 return;
173
174         setQPainterPen(computeColor(col));
175         drawPoint(x, y);
176 }
177
178
179 void GuiPainter::line(int x1, int y1, int x2, int y2,
180         Color col,
181         line_style ls,
182         int lw)
183 {
184         if (!isDrawingEnabled())
185                 return;
186
187         setQPainterPen(computeColor(col), ls, lw);
188         bool const do_antialiasing = renderHints() & TextAntialiasing
189                 && x1 != x2 && y1 != y2;
190         setRenderHint(Antialiasing, do_antialiasing);
191         drawLine(x1, y1, x2, y2);
192         setRenderHint(Antialiasing, false);
193 }
194
195
196 void GuiPainter::lines(int const * xp, int const * yp, int np,
197         Color col,
198         fill_style fs,
199         line_style ls,
200         int lw)
201 {
202         if (!isDrawingEnabled())
203                 return;
204
205         // double the size if needed
206         // FIXME THREAD
207         static QVector<QPoint> points(32);
208         if (np > points.size())
209                 points.resize(2 * np);
210
211         bool antialias = false;
212         for (int i = 0; i < np; ++i) {
213                 points[i].setX(xp[i]);
214                 points[i].setY(yp[i]);
215                 if (i != 0)
216                         antialias |= xp[i-1] != xp[i] && yp[i-1] != yp[i];
217         }
218         QColor const color = computeColor(col);
219         setQPainterPen(color, ls, lw);
220         bool const text_is_antialiased = renderHints() & TextAntialiasing;
221         setRenderHint(Antialiasing, antialias && text_is_antialiased);
222         if (fs == fill_none) {
223                 drawPolyline(points.data(), np);
224         } else {
225                 QBrush const oldbrush = brush();
226                 setBrush(QBrush(color));
227                 drawPolygon(points.data(), np, fs == fill_oddeven ?
228                             Qt::OddEvenFill : Qt::WindingFill);
229                 setBrush(oldbrush);
230         }
231         setRenderHint(Antialiasing, false);
232 }
233
234
235 void GuiPainter::path(int const * xp, int const * yp,
236         int const * c1x, int const * c1y,
237         int const * c2x, int const * c2y,
238         int np,
239         Color col,
240         fill_style fs,
241         line_style ls,
242         int lw)
243 {
244         if (!isDrawingEnabled())
245                 return;
246
247         QPainterPath bpath;
248         // This is the starting point, so its control points are meaningless
249         bpath.moveTo(xp[0], yp[0]);
250
251         for (int i = 1; i < np; ++i) {
252                 bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
253                             c2x[i] == xp[i] && c2y[i] == yp[i];
254                 if (line)
255                         bpath.lineTo(xp[i], yp[i]);
256                 else
257                         bpath.cubicTo(c1x[i], c1y[i],  c2x[i], c2y[i], xp[i], yp[i]);
258         }
259         QColor const color = computeColor(col);
260         setQPainterPen(color, ls, lw);
261         bool const text_is_antialiased = renderHints() & TextAntialiasing;
262         setRenderHint(Antialiasing, text_is_antialiased);
263         drawPath(bpath);
264         if (fs != fill_none)
265                 fillPath(bpath, QBrush(color));
266         setRenderHint(Antialiasing, false);
267 }
268
269
270 void GuiPainter::rectangle(int x, int y, int w, int h,
271         Color col,
272         line_style ls,
273         int lw)
274 {
275         if (!isDrawingEnabled())
276                 return;
277
278         setQPainterPen(computeColor(col), ls, lw);
279         drawRect(x, y, w, h);
280 }
281
282
283 void GuiPainter::fillRectangle(int x, int y, int w, int h, Color col)
284 {
285         if (!isDrawingEnabled())
286                 return;
287
288         fillRect(x, y, w, h, guiApp->colorCache().get(col));
289 }
290
291
292 void GuiPainter::arc(int x, int y, unsigned int w, unsigned int h,
293         int a1, int a2, Color col)
294 {
295         if (!isDrawingEnabled())
296                 return;
297
298         // LyX usings 1/64ths degree, Qt usings 1/16th
299         setQPainterPen(computeColor(col));
300         bool const do_antialiasing = renderHints() & TextAntialiasing;
301         setRenderHint(Antialiasing, do_antialiasing);
302         drawArc(x, y, w, h, a1 / 4, a2 / 4);
303         setRenderHint(Antialiasing, false);
304 }
305
306
307 void GuiPainter::image(int x, int y, int w, int h, graphics::Image const & i)
308 {
309         graphics::GuiImage const & qlimage =
310                 static_cast<graphics::GuiImage const &>(i);
311
312         fillRectangle(x, y, w, h, Color_graphicsbg);
313
314         if (!isDrawingEnabled())
315                 return;
316
317         QImage const image = qlimage.image();
318         QRectF const drect = QRectF(x, y, w, h);
319         QRectF const srect = QRectF(0, 0, image.width(), image.height());
320         // Bilinear filtering is needed on a rare occasion for instant previews when
321         // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
322         // This filter is optimised by qt on pixel-aligned images, so this does not
323         // affect performances in other cases.
324         setRenderHint(SmoothPixmapTransform);
325         drawImage(drect, image, srect);
326         setRenderHint(SmoothPixmapTransform, false);
327 }
328
329
330 void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
331 {
332         text(x, y, docstring(1, c), f);
333 }
334
335
336 void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
337 {
338         text(x, y, s, f, Auto, 0.0, 0.0);
339 }
340
341
342 void GuiPainter::do_drawText(int x, int y, QString str,
343                              GuiPainter::Direction const dir,
344                              FontInfo const & f, QFont ff)
345 {
346         setQPainterPen(computeColor(f.realColor()));
347         if (font() != ff)
348                 setFont(ff);
349
350          /* In LyX, the character direction is forced by the language.
351           * Therefore, we have to signal that fact to Qt.
352           */
353 #if 1
354         /* Use unicode override characters to enforce drawing direction
355          * Source: http://www.iamcal.com/understanding-bidirectional-text/
356          */
357         if (dir == RtL)
358                 // Right-to-left override: forces to draw text right-to-left
359                 str = QChar(0x202E) + str;
360         else if (dir == LtR)
361                 // Left-to-right override: forces to draw text left-to-right
362                 str =  QChar(0x202D) + str;
363         drawText(x, y, str);
364 #else
365         /* This looks like a cleaner solution, but it has drawbacks
366          * - does not work reliably (Mac OS X, ...)
367          * - it is not really documented
368          * Keep it here for now, in case it can be helpful
369          */
370         //This is much stronger than setLayoutDirection.
371         int flag = 0;
372         if (dir == RtL)
373                 flag = Qt::TextForceRightToLeft;
374         else if (dir == LtR)
375                 flag = Qt::TextForceLeftToRight;
376         drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
377                  flag | Qt::TextDontClip,
378                  str);
379 #endif
380 }
381
382
383 void GuiPainter::text(int x, int y, docstring const & s,
384                       FontInfo const & f, Direction const dir,
385                       double const wordspacing, double const tw)
386 {
387         //LYXERR0("text: x=" << x << ", s=" << s);
388         if (s.empty() || !isDrawingEnabled())
389                 return;
390
391         /* Caution: The following ucs4 to QString conversions work for symbol fonts
392         only because they are no real conversions but simple casts in reality.
393         When we want to draw a symbol or calculate the metrics we pass the position
394         of the symbol in the font (as given in lib/symbols) as a char_type to the
395         frontend. This is just wrong, because the symbol is no UCS4 character at
396         all. You can think of this number as the code point of the symbol in a
397         custom symbol encoding. It works because this char_type is later on again
398         interpreted as a position in the font.
399         The correct solution would be to have extra functions for symbols, but that
400         would require to duplicate a lot of frontend and mathed support code.
401         */
402         QString str = toqstr(s);
403
404 #if 0
405         // HACK: QT3 refuses to show single compose characters
406         //       Still needed with Qt4?
407         if (ls == 1 && str[0].unicode() >= 0x05b0 && str[0].unicode() <= 0x05c2)
408                 str = ' ' + str;
409 #endif
410
411         QFont ff = getFont(f);
412         ff.setWordSpacing(wordspacing);
413         GuiFontMetrics const & fm = getFontMetrics(f);
414
415         int textwidth = 0;
416         if (tw == 0.0)
417                 // Note that we have to take in account space stretching (word spacing)
418                 textwidth = fm.width(s) + count(s.begin(), s.end(), ' ') * wordspacing;
419         else
420                 textwidth = static_cast<int>(tw);
421
422         textDecoration(f, x, y, textwidth);
423
424         if (use_pixmap_cache_) {
425                 QPixmap pm;
426                 QString key = generateStringSignature(str, f, wordspacing);
427
428                 // Warning: Left bearing is in general negative! Only the case
429                 // where left bearing is negative is of interest WRT the
430                 // pixmap width and the text x-position.
431                 // Only the left bearing of the first character is important
432                 // as we always write from left to right, even for
433                 // right-to-left languages.
434                 // FIXME: this is probably broken for RTL now that we draw full strings.
435                 // Morover the first/last element is possibly not the right one since the glyph may have changed.
436                 int const lb = min(fm.lbearing(s[0]), 0);
437                 int const mA = fm.maxAscent();
438                 if (QPixmapCache::find(key, pm)) {
439                         // Draw the cached pixmap.
440                         drawPixmap(x + lb, y - mA, pm);
441                         return;
442                 }
443
444                 // Only the right bearing of the last character is
445                 // important as we always write from left to right,
446                 // even for right-to-left languages.
447                 int const rb = fm.rbearing(s[s.size()-1]);
448                 int const w = textwidth + rb - lb;
449                 int const mD = fm.maxDescent();
450                 int const h = mA + mD;
451                 if (w > 0 && h > 0) {
452                         pm = QPixmap(static_cast<int>(pixelRatio() * w),
453                                                  static_cast<int>(pixelRatio() * h));
454 #if QT_VERSION >= 0x050000
455                         pm.setDevicePixelRatio(pixelRatio());
456 #endif
457                         pm.fill(Qt::transparent);
458                         GuiPainter p(&pm, pixelRatio());
459                         p.do_drawText(-lb, mA, str, dir, f, ff);
460                         QPixmapCache::insert(key, pm);
461                         //LYXERR(Debug::PAINTING, "h=" << h << "  mA=" << mA << "  mD=" << mD
462                         //      << "  w=" << w << "  lb=" << lb << "  tw=" << textwidth
463                         //      << "  rb=" << rb);
464
465                         // Draw the new cached pixmap.
466                         drawPixmap(x + lb, y - mA, pm);
467                         //rectangle(x-lb, y-mA, w, h, Color_green);
468                 }
469                 return;
470         }
471
472         // don't use the pixmap cache,
473         do_drawText(x, y, str, dir, f, ff);
474         //LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
475         //      << " at " << x << "," << y);
476 }
477
478
479 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
480                       double const wordspacing, double const tw)
481 {
482         text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
483              wordspacing, tw);
484 }
485
486
487 void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
488                       Color other, size_type const from, size_type const to,
489                       double const wordspacing, double const tw)
490 {
491         GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
492         FontInfo fi = f.fontInfo();
493         Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
494
495         // dimensions
496         int const ascent = fm.maxAscent();
497         int const height = fm.maxAscent() + fm.maxDescent();
498         int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
499         int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
500         if (xmin > xmax)
501                 swap(xmin, xmax);
502
503         // First the part in other color
504         Color const orig = fi.realColor();
505         fi.setPaintColor(other);
506         QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
507         setClipRegion(clip);
508         text(x, y, str, fi, dir, wordspacing, tw);
509
510         // Then the part in normal color
511         // Note that in Qt5, it is not possible to use Qt::UniteClip,
512         // therefore QRegion is used.
513         fi.setPaintColor(orig);
514         QRegion region(viewport());
515         setClipRegion(region - clip);
516         text(x, y, str, fi, dir, wordspacing, tw);
517         setClipping(false);
518 }
519
520
521 void GuiPainter::textDecoration(FontInfo const & f, int x, int y, int width)
522 {
523         if (f.underbar() == FONT_ON)
524                 underline(f, x, y, width);
525         if (f.strikeout() == FONT_ON)
526                 strikeoutLine(f, x, y, width);
527         if (f.uuline() == FONT_ON)
528                 doubleUnderline(f, x, y, width);
529         if (f.uwave() == FONT_ON)
530                 // f.color() doesn't work on some circumstances
531                 wavyHorizontalLine(x, y, width,  f.realColor().baseColor);
532 }
533
534
535 static int max(int a, int b) { return a > b ? a : b; }
536
537
538 void GuiPainter::button(int x, int y, int w, int h, bool mouseHover)
539 {
540         if (mouseHover)
541                 fillRectangle(x, y, w, h, Color_buttonhoverbg);
542         else
543                 fillRectangle(x, y, w, h, Color_buttonbg);
544         buttonFrame(x, y, w, h);
545 }
546
547
548 void GuiPainter::buttonFrame(int x, int y, int w, int h)
549 {
550         line(x, y, x, y + h - 1, Color_buttonframe);
551         line(x - 1 + w, y, x - 1 + w, y + h - 1, Color_buttonframe);
552         line(x, y - 1, x - 1 + w, y - 1, Color_buttonframe);
553         line(x, y + h - 1, x - 1 + w, y + h - 1, Color_buttonframe);
554 }
555
556
557 void GuiPainter::rectText(int x, int y, docstring const & str,
558         FontInfo const & font, Color back, Color frame)
559 {
560         int width;
561         int ascent;
562         int descent;
563
564         FontMetrics const & fm = theFontMetrics(font);
565         fm.rectText(str, width, ascent, descent);
566
567         if (back != Color_none)
568                 fillRectangle(x + 1, y - ascent + 1, width - 1,
569                               ascent + descent - 1, back);
570
571         if (frame != Color_none)
572                 rectangle(x, y - ascent, width, ascent + descent, frame);
573
574         text(x + 3, y, str, font);
575 }
576
577
578 void GuiPainter::buttonText(int x, int y, docstring const & str,
579         FontInfo const & font, bool mouseHover)
580 {
581         int width;
582         int ascent;
583         int descent;
584
585         FontMetrics const & fm = theFontMetrics(font);
586         fm.buttonText(str, width, ascent, descent);
587
588         static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
589
590         button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
591         text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
592 }
593
594
595 int GuiPainter::preeditText(int x, int y, char_type c,
596         FontInfo const & font, preedit_style style)
597 {
598         FontInfo temp_font = font;
599         FontMetrics const & fm = theFontMetrics(font);
600         int ascent = fm.maxAscent();
601         int descent = fm.maxDescent();
602         int height = ascent + descent;
603         int width = fm.width(c);
604
605         switch (style) {
606                 case preedit_default:
607                         // default unselecting mode.
608                         fillRectangle(x, y - height + 1, width, height, Color_background);
609                         dashedUnderline(font, x, y - descent + 1, width);
610                         break;
611                 case preedit_selecting:
612                         // We are in selecting mode: white text on black background.
613                         fillRectangle(x, y - height + 1, width, height, Color_black);
614                         temp_font.setColor(Color_white);
615                         break;
616                 case preedit_cursor:
617                         // The character comes with a cursor.
618                         fillRectangle(x, y - height + 1, width, height, Color_background);
619                         underline(font, x, y - descent + 1, width);
620                         break;
621         }
622         text(x, y - descent + 1, c, temp_font);
623
624         return width;
625 }
626
627
628 void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
629                            line_style ls)
630 {
631         FontMetrics const & fm = theFontMetrics(f);
632         int const pos = fm.underlinePos();
633
634         line(x, y + pos, x + width, y + pos,
635              f.realColor(), ls, fm.lineWidth());
636 }
637
638
639 void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
640 {
641         FontMetrics const & fm = theFontMetrics(f);
642         int const pos = fm.strikeoutPos();
643
644         line(x, y - pos, x + width, y - pos,
645              f.realColor(), line_solid, fm.lineWidth());
646 }
647
648
649 void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
650 {
651         FontMetrics const & fm = theFontMetrics(f);
652         int const pos1 = fm.underlinePos() + fm.lineWidth();
653         int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
654
655         line(x, y + pos1, x + width, y + pos1,
656                  f.realColor(), line_solid, fm.lineWidth());
657         line(x, y + pos2, x + width, y + pos2,
658                  f.realColor(), line_solid, fm.lineWidth());
659 }
660
661
662 void GuiPainter::dashedUnderline(FontInfo const & f, int x, int y, int width)
663 {
664         FontMetrics const & fm = theFontMetrics(f);
665
666         int const below = max(fm.maxDescent() / 2, 2);
667         int height = max((fm.maxDescent() / 4) - 1, 1);
668
669         if (height >= 2)
670                 height += below;
671
672         for (int n = 0; n != height; ++n)
673                 line(x, y + below + n, x + width, y + below + n, f.realColor(), line_onoffdash);
674 }
675
676
677 void GuiPainter::wavyHorizontalLine(int x, int y, int width, ColorCode col)
678 {
679         setQPainterPen(computeColor(col));
680         int const step = 2;
681         int const xend = x + width;
682         int height = 1;
683         //FIXME: I am not sure if Antialiasing gives the best effect.
684         //setRenderHint(Antialiasing, true);
685         while (x < xend) {
686                 height = - height;
687                 drawLine(x, y - height, x + step, y + height);
688                 x += step;
689                 drawLine(x, y + height, x + step/2, y + height);
690                 x += step/2;
691         }
692         //setRenderHint(Antialiasing, false);
693 }
694
695 } // namespace frontend
696 } // namespace lyx