* Full author contact details are available in file CREDITS.
*/
-#ifdef Q_OS_MAC
-#define USE_RTL_OVERRIDE 1
-#endif
-
#include <config.h>
#include "GuiPainter.h"
#include "support/lassert.h"
#include "support/debug.h"
+#include <algorithm>
+
#include <QPixmapCache>
#include <QTextLayout>
namespace lyx {
namespace frontend {
-
-const float Painter::thin_line = 0.0;
+
+const int Painter::thin_line = 1;
GuiPainter::GuiPainter(QPaintDevice * device, double pixel_ratio)
: QPainter(device), Painter(pixel_ratio),
void GuiPainter::setQPainterPen(QColor const & col,
- Painter::line_style ls, float lw)
+ Painter::line_style ls, int lw)
{
if (col == current_color_ && ls == current_ls_ && lw == current_lw_)
return;
pen.setColor(col);
switch (ls) {
- case line_solid: pen.setStyle(Qt::SolidLine); break;
- case line_onoffdash: pen.setStyle(Qt::DotLine); break;
+ case line_solid:
+ case line_solid_aliased:
+ pen.setStyle(Qt::SolidLine); break;
+ case line_onoffdash:
+ pen.setStyle(Qt::DotLine); break;
}
- pen.setWidthF(lw);
+ pen.setWidth(lw);
setPen(pen);
}
-QString GuiPainter::generateStringSignature(QString const & str, FontInfo const & f)
+QString GuiPainter::generateStringSignature(QString const & str,
+ FontInfo const & f,
+ double wordspacing)
{
QString sig = str;
sig.append(QChar(static_cast<short>(f.family())));
Color const & color = f.realColor();
sig.append(QChar(static_cast<short>(color.baseColor)));
sig.append(QChar(static_cast<short>(color.mergeColor)));
+ sig.append(QString::number(wordspacing));
if (!monochrome_min_.empty()) {
QColor const & min = monochrome_min_.top();
QColor const & max = monochrome_max_.top();
void GuiPainter::line(int x1, int y1, int x2, int y2,
Color col,
line_style ls,
- float lw)
+ int lw)
{
if (!isDrawingEnabled())
return;
setQPainterPen(computeColor(col), ls, lw);
bool const do_antialiasing = renderHints() & TextAntialiasing
- && x1 != x2 && y1 != y2;
+ && x1 != x2 && y1 != y2 && ls != line_solid_aliased;
setRenderHint(Antialiasing, do_antialiasing);
drawLine(x1, y1, x2, y2);
setRenderHint(Antialiasing, false);
Color col,
fill_style fs,
line_style ls,
- float lw)
+ int lw)
{
if (!isDrawingEnabled())
return;
if (np > points.size())
points.resize(2 * np);
+ // Note: the proper way to not get blurry vertical and horizontal lines is
+ // to add 0.5 to all coordinates.
bool antialias = false;
for (int i = 0; i < np; ++i) {
points[i].setX(xp[i]);
QColor const color = computeColor(col);
setQPainterPen(color, ls, lw);
bool const text_is_antialiased = renderHints() & TextAntialiasing;
- setRenderHint(Antialiasing, antialias && text_is_antialiased);
+ setRenderHint(Antialiasing,
+ antialias && text_is_antialiased && ls != line_solid_aliased);
if (fs == fill_none) {
drawPolyline(points.data(), np);
} else {
}
+void GuiPainter::path(int const * xp, int const * yp,
+ int const * c1x, int const * c1y,
+ int const * c2x, int const * c2y,
+ int np,
+ Color col,
+ fill_style fs,
+ line_style ls,
+ int lw)
+{
+ if (!isDrawingEnabled())
+ return;
+
+ QPainterPath bpath;
+ // This is the starting point, so its control points are meaningless
+ bpath.moveTo(xp[0], yp[0]);
+
+ for (int i = 1; i < np; ++i) {
+ bool line = c1x[i] == xp[i - 1] && c1y[i] == yp[i - 1] &&
+ c2x[i] == xp[i] && c2y[i] == yp[i];
+ if (line)
+ bpath.lineTo(xp[i], yp[i]);
+ else
+ bpath.cubicTo(c1x[i], c1y[i], c2x[i], c2y[i], xp[i], yp[i]);
+ }
+ QColor const color = computeColor(col);
+ setQPainterPen(color, ls, lw);
+ bool const text_is_antialiased = renderHints() & TextAntialiasing;
+ setRenderHint(Antialiasing, text_is_antialiased && ls != line_solid_aliased);
+ drawPath(bpath);
+ if (fs != fill_none)
+ fillPath(bpath, QBrush(color));
+ setRenderHint(Antialiasing, false);
+}
+
+
void GuiPainter::rectangle(int x, int y, int w, int h,
Color col,
line_style ls,
- float lw)
+ int lw)
{
if (!isDrawingEnabled())
return;
QImage const image = qlimage.image();
QRectF const drect = QRectF(x, y, w, h);
QRectF const srect = QRectF(0, 0, image.width(), image.height());
+ // Bilinear filtering is needed on a rare occasion for instant previews when
+ // the user's configuration mixes low-dpi and high-dpi monitors (#10114).
+ // This filter is optimised by qt on pixel-aligned images, so this does not
+ // affect performances in other cases.
+ setRenderHint(SmoothPixmapTransform);
drawImage(drect, image, srect);
+ setRenderHint(SmoothPixmapTransform, false);
+}
+
+
+void GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
+{
+ text(x, y, docstring(1, c), f);
}
-int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
+void GuiPainter::text(int x, int y, docstring const & s, FontInfo const & f)
{
- return text(x, y, docstring(1, c), f);
+ text(x, y, s, f, Auto, 0.0, 0.0);
}
-int GuiPainter::text(int x, int y, docstring const & s,
- FontInfo const & f, bool const rtl)
+void GuiPainter::do_drawText(int x, int y, QString str,
+ GuiPainter::Direction const dir,
+ FontInfo const & f, QFont ff)
+{
+ setQPainterPen(computeColor(f.realColor()));
+ if (font() != ff)
+ setFont(ff);
+
+ /* In LyX, the character direction is forced by the language.
+ * Therefore, we have to signal that fact to Qt.
+ */
+#if 1
+ /* Use unicode override characters to enforce drawing direction
+ * Source: http://www.iamcal.com/understanding-bidirectional-text/
+ */
+ if (dir == RtL)
+ // Right-to-left override: forces to draw text right-to-left
+ str = QChar(0x202E) + str;
+ else if (dir == LtR)
+ // Left-to-right override: forces to draw text left-to-right
+ str = QChar(0x202D) + str;
+ drawText(x, y, str);
+#else
+ /* This looks like a cleaner solution, but it has drawbacks
+ * - does not work reliably (Mac OS X, ...)
+ * - it is not really documented
+ * Keep it here for now, in case it can be helpful
+ */
+ //This is much stronger than setLayoutDirection.
+ int flag = 0;
+ if (dir == RtL)
+ flag = Qt::TextForceRightToLeft;
+ else if (dir == LtR)
+ flag = Qt::TextForceLeftToRight;
+ drawText(x + ((dir == RtL) ? textwidth : 0), y - fm.maxAscent(), 0, 0,
+ flag | Qt::TextDontClip,
+ str);
+#endif
+}
+
+
+void GuiPainter::text(int x, int y, docstring const & s,
+ FontInfo const & f, Direction const dir,
+ double const wordspacing, double const tw)
{
//LYXERR0("text: x=" << x << ", s=" << s);
- if (s.empty())
- return 0;
+ if (s.empty() || !isDrawingEnabled())
+ return;
/* Caution: The following ucs4 to QString conversions work for symbol fonts
only because they are no real conversions but simple casts in reality.
of the symbol in the font (as given in lib/symbols) as a char_type to the
frontend. This is just wrong, because the symbol is no UCS4 character at
all. You can think of this number as the code point of the symbol in a
- custom symbol encoding. It works because this char_type is lateron again
- interpreted as a position in the font again.
+ custom symbol encoding. It works because this char_type is later on again
+ interpreted as a position in the font.
The correct solution would be to have extra functions for symbols, but that
would require to duplicate a lot of frontend and mathed support code.
*/
str = ' ' + str;
#endif
- QFont const & ff = getFont(f);
+ QFont ff = getFont(f);
+ ff.setWordSpacing(wordspacing);
GuiFontMetrics const & fm = getFontMetrics(f);
- // Here we use the font width cache instead of
- // textwidth = fontMetrics().width(str);
- // because the above is awfully expensive on MacOSX
- int const textwidth = fm.width(s);
-
- if (!isDrawingEnabled())
- return textwidth;
+ int textwidth = 0;
+ if (tw == 0.0)
+ // Note that we have to take in account space stretching (word spacing)
+ textwidth = fm.width(s) +
+ static_cast<int>(fm.countExpanders(s) * wordspacing);
+ else
+ textwidth = static_cast<int>(tw);
textDecoration(f, x, y, textwidth);
- // Qt4 does not display a glyph whose codepoint is the
- // same as that of a soft-hyphen (0x00ad), unless it
- // occurs at a line-break. As a kludge, we force Qt to
- // render this glyph using a one-column line.
- // This is needed for some math glyphs.
- // Should the soft hyphen char be displayed at all?
- // I don't think so (i.e., Qt is correct as far as
- // texted is concerned). /spitz
- if (s.size() == 1 && str[0].unicode() == 0x00ad) {
- setQPainterPen(computeColor(f.realColor()));
- QTextLayout adsymbol(str);
- adsymbol.setFont(ff);
- adsymbol.beginLayout();
- QTextLine line = adsymbol.createLine();
- line.setNumColumns(1);
- line.setPosition(QPointF(0, -line.ascent()));
- adsymbol.endLayout();
- line.draw(this, QPointF(x, y));
- return textwidth;
- }
-
if (use_pixmap_cache_) {
QPixmap pm;
- QString key = generateStringSignature(str, f);
+ QString key = generateStringSignature(str, f, wordspacing);
// Warning: Left bearing is in general negative! Only the case
// where left bearing is negative is of interest WRT the
// Only the left bearing of the first character is important
// as we always write from left to right, even for
// right-to-left languages.
+ // FIXME: this is probably broken for RTL now that we draw full strings.
+ // Morover the first/last element is possibly not the right one since the glyph may have changed.
int const lb = min(fm.lbearing(s[0]), 0);
int const mA = fm.maxAscent();
if (QPixmapCache::find(key, pm)) {
// Draw the cached pixmap.
drawPixmap(x + lb, y - mA, pm);
- return textwidth;
+ return;
}
// Only the right bearing of the last character is
#endif
pm.fill(Qt::transparent);
GuiPainter p(&pm, pixelRatio());
- p.setQPainterPen(computeColor(f.realColor()));
- if (p.font() != ff)
- p.setFont(ff);
- // We need to draw the text as LTR as we use our own bidi code.
- p.setLayoutDirection(Qt::LeftToRight);
- p.drawText(-lb, mA, str);
+ p.do_drawText(-lb, mA, str, dir, f, ff);
QPixmapCache::insert(key, pm);
//LYXERR(Debug::PAINTING, "h=" << h << " mA=" << mA << " mD=" << mD
// << " w=" << w << " lb=" << lb << " tw=" << textwidth
// Draw the new cached pixmap.
drawPixmap(x + lb, y - mA, pm);
-
- return textwidth;
+ //rectangle(x-lb, y-mA, w, h, Color_green);
}
+ return;
}
- // don't use the pixmap cache,
- // draw directly onto the painting device
+ // don't use the pixmap cache
setQPainterPen(computeColor(f.realColor()));
- if (font() != ff)
- setFont(ff);
-
- /* In LyX, the character direction is forced by the language.
- * Therefore, we have to signal that fact to Qt.
- */
-#ifdef USE_RTL_OVERRIDE
- /* Use unicode override characters to enforce drawing direction
- * Source: http://www.iamcal.com/understanding-bidirectional-text/
- */
- if (rtl)
- // Right-to-left override: forces to draw text right-to-left
- str = QChar(0x202E) + str;
- else
- // Left-to-right override: forces to draw text left-to-right
- str = QChar(0x202D) + str;
- drawText(x, y, str);
-#else
- /* This is a cleanr solution, but it has two drawbacks
- * - it seems that it does not work under Mac OS X
- * - it is not really documented
- */
- //This is much stronger than setLayoutDirection.
- int flag = rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight;
- drawText(x + (rtl ? textwidth : 0), y - fm.maxAscent(), 0, 0,
- flag | Qt::TextDontClip,
- str);
-#endif
+ if (dir != Auto) {
+ shared_ptr<QTextLayout const> ptl =
+ fm.getTextLayout(s, dir == RtL, wordspacing);
+ ptl->draw(this, QPointF(x, y - fm.maxAscent()));
+ }
+ else {
+ if (font() != ff)
+ setFont(ff);
+ drawText(x, y, str);
+ }
//LYXERR(Debug::PAINTING, "draw " << string(str.toUtf8())
// << " at " << x << "," << y);
- return textwidth;
}
-int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
+void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
+ double const wordspacing, double const tw)
{
- return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
+ text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft() ? RtL : LtR,
+ wordspacing, tw);
}
-int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
- Color other, size_type from, size_type to)
+void GuiPainter::text(int x, int y, docstring const & str, Font const & f,
+ Color other, size_type const from, size_type const to,
+ double const wordspacing, double const tw)
{
GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
FontInfo fi = f.fontInfo();
- bool const rtl = f.isVisibleRightToLeft();
+ Direction const dir = f.isVisibleRightToLeft() ? RtL : LtR;
// dimensions
int const ascent = fm.maxAscent();
int const height = fm.maxAscent() + fm.maxDescent();
- int xmin = fm.pos2x(str, from, rtl);
- int xmax = fm.pos2x(str, to, rtl);
+ int xmin = fm.pos2x(str, from, dir == RtL, wordspacing);
+ int xmax = fm.pos2x(str, to, dir == RtL, wordspacing);
if (xmin > xmax)
swap(xmin, xmax);
fi.setPaintColor(other);
QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
setClipRegion(clip);
- int const textwidth = text(x, y, str, fi, rtl);
+ text(x, y, str, fi, dir, wordspacing, tw);
// Then the part in normal color
// Note that in Qt5, it is not possible to use Qt::UniteClip,
fi.setPaintColor(orig);
QRegion region(viewport());
setClipRegion(region - clip);
- text(x, y, str, fi, rtl);
+ text(x, y, str, fi, dir, wordspacing, tw);
setClipping(false);
-
- return textwidth;
}
static int const d = Inset::TEXT_TO_INSET_OFFSET / 2;
- button(x + d, y - ascent, width - d, descent + ascent, mouseHover);
+ button(x + d, y - ascent, width - Inset::TEXT_TO_INSET_OFFSET, descent + ascent, mouseHover);
text(x + Inset::TEXT_TO_INSET_OFFSET, y, str, font);
}
}
-void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
+void GuiPainter::underline(FontInfo const & f, int x, int y, int width,
+ line_style ls)
{
FontMetrics const & fm = theFontMetrics(f);
+ int const pos = fm.underlinePos();
- int const below = max(fm.maxDescent() / 2, 2);
-
- line(x, y + below, x + width, y + below, f.realColor());
- line(x, y + below - 2, x + width, y + below - 2, f.realColor());
+ line(x, y + pos, x + width, y + pos,
+ f.realColor(), ls, fm.lineWidth());
}
-void GuiPainter::underline(FontInfo const & f, int x, int y, int width)
+void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
{
FontMetrics const & fm = theFontMetrics(f);
+ int const pos = fm.strikeoutPos();
- int const below = max(fm.maxDescent() / 2, 2);
- int const height = max((fm.maxDescent() / 4) - 1, 1);
-
- if (height < 2)
- line(x, y + below, x + width, y + below, f.realColor());
- else
- fillRectangle(x, y + below, width, below + height, f.realColor());
+ line(x, y - pos, x + width, y - pos,
+ f.realColor(), line_solid, fm.lineWidth());
}
-void GuiPainter::strikeoutLine(FontInfo const & f, int x, int y, int width)
+void GuiPainter::doubleUnderline(FontInfo const & f, int x, int y, int width)
{
FontMetrics const & fm = theFontMetrics(f);
+ int const pos1 = fm.underlinePos() + fm.lineWidth();
+ int const pos2 = fm.underlinePos() - fm.lineWidth() + 1;
- int const middle = max((fm.maxHeight() / 4), 1);
- int const height = middle/3;
-
- if (height < 2)
- line(x, y - middle, x + width, y - middle, f.realColor());
- else
- fillRectangle(x, y - middle, width, height, f.realColor());
+ line(x, y + pos1, x + width, y + pos1,
+ f.realColor(), line_solid, fm.lineWidth());
+ line(x, y + pos2, x + width, y + pos2,
+ f.realColor(), line_solid, fm.lineWidth());
}