Merge branch 'rowpainter2'

This commit is contained in:
Jean-Marc 2015-07-21 23:47:12 +02:00
commit b63421b7dc
12 changed files with 308 additions and 673 deletions

View File

@ -1018,139 +1018,17 @@ bool Cursor::posVisToNewRow(bool movingLeft)
void Cursor::posVisToRowExtremity(bool left)
{
// prepare bidi tables
Paragraph const & par = paragraph();
Buffer const & buf = *buffer();
Row const & row = textRow();
Bidi bidi;
bidi.computeTables(par, buf, row);
LYXERR(Debug::RTL, "entering extremity: " << pit() << "," << pos() << ","
<< (boundary() ? 1 : 0));
if (left) { // move to leftmost position
// if this is an RTL paragraph, and we're at the last row in the
// paragraph, move to lastpos
if (par.isRTL(buf.params()) && row.endpos() == lastpos())
pos() = lastpos();
else {
pos() = bidi.vis2log(row.pos());
TextMetrics const & tm = bv_->textMetrics(text());
// Looking for extremities is like clicking on the left or the
// right of the row.
int x = tm.origin().x_ + (left ? 0 : textRow().width());
bool b = false;
pos() = tm.getPosNearX(textRow(), x, b);
boundary(b);
// Moving to the leftmost position in the row,
// the cursor should normally be placed to the
// *left* of the leftmost position. A very
// common exception, though, is if the
// leftmost character also happens to be the
// separator at the (logical) end of the row
// --- in this case, the separator is
// positioned beyond the left margin, and we
// don't want to move the cursor there (moving
// to the left of the separator is equivalent
// to moving to the next line). So, in this
// case we actually want to place the cursor
// to the *right* of the leftmost position
// (the separator). Another exception is if
// we're moving to the logically last position
// in the row, which is *not* a separator:
// this means that the entire row has no
// separators (if there were any, the row
// would have been broken there); and
// therefore in this case we also move to the
// *right* of the last position (this
// indicates to the user that there is no
// space after this position, and is
// consistent with the behavior in the middle
// of a row --- moving right or left moves to
// the next/previous character; if we were to
// move to the *left* of this position, that
// would simulate a separator which is not
// really there!). Finally, there is an
// exception to the previous exception: if
// this non-separator-but-last-position-in-row
// is an inset, then we *do* want to stay to
// the left of it anyway: this is the
// "boundary" which we simulate at insets.
// Another exception is when row.endpos() is
// 0.
// do we want to be to the right of pos?
// as explained above, if at last pos in row, stay to the right
bool const right_of_pos = row.endpos() > 0
&& pos() == row.endpos() - 1 && !par.isInset(pos());
// Now we know if we want to be to the left or to the right of pos,
// let's make sure we are where we want to be.
bool const new_pos_is_RTL =
par.getFontSettings(buf.params(), pos()).isVisibleRightToLeft();
if (new_pos_is_RTL != right_of_pos) {
++pos();
boundary(true);
}
}
} else {
// move to rightmost position
// if this is an LTR paragraph, and we're at the last row in the
// paragraph, move to lastpos
if (!par.isRTL(buf.params()) && row.endpos() == lastpos())
pos() = lastpos();
else {
pos() = row.endpos() > 0 ? bidi.vis2log(row.endpos() - 1) : 0;
// Moving to the rightmost position in the
// row, the cursor should normally be placed
// to the *right* of the rightmost position. A
// very common exception, though, is if the
// rightmost character also happens to be the
// separator at the (logical) end of the row
// --- in this case, the separator is
// positioned beyond the right margin, and we
// don't want to move the cursor there (moving
// to the right of the separator is equivalent
// to moving to the next line). So, in this
// case we actually want to place the cursor
// to the *left* of the rightmost position
// (the separator). Another exception is if
// we're moving to the logically last position
// in the row, which is *not* a separator:
// this means that the entire row has no
// separators (if there were any, the row
// would have been broken there); and
// therefore in this case we also move to the
// *left* of the last position (this indicates
// to the user that there is no space after
// this position, and is consistent with the
// behavior in the middle of a row --- moving
// right or left moves to the next/previous
// character; if we were to move to the
// *right* of this position, that would
// simulate a separator which is not really
// there!). Finally, there is an exception to
// the previous exception: if this
// non-separator-but-last-position-in-row is
// an inset, then we *do* want to stay to the
// right of it anyway: this is the "boundary"
// which we simulate at insets. Another
// exception is when row.endpos() is 0.
// do we want to be to the left of pos?
// as explained above, if at last pos in row, stay to the left,
// unless the last position is the same as the first.
bool const left_of_pos = row.endpos() > 0
&& pos() == row.endpos() - 1 && !par.isInset(pos());
// Now we know if we want to be to the left or to the right of pos,
// let's make sure we are where we want to be.
bool const new_pos_is_RTL =
par.getFontSettings(buf.params(), pos()).isVisibleRightToLeft();
if (new_pos_is_RTL == left_of_pos) {
++pos();
boundary(true);
}
}
}
LYXERR(Debug::RTL, "leaving extremity: " << pit() << "," << pos() << ","
<< (boundary() ? 1 : 0));
}

View File

@ -24,6 +24,7 @@
#include "support/debug.h"
#include "support/lassert.h"
#include "support/lstrings.h"
#include "support/lyxalgo.h"
#include <ostream>
@ -32,8 +33,18 @@ using namespace std;
namespace lyx {
using support::rtrim;
using frontend::FontMetrics;
int Row::Element::countSeparators() const
{
if (type != STRING)
return 0;
return count(str.begin(), str.end(), ' ');
}
double Row::Element::pos2x(pos_type const i) const
{
// This can happen with inline completion when clicking on the
@ -52,7 +63,7 @@ double Row::Element::pos2x(pos_type const i) const
w = rtl ? full_width() : 0;
else {
FontMetrics const & fm = theFontMetrics(font);
w = fm.pos2x(str, i - pos, font.isVisibleRightToLeft());
w = fm.pos2x(str, i - pos, font.isVisibleRightToLeft(), extra);
}
return w;
@ -68,7 +79,7 @@ pos_type Row::Element::x2pos(int &x) const
switch (type) {
case STRING: {
FontMetrics const & fm = theFontMetrics(font);
i = fm.x2pos(str, x, rtl);
i = fm.x2pos(str, x, rtl, extra);
break;
}
case VIRTUAL:
@ -76,7 +87,6 @@ pos_type Row::Element::x2pos(int &x) const
i = 0;
x = rtl ? int(full_width()) : 0;
break;
case SEPARATOR:
case INSET:
case SPACE:
// those elements contain only one position. Round to
@ -96,24 +106,21 @@ pos_type Row::Element::x2pos(int &x) const
}
bool Row::Element::breakAt(int w)
bool Row::Element::breakAt(int w, bool force)
{
if (type != STRING || dim.wid <= w)
return false;
bool const rtl = font.isVisibleRightToLeft();
if (rtl)
w = dim.wid - w;
pos_type new_pos = x2pos(w);
if (new_pos == pos)
return false;
str = str.substr(0, new_pos - pos);
if (rtl)
dim.wid -= w;
else
dim.wid = w;
endpos = new_pos;
return true;
FontMetrics const & fm = theFontMetrics(font);
int x = w;
if(fm.breakAt(str, x, rtl, force)) {
dim.wid = x;
endpos = pos + str.length();
//lyxerr << "breakAt(" << w << ") Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl;
return true;
}
return false;
}
@ -225,9 +232,6 @@ ostream & operator<<(ostream & os, Row::Element const & e)
case Row::INSET:
os << "INSET: " << to_utf8(e.inset->layoutName()) << ", ";
break;
case Row::SEPARATOR:
os << "SEPARATOR: extra=" << e.extra << ", ";
break;
case Row::SPACE:
os << "SPACE: ";
break;
@ -258,6 +262,26 @@ ostream & operator<<(ostream & os, Row const & row)
}
int Row::countSeparators() const
{
int n = 0;
const_iterator const end = elements_.end();
for (const_iterator cit = elements_.begin() ; cit != end ; ++cit)
n += cit->countSeparators();
return n;
}
void Row::setSeparatorExtraWidth(double w)
{
separator = w;
iterator const end = elements_.end();
for (iterator it = elements_.begin() ; it != end ; ++it)
if (it->type == Row::STRING)
it->extra = w;
}
bool Row::sameString(Font const & f, Change const & ch) const
{
if (elements_.empty())
@ -278,6 +302,7 @@ void Row::finalizeLast()
elt.final = true;
if (elt.type == STRING) {
dim_.wid -= elt.dim.wid;
elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
dim_.wid += elt.dim.wid;
}
@ -304,8 +329,16 @@ void Row::add(pos_type const pos, char_type const c,
Element e(STRING, pos, f, ch);
elements_.push_back(e);
}
back().str += c;
back().endpos = pos + 1;
if (back().str.length() % 30 == 0) {
dim_.wid -= back().dim.wid;
back().str += c;
back().endpos = pos + 1;
back().dim.wid = theFontMetrics(back().font).width(back().str);
dim_.wid += back().dim.wid;
} else {
back().str += c;
back().endpos = pos + 1;
}
}
@ -323,18 +356,6 @@ void Row::addVirtual(pos_type const pos, docstring const & s,
}
void Row::addSeparator(pos_type const pos, char_type const c,
Font const & f, Change const & ch)
{
finalizeLast();
Element e(SEPARATOR, pos, f, ch);
e.str += c;
e.dim.wid = theFontMetrics(f).width(c);
elements_.push_back(e);
dim_.wid += e.dim.wid;
}
void Row::addSpace(pos_type const pos, int const width,
Font const & f, Change const & ch)
{
@ -357,33 +378,17 @@ void Row::shortenIfNeeded(pos_type const keep, int const w)
{
if (empty() || width() <= w)
return;
Elements::iterator const beg = elements_.begin();
Elements::iterator const end = elements_.end();
Elements::iterator last_sep = elements_.end();
int last_width = 0;
int wid = left_margin;
Elements::iterator cit = beg;
for ( ; cit != end ; ++cit) {
if (cit->type == SEPARATOR && cit->pos >= keep) {
last_sep = cit;
last_width = wid;
}
if (wid + cit->dim.wid > w)
if (cit->endpos >= keep && wid + cit->dim.wid > w)
break;
wid += cit->dim.wid;
}
if (last_sep != end) {
// We have found a suitable separator. This is the
// common case.
end_ = last_sep->endpos;
dim_.wid = last_width;
elements_.erase(last_sep, end);
return;
}
if (cit == end) {
// This should not happen since the row is too long.
LYXERR0("Something is wrong cannot shorten row: " << *this);
@ -397,23 +402,41 @@ void Row::shortenIfNeeded(pos_type const keep, int const w)
wid -= cit->dim.wid;
}
// Try to break this row cleanly (at word boundary)
if (cit->breakAt(w - wid, false)) {
end_ = cit->endpos;
// after breakAt, there may be spaces at the end of the
// string, but they are not counted in the string length
// (qtextlayout feature, actually). We remove them, but do not
// change the endo of the row, since the spaces at row break
// are invisible.
cit->str = rtrim(cit->str);
cit->endpos = cit->pos + cit->str.length();
dim_.wid = wid + cit->dim.wid;
// If there are other elements, they should be removed.
elements_.erase(next(cit, 1), end);
return;
}
if (cit != beg) {
// There is no separator, but several elements (probably
// insets) have been added. We can cut at this place.
// There is no separator, but several elements have been
// added. We can cut right here.
end_ = cit->pos;
dim_.wid = wid;
elements_.erase(cit, end);
return;
}
/* If we are here, it means that we have not found a separator
* to shorten the row. There is one case where we can do
* something: when we have one big string, maybe with some
* other things after it.
/* If we are here, it means that we have not found a separator to
* shorten the row. Let's try to break it again, but not at word
* boundary this time.
*/
if (cit->breakAt(w - left_margin)) {
if (cit->breakAt(w - wid, true)) {
end_ = cit->endpos;
dim_.wid = left_margin + cit->dim.wid;
// See comment above.
cit->str = rtrim(cit->str);
cit->endpos = cit->pos + cit->str.length();
dim_.wid = wid + cit->dim.wid;
// If there are other elements, they should be removed.
elements_.erase(next(cit, 1), end);
}

View File

@ -29,18 +29,6 @@ namespace lyx {
class DocIterator;
class Inset;
/**
* FIXME: Change Row object to operate only on integers and not doubles.
*
* This use of double is only useful to distribute the extra
* horizontal space between separators in justified text. If we do
* integer arithmetic, then it is possible to have two groups of
* separators, with size s or s+1. Then strings can be drawn without
* cutting at separators in justfied text, as it is done in
* non-justified text. This will improve performance.
*/
/**
* An on-screen row of text. A paragraph is broken into a RowList for
* display. Each Row contains a tokenized description of the contents
@ -58,8 +46,6 @@ public:
* correspond to any paragraph contents
*/
VIRTUAL,
// A stretchable space, basically
SEPARATOR,
// An inset
INSET,
// Some spacing described by its width, not a string
@ -76,7 +62,10 @@ public:
extra(0), font(f), change(ch), final(false) {}
// Return total width of element, including separator overhead
double full_width() const { return dim.wid + extra; };
double full_width() const { return dim.wid + extra * countSeparators(); };
// Return the number of separator in the element (only STRING type)
int countSeparators() const;
/** Return position in pixels (from the left) of position
* \param i in the row element.
*/
@ -86,10 +75,12 @@ public:
* adjusted to the actual pixel position.
*/
pos_type x2pos(int &x) const;
/** Break the element if possible, so that its width is
* less then \param w. Returns true on success.
/** Break the element if possible, so that its width is less
* than \param w. Returns true on success. When \param force
* is true, the string is cut at any place, other wise it
* respects the row breaking rules of characters.
*/
bool breakAt(int w);
bool breakAt(int w, bool force);
// Returns the position on left side of the element.
pos_type left_pos() const;
@ -109,10 +100,10 @@ public:
// Non-zero only if element is an inset
Inset const * inset;
// Only non-null for separator elements
// Only non-null for justified rows
double extra;
// Non-empty if element is a string or separator
// Non-empty if element is a string or is virtual
docstring str;
//
Font font;
@ -172,6 +163,11 @@ public:
///
int descent() const { return dim_.des; }
// Return the number of separators in the row
int countSeparators() const;
// Set the extra spacing for every separator in STRING elements
void setSeparatorExtraWidth(double w);
///
void add(pos_type pos, Inset const * ins, Dimension const & dim,
Font const & f, Change const & ch);
@ -182,9 +178,6 @@ public:
void addVirtual(pos_type pos, docstring const & s,
Font const & f, Change const & ch);
///
void addSeparator(pos_type pos, char_type const c,
Font const & f, Change const & ch);
///
void addSpace(pos_type pos, int width, Font const & f, Change const & ch);
///
@ -239,7 +232,7 @@ public:
friend std::ostream & operator<<(std::ostream & os, Row const & row);
/// width of a separator (i.e. space)
/// additional width for separators in justified rows (i.e. space)
double separator;
/// width of hfills in the label
double label_hfill;

View File

@ -64,8 +64,6 @@ RowPainter::RowPainter(PainterInfo & pi,
solid_line_thickness_(1), solid_line_offset_(1),
dotted_line_thickness_(1), dotted_line_offset_(2)
{
bidi_.computeTables(par_, pi_.base.bv->buffer(), row_);
if (lyxrc.zoom >= 200) {
// derive the line thickness from zoom factor
// the zoom is given in percent
@ -103,32 +101,33 @@ FontInfo RowPainter::labelFont() const
}
int RowPainter::leftMargin() const
{
return text_metrics_.leftMargin(text_metrics_.width(), pit_,
row_.pos());
}
// If you want to debug inset metrics uncomment the following line:
//#define DEBUG_METRICS
// This draws green lines around each inset.
void RowPainter::paintInset(Inset const * inset, pos_type const pos)
void RowPainter::paintInset(Inset const * inset, Font const & font,
Change const & change,
pos_type const pos)
{
Font const font = text_metrics_.displayFont(pit_, pos);
// Handle selection
bool const pi_selected = pi_.selected;
Cursor const & cur = pi_.base.bv->cursor();
if (cur.selection() && cur.text() == &text_
&& cur.normalAnchor().text() == &text_)
pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos;
LASSERT(inset, return);
// Backup full_repaint status because some insets (InsetTabular)
// requires a full repaint
bool const pi_full_repaint = pi_.full_repaint;
bool const pi_do_spellcheck = pi_.do_spellcheck;
Change const pi_change = pi_.change_;
pi_.base.font = inset->inheritFont() ? font.fontInfo() :
pi_.base.bv->buffer().params().getFont().fontInfo();
pi_.ltr_pos = (bidi_.level(pos) % 2 == 0);
Change prev_change = change_;
pi_.change_ = change_.changed() ? change_ : par_.lookupChange(pos);
pi_.ltr_pos = !font.isVisibleRightToLeft();
pi_.change_ = change_.changed() ? change_ : change;
pi_.do_spellcheck &= inset->allowSpellCheck();
int const x1 = int(x_);
@ -148,8 +147,9 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos)
// Restore full_repaint status.
pi_.full_repaint = pi_full_repaint;
pi_.change_ = prev_change;
pi_.change_ = pi_change;
pi_.do_spellcheck = pi_do_spellcheck;
pi_.selected = pi_selected;
#ifdef DEBUG_METRICS
int const x2 = x1 + dim.wid;
@ -163,14 +163,6 @@ void RowPainter::paintInset(Inset const * inset, pos_type const pos)
}
void RowPainter::paintSeparator(double orig_x, double width,
FontInfo const & font)
{
pi_.pain.textDecoration(font, int(orig_x), yo_, int(width));
x_ += width;
}
void RowPainter::paintForeignMark(double orig_x, Language const * lang, int desc) const
{
if (!lyxrc.mark_foreign_language)
@ -187,14 +179,12 @@ void RowPainter::paintForeignMark(double orig_x, Language const * lang, int desc
void RowPainter::paintMisspelledMark(double const orig_x,
docstring const & str, Font const & font,
pos_type const start_pos,
bool const changed) const
Row::Element const & e) const
{
// if changed the misspelled marker gets placed slightly lower than normal
// to avoid drawing at the same vertical offset
int const y = yo_ + solid_line_offset_ + solid_line_thickness_
+ (changed ? solid_line_thickness_ + 1 : 0)
+ (e.change.changed() ? solid_line_thickness_ + 1 : 0)
+ dotted_line_offset_;
//FIXME: this could be computed only once, it is probably not costly.
@ -211,8 +201,8 @@ void RowPainter::paintMisspelledMark(double const orig_x,
--cpos;
}
pos_type pos = start_pos;
while (pos < start_pos + pos_type(str.length())) {
pos_type pos = e.pos;
while (pos < e.pos + pos_type(e.str.length())) {
if (!par_.isMisspelled(pos)) {
++pos;
continue;
@ -227,12 +217,12 @@ void RowPainter::paintMisspelledMark(double const orig_x,
continue;
}
FontMetrics const & fm = theFontMetrics(font);
int x1 = fm.pos2x(str, range.first - start_pos,
font.isVisibleRightToLeft());
int x2 = fm.pos2x(str, min(range.last - start_pos + 1,
pos_type(str.length())),
font.isVisibleRightToLeft());
FontMetrics const & fm = theFontMetrics(e.font);
int x1 = fm.pos2x(e.str, range.first - e.pos,
e.font.isVisibleRightToLeft(), e.extra);
int x2 = fm.pos2x(e.str, min(range.last - e.pos + 1,
pos_type(e.str.length())),
e.font.isVisibleRightToLeft(), e.extra);
if (x1 > x2)
swap(x1, x2);
@ -244,120 +234,45 @@ void RowPainter::paintMisspelledMark(double const orig_x,
}
void RowPainter::paintStringAndSel(docstring const & str, Font const & font,
Change const & change,
pos_type start_pos, pos_type end_pos)
void RowPainter::paintStringAndSel(Row::Element const & e)
{
// at least part of text selected?
bool const some_sel = (end_pos >= row_.sel_beg && start_pos < row_.sel_end)
bool const some_sel = (e.endpos >= row_.sel_beg && e.pos < row_.sel_end)
|| pi_.selected;
// all the text selected?
bool const all_sel = (start_pos >= row_.sel_beg && end_pos < row_.sel_end)
bool const all_sel = (e.pos >= row_.sel_beg && e.endpos < row_.sel_end)
|| pi_.selected;
if (all_sel) {
Font copy = font;
Font copy = e.font;
copy.fontInfo().setPaintColor(Color_selectiontext);
x_ += pi_.pain.text(int(x_), yo_, str, copy);
} else if (change.changed()) {
Font copy = font;
copy.fontInfo().setPaintColor(change.color());
x_ += pi_.pain.text(int(x_), yo_, str, copy);
pi_.pain.text(int(x_), yo_, e.str, copy, e.extra);
} else if (e.change.changed()) {
Font copy = e.font;
copy.fontInfo().setPaintColor(e.change.color());
pi_.pain.text(int(x_), yo_, e.str, copy, e.extra);
} else if (!some_sel) {
x_ += pi_.pain.text(int(x_), yo_, str, font);
pi_.pain.text(int(x_), yo_, e.str, e.font, e.extra);
} else {
x_ += pi_.pain.text(int(x_), yo_, str, font, Color_selectiontext,
max(row_.sel_beg, start_pos) - start_pos,
min(row_.sel_end, end_pos) - start_pos);
pi_.pain.text(int(x_), yo_, e.str, e.font, Color_selectiontext,
max(row_.sel_beg, e.pos) - e.pos,
min(row_.sel_end, e.endpos) - e.pos, e.extra);
}
x_ += e.full_width();
}
void RowPainter::paintFromPos(pos_type & vpos, bool changed)
void RowPainter::paintChange(double orig_x, Font const & font,
Change const & change) const
{
pos_type pos = bidi_.vis2log(vpos);
pos_type start_pos = pos;
// first character
docstring str;
str.reserve(100);
char_type const c = par_.getChar(pos);
str.push_back(c);
double const orig_x = x_;
Font const font = text_metrics_.displayFont(pit_, pos);
FontSpan const font_span = par_.fontSpan(pos);
// Track-change status.
Change const & change_running = par_.lookupChange(pos);
// collect as much similar chars as we can
pos_type const end = row_.endpos();
for (++vpos ; vpos < end ; ++vpos) {
pos = bidi_.vis2log(vpos);
if (!font_span.contains(pos))
break;
Change const & change = par_.lookupChange(pos);
if (!change_running.isSimilarTo(change))
// Track change type or author has changed.
break;
char_type const c = par_.getChar(pos);
if (c == '\t')
break;
if (!isPrintableNonspace(c))
break;
str.push_back(c);
}
// Make pos point to the last character in the string.
// Using "pos = bidi_.vis2log(vpos)" does not work for some reason.
if (vpos < end)
pos = bidi_.vis2log(vpos - 1);
// Now make pos point to the position _after_ the string.
// Using vis2log for that is not a good idea in general, we
// want logical ordering.
if (font.isVisibleRightToLeft())
--pos;
else
++pos;
if (str[0] == '\t')
str.replace(0,1,from_ascii(" "));
/* Because we do our own bidi, at this point the strings are
* already in visual order. However, Qt also applies its own
* bidi algorithm to strings that it paints to the screen.
* Therefore, if we were to paint Hebrew/Arabic words as a
* single string, the letters in the words would get reversed
* again. In order to avoid that, we reverse our string.
* See also http://thread.gmane.org/gmane.editors.lyx.devel/79740
* for an earlier thread on the subject
*/
if (font.isVisibleRightToLeft()) {
reverse(str.begin(), str.end());
// If the string is reversed, the positions need to be adjusted
++pos;
++start_pos;
swap(start_pos, pos);
}
// Actually paint the text, taking care about the selection
paintStringAndSel(str, font, change_running, start_pos, pos);
// The line that indicates word in a different language
paintForeignMark(orig_x, font.language());
// Paint the spelling mark if needed.
if (lyxrc.spellcheck_continuously && pi_.do_spellcheck
&& par_.isMisspelled(start_pos)) {
paintMisspelledMark(orig_x, str, font, start_pos, changed);
}
if (!change.changed())
return;
// Calculate 1/3 height of font
FontMetrics const & fm = theFontMetrics(font);
int const y_bar = change.deleted() ? yo_ - fm.maxAscent() / 3
: yo_ + 2 * solid_line_offset_ + solid_line_thickness_;
pi_.pain.line(int(orig_x), y_bar, int(x_), y_bar,
change.color(), Painter::line_solid, solid_line_thickness_);
}
@ -535,13 +450,10 @@ void RowPainter::paintLabel() const
FontMetrics const & fm = theFontMetrics(font);
double x = x_;
if (is_rtl) {
x = width_ - leftMargin()
+ fm.width(layout.labelsep);
} else {
x = x_ - fm.width(layout.labelsep)
- fm.width(str);
}
if (is_rtl)
x = width_ - row_.right_margin + fm.width(layout.labelsep);
else
x = x_ - fm.width(layout.labelsep) - fm.width(str);
pi_.pain.text(int(x), yo_, str, font);
}
@ -575,12 +487,10 @@ void RowPainter::paintTopLevelLabel() const
double x = x_;
if (layout.labeltype == LABEL_CENTERED) {
if (is_rtl)
x = leftMargin();
x += (width_ - text_metrics_.rightMargin(pm_) - leftMargin()) / 2;
x = row_.left_margin + (width_ - row_.left_margin - row_.right_margin) / 2;
x -= fm.width(str) / 2;
} else if (is_rtl) {
x = width_ - leftMargin() - fm.width(str);
x = width_ - row_.right_margin - fm.width(str);
}
pi_.pain.text(int(x), yo_ - maxdesc - labeladdon, str, font);
}
@ -655,7 +565,7 @@ void RowPainter::paintLast()
int const y = yo_ - size;
int const max_row_width = width_ - size - Inset::TEXT_TO_INSET_OFFSET;
int x = is_rtl ? nestMargin() + changebarMargin()
: max_row_width - text_metrics_.rightMargin(pm_);
: max_row_width - row_.right_margin;
// If needed, move the box a bit to avoid overlapping with text.
int const rem = max_row_width - row_.width();
@ -679,13 +589,6 @@ void RowPainter::paintLast()
}
case END_LABEL_NO_LABEL:
if (lyxrc.paragraph_markers && size_type(pit_ + 1) < pars_.size()) {
docstring const s = docstring(1, char_type(0x00B6));
FontInfo f = FontInfo(text_.layoutFont(pit_));
f.setColor(Color_paragraphmarker);
pi_.pain.text(int(x_), yo_, s, f);
x_ += theFontMetrics(f).width(s);
}
break;
}
}
@ -693,194 +596,65 @@ void RowPainter::paintLast()
void RowPainter::paintOnlyInsets()
{
CoordCache const & cache = pi_.base.bv->coordCache();
pos_type const end = row_.endpos();
for (pos_type pos = row_.pos(); pos != end; ++pos) {
// If outer row has changed, nested insets are repaint completely.
Inset const * inset = par_.getInset(pos);
bool const nested_inset = inset &&
((inset->asInsetMath() &&
!inset->asInsetMath()->asMacroTemplate())
|| inset->asInsetText()
|| inset->asInsetTabular());
if (!nested_inset)
continue;
if (x_ > pi_.base.bv->workWidth()
|| !cache.getInsets().has(inset))
continue;
x_ = cache.getInsets().x(inset);
bool const pi_selected = pi_.selected;
Cursor const & cur = pi_.base.bv->cursor();
if (cur.selection() && cur.text() == &text_
&& cur.normalAnchor().text() == &text_)
pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos;
paintInset(inset, pos);
pi_.selected = pi_selected;
Row::const_iterator cit = row_.begin();
Row::const_iterator const & end = row_.end();
for ( ; cit != end ; ++cit) {
Row::Element const & e = *cit;
if (e.type == Row::INSET) {
// If outer row has changed, nested insets are repainted completely.
pi_.base.bv->coordCache().insets().add(e.inset, int(x_), yo_);
bool const nested_inset =
(e.inset->asInsetMath() && !e.inset->asInsetMath()->asMacroTemplate())
|| e.inset->asInsetText() || e.inset->asInsetTabular();
if (!nested_inset) {
x_ += e.full_width();
continue;
}
paintInset(e.inset, e.font, e.change, e.pos);
} else
x_ += e.full_width();
}
}
void RowPainter::paintText()
{
//LYXERR0("-------------------------------------------------------");
pos_type const end = row_.endpos();
// Spaces at logical line breaks in bidi text must be skipped during
// painting. However, they may appear visually in the middle
// of a row; they must be skipped, wherever they are...
// * logically "abc_[HEBREW_\nHEBREW]"
// * visually "abc_[_WERBEH\nWERBEH]"
pos_type skipped_sep_vpos = -1;
pos_type body_pos = par_.beginOfBody();
if (body_pos > 0 &&
(body_pos > end || !par_.isLineSeparator(body_pos - 1))) {
body_pos = 0;
}
Row::const_iterator cit = row_.begin();
Row::const_iterator const & end = row_.end();
for ( ; cit != end ; ++cit) {
double const orig_x = x_;
Row::Element const & e = *cit;
int foreign_descent = 0;
Layout const & layout = par_.layout();
switch (e.type) {
case Row::STRING:
case Row::VIRTUAL:
paintStringAndSel(e);
Change change_running;
int change_last_x = 0;
// check for possible inline completion
DocIterator const & inlineCompletionPos = pi_.base.bv->inlineCompletionPos();
pos_type inlineCompletionVPos = -1;
if (inlineCompletionPos.inTexted()
&& inlineCompletionPos.text() == &text_
&& inlineCompletionPos.pit() == pit_
&& inlineCompletionPos.pos() - 1 >= row_.pos()
&& inlineCompletionPos.pos() - 1 < row_.endpos()) {
// draw logically behind the previous character
inlineCompletionVPos = bidi_.log2vis(inlineCompletionPos.pos() - 1);
}
// Use font span to speed things up, see below
FontSpan font_span;
Font font;
// If the last logical character is a separator, don't paint it, unless
// it's in the last row of a paragraph; see skipped_sep_vpos declaration
if (end > 0 && end < par_.size() && par_.isSeparator(end - 1))
skipped_sep_vpos = bidi_.log2vis(end - 1);
for (pos_type vpos = row_.pos(); vpos < end; ) {
if (x_ > pi_.base.bv->workWidth())
// Paint the spelling mark if needed.
if (lyxrc.spellcheck_continuously && pi_.do_spellcheck
&& par_.isMisspelled(e.pos)) {
paintMisspelledMark(orig_x, e);
}
break;
// Skip the separator at the logical end of the row
if (vpos == skipped_sep_vpos) {
++vpos;
continue;
}
pos_type const pos = bidi_.vis2log(vpos);
if (pos >= par_.size()) {
++vpos;
continue;
}
// Use font span to speed things up, see above
if (!font_span.contains(pos)) {
font_span = par_.fontSpan(pos);
font = text_metrics_.displayFont(pit_, pos);
// split font span if inline completion is inside
if (inlineCompletionVPos != -1
&& font_span.contains(inlineCompletionPos.pos()))
font_span.last = inlineCompletionPos.pos();
}
// Note that this value will only be used in
// situations where no ligature of composition of
// characters is needed. (see comments in uses of width_pos).
const int width_pos = pm_.singleWidth(pos, font);
Change const & change = par_.lookupChange(pos);
if (change.changed() && !change_running.changed()) {
change_running = change;
change_last_x = int(x_);
}
Inset const * inset = par_.getInset(pos);
bool const highly_editable_inset = inset
&& inset->editable();
// If we reach the end of a change or if the author changes, paint it.
// We also don't paint across things like tables
if (change_running.changed() && (highly_editable_inset
|| !change.changed() || !change_running.isSimilarTo(change))) {
// Calculate 1/3 height of the buffer's default font
FontMetrics const & fm
= theFontMetrics(pi_.base.bv->buffer().params().getFont());
int const y_bar = change_running.deleted() ?
yo_ - fm.maxAscent() / 3 : yo_ + 2 * solid_line_offset_ + solid_line_thickness_;
pi_.pain.line(change_last_x, y_bar, int(x_), y_bar,
change_running.color(), Painter::line_solid, solid_line_thickness_);
// Change might continue with a different author or type
if (change.changed() && !highly_editable_inset) {
change_running = change;
change_last_x = int(x_);
} else
change_running.setUnchanged();
}
if (body_pos > 0 && pos == body_pos - 1) {
int const lwidth = theFontMetrics(labelFont())
.width(layout.labelsep);
// width_pos is either the width of a space or an inset
x_ += row_.label_hfill + lwidth - width_pos;
}
// Is the inline completion in front of character?
if (font.isRightToLeft() && vpos == inlineCompletionVPos)
paintInlineCompletion(font);
if (par_.isSeparator(pos)) {
Font const orig_font = text_metrics_.displayFont(pit_, pos);
double const orig_x = x_;
// width_pos is the width of a space
double separator_width = width_pos;
if (pos >= body_pos)
separator_width += row_.separator;
paintSeparator(orig_x, separator_width, orig_font.fontInfo());
paintForeignMark(orig_x, orig_font.language());
++vpos;
} else if (inset) {
case Row::INSET: {
// If outer row has changed, nested insets are repaint completely.
pi_.base.bv->coordCache().insets().add(inset, int(x_), yo_);
bool const pi_selected = pi_.selected;
Cursor const & cur = pi_.base.bv->cursor();
if (cur.selection() && cur.text() == &text_
&& cur.normalAnchor().text() == &text_)
pi_.selected = row_.sel_beg <= pos && row_.sel_end > pos;
paintInset(inset, pos);
pi_.selected = pi_selected;
++vpos;
} else {
// paint as many characters as possible.
paintFromPos(vpos, change_running.changed());
pi_.base.bv->coordCache().insets().add(e.inset, int(x_), yo_);
paintInset(e.inset, e.font, e.change, e.pos);
foreign_descent = e.dim.descent();
}
break;
case Row::SPACE:
pi_.pain.textDecoration(e.font.fontInfo(), int(x_), yo_, int(e.full_width()));
x_ += e.full_width();
}
// Is the inline completion after character?
if (!font.isRightToLeft() && vpos - 1 == inlineCompletionVPos)
paintInlineCompletion(font);
}
// The line that indicates word in a different language
paintForeignMark(orig_x, e.font.language(), foreign_descent);
// if we reach the end of a struck out range, paint it
if (change_running.changed()) {
FontMetrics const & fm
= theFontMetrics(pi_.base.bv->buffer().params().getFont());
int const y_bar = change_running.deleted() ?
yo_ - fm.maxAscent() / 3 : yo_ + 2 * solid_line_offset_ + solid_line_thickness_;
pi_.pain.line(change_last_x, y_bar, int(x_), y_bar,
change_running.color(), Painter::line_solid, solid_line_thickness_);
change_running.setUnchanged();
// change tracking (not for insets that track their own changes)
if (e.type != Row::INSET || ! e.inset->canTrackChanges())
paintChange(orig_x, e.font, e.change);
}
}
@ -983,38 +757,4 @@ void RowPainter::paintSelection() const
}
void RowPainter::paintInlineCompletion(Font const & font)
{
docstring completion = pi_.base.bv->inlineCompletion();
FontInfo f = font.fontInfo();
bool rtl = font.isRightToLeft();
// draw the unique and the non-unique completion part
// Note: this is not time-critical as it is
// only done once per screen.
size_t uniqueTo = pi_.base.bv->inlineCompletionUniqueChars();
docstring s1 = completion.substr(0, uniqueTo);
docstring s2 = completion.substr(uniqueTo);
ColorCode c1 = Color_inlinecompletion;
ColorCode c2 = Color_nonunique_inlinecompletion;
// right to left?
if (rtl) {
swap(s1, s2);
swap(c1, c2);
}
if (!s1.empty()) {
f.setColor(c1);
pi_.pain.text(int(x_), yo_, s1, f);
x_ += theFontMetrics(font).width(s1);
}
if (!s2.empty()) {
f.setColor(c2);
pi_.pain.text(int(x_), yo_, s2, f);
x_ += theFontMetrics(font).width(s2);
}
}
} // namespace lyx

View File

@ -14,8 +14,8 @@
#ifndef ROWPAINTER_H
#define ROWPAINTER_H
#include "Bidi.h"
#include "Changes.h"
#include "Row.h"
#include "support/types.h"
@ -30,27 +30,11 @@ class PainterInfo;
class Paragraph;
class ParagraphList;
class ParagraphMetrics;
class Row;
class Text;
class TextMetrics;
namespace frontend { class Painter; }
/**
* FIXME: Re-implement row painting using row elements.
*
* This is not difficult in principle, but the code is intricate and
* needs some careful analysis. The first thing that needs to be done
* is to break row elements with the same criteria. Currently breakRow
* does not consider on-the-fly spell-checking, but it is not clear to
* me that it is required. Moreover, this thing would only work if we
* are sure that the Row object is up-to-date when drawing happens.
* This depends on the update machinery.
*
* This would allow to get rid of the Bidi class.
*/
/**
* A class used for painting an individual row of text.
* FIXME: get rid of that class.
@ -74,21 +58,14 @@ public:
void paintSelection() const;
private:
void paintSeparator(double orig_x, double width, FontInfo const & font);
void paintSeparator(double width, Font const & font);
void paintForeignMark(double orig_x, Language const * lang, int desc = 0) const;
void paintStringAndSel(docstring const & str, Font const & font,
Change const & change,
pos_type start_pos, pos_type end_pos);
void paintMisspelledMark(double orig_x,
docstring const & str, Font const & font,
pos_type pos, bool changed) const;
void paintStringAndSel(Row::Element const & e);
void paintMisspelledMark(double orig_x, Row::Element const & e) const;
void paintChange(double orig_x , Font const & font, Change const & change) const;
int paintAppendixStart(int y) const;
void paintFromPos(pos_type & vpos, bool changed);
void paintInset(Inset const * inset, pos_type const pos);
void paintInlineCompletion(Font const & font);
/// return left margin
int leftMargin() const;
void paintInset(Inset const * inset, Font const & font,
Change const & change, pos_type const pos);
/// return the label font for this row
FontInfo labelFont() const;
@ -115,9 +92,6 @@ private:
Paragraph const & par_;
ParagraphMetrics const & pm_;
/// bidi cache
Bidi bidi_;
/// row changed? (change tracking)
Change const change_;

View File

@ -58,28 +58,6 @@ using frontend::FontMetrics;
namespace {
int numberOfSeparators(Row const & row)
{
int n = 0;
Row::const_iterator cit = row.begin();
Row::const_iterator const end = row.end();
for ( ; cit != end ; ++cit)
if (cit->type == Row::SEPARATOR)
++n;
return n;
}
void setSeparatorWidth(Row & row, double w)
{
row.separator = w;
Row::iterator it = row.begin();
Row::iterator const end = row.end();
for ( ; it != end ; ++it)
if (it->type == Row::SEPARATOR)
it->extra = w;
}
int numberOfLabelHfills(Paragraph const & par, Row const & row)
{
@ -569,12 +547,6 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
// FIXME: put back this assertion when the crash on new doc is solved.
//LASSERT(w >= 0, /**/);
bool const is_rtl = text_->isRTL(par);
if (is_rtl)
row.left_margin = rightMargin(pit);
else
row.left_margin = leftMargin(max_width_, pit, row.pos());
// is there a manual margin with a manual label
Layout const & layout = par.layout();
@ -609,15 +581,15 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
// set x how you need it
switch (getAlign(par, row.pos())) {
case LYX_ALIGN_BLOCK: {
int const ns = numberOfSeparators(row);
int const ns = row.countSeparators();
/** If we have separators, and this row has
* not be broken abruptly by a display inset
* or newline, then stretch it */
if (ns && !row.right_boundary()
&& row.endpos() != par.size()) {
setSeparatorWidth(row, double(w) / ns);
row.setSeparatorExtraWidth(double(w) / ns);
row.dimension().wid = width;
} else if (is_rtl) {
} else if (text_->isRTL(par)) {
row.dimension().wid = width;
row.left_margin += w;
}
@ -785,13 +757,19 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
Paragraph const & par = text_->getPar(pit);
pos_type const end = par.size();
pos_type const pos = row.pos();
int const width = max_width_ - right_margin;
pos_type const body_pos = par.beginOfBody();
bool const is_rtl = text_->isRTL(par);
row.clear();
// This make get changed in computeRowMetrics depending on RTL
row.left_margin = leftMargin(max_width_, pit, pos);
row.dimension().wid = row.left_margin;
row.right_margin = right_margin;
if (is_rtl)
swap(row.left_margin, row.right_margin);
// Remember that the row width takes into account the left_margin
// but not the right_margin.
row.dimension().wid = row.left_margin;
// the width available for the row.
int const width = max_width_ - row.right_margin;
if (pos >= end || row.width() > width) {
row.endpos(end);
@ -819,7 +797,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
// or the end of the par, then build a representation of the row.
pos_type i = pos;
FontIterator fi = FontIterator(*this, par, pit, pos);
while (i < end && row.width() < width) {
while (i < end && row.width() <= width) {
char_type c = par.getChar(i);
// The most special cases are handled first.
if (par.isInset(i)) {
@ -837,13 +815,6 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
int const add = max(fm.width(par.layout().labelsep),
labelEnd(pit) - row.width());
row.addSpace(i, add, *fi, par.lookupChange(i));
} else if (par.isLineSeparator(i)) {
// In theory, no inset has this property. If
// this is done, a new addSeparator which
// takes an inset as parameter should be
// added.
LATTEST(!par.isInset(i));
row.addSeparator(i, c, *fi, par.lookupChange(i));
} else if (c == '\t')
row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")),
*fi, par.lookupChange(i));
@ -904,7 +875,8 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
row.shortenIfNeeded(body_pos, width);
// make sure that the RTL elements are in reverse ordering
row.reverseRTL(text_->isRTL(par));
row.reverseRTL(is_rtl);
//LYXERR0("breakrow: row is " << row);
}
@ -1090,6 +1062,7 @@ void TextMetrics::setRowHeight(Row & row, pit_type const pit,
pos_type TextMetrics::getPosNearX(Row const & row, int & x,
bool & boundary) const
{
//LYXERR0("getPosNearX(" << x << ") row=" << row);
/// For the main Text, it is possible that this pit is not
/// yet in the CoordCache when moving cursor up.
/// x Paragraph coordinate is always 0 for main text anyway.
@ -1145,6 +1118,7 @@ pos_type TextMetrics::getPosNearX(Row const & row, int & x,
boundary = true;
x += xo;
//LYXERR0("getPosNearX ==> pos=" << pos << ", boundary=" << boundary);
return pos;
}

View File

@ -84,15 +84,27 @@ public:
* return the x offset of a position in the string. The
* direction of the string is forced, and the returned value
* is from the left edge of the word, not from the start of the string.
* \param rtl is true for right-to-left layout
* \param ws is the amount of extra inter-word space applied text justication.
*/
virtual int pos2x(docstring const & s, int pos, bool rtl) const = 0;
virtual int pos2x(docstring const & s, int pos, bool rtl, double ws) const = 0;
/**
* return the position in the string for a given x offset. The
* direction of the string is forced, and the returned value
* is from the left edge of the word, not from the start of the string.
* the offset x is updated to match the closest position in the string.
* \param rtl is true for right-to-left layout
* \param ws is the amount of extra inter-word space applied text justication.
*/
virtual int x2pos(docstring const & s, int & x, bool rtl) const = 0;
virtual int x2pos(docstring const & s, int & x, bool rtl, double ws) const = 0;
/**
* Break string at width at most x.
* \return true if successful
* \param rtl is true for right-to-left layout
* \param force is false for breaking at word separator, true for
* arbitrary position.
*/
virtual bool breakAt(docstring & s, int & x, bool rtl, bool force) const = 0;
/// return char dimension for the font.
virtual Dimension const dimension(char_type c) const = 0;
/**

View File

@ -120,13 +120,15 @@ public:
* text direction is given by \c rtl.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, FontInfo const & f, bool rtl = false) = 0;
virtual int text(int x, int y, docstring const & str, FontInfo const & f,
bool rtl = false, double wordspacing = 0.0) = 0;
/** draw a string at position x, y (y is the baseline). The
* text direction is enforced by the \c Font.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f) = 0;
virtual int text(int x, int y, docstring const & str, Font const & f,
double wordspacing = 0.0) = 0;
/** draw a string at position x, y (y is the baseline), but
* make sure that the part between \c from and \c to is in
@ -134,7 +136,8 @@ public:
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f,
Color other, size_type from, size_type to) = 0;
Color other, size_type from, size_type to,
double const wordspacing) = 0;
void setDrawingEnabled(bool drawing_enabled)
{ drawing_enabled_ = drawing_enabled; }

View File

@ -150,10 +150,11 @@ int GuiFontMetrics::signedWidth(docstring const & s) const
}
namespace {
void setTextLayout(QTextLayout & tl, docstring const & s, QFont const & font,
bool const rtl)
void setTextLayout(QTextLayout & tl, docstring const & s, QFont font,
bool const rtl, double const wordspacing)
{
tl.setText(toqstr(s));
font.setWordSpacing(wordspacing);
tl.setFont(font);
// Note that both setFlags and the enums are undocumented
tl.setFlags(rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight);
@ -164,18 +165,22 @@ void setTextLayout(QTextLayout & tl, docstring const & s, QFont const & font,
}
int GuiFontMetrics::pos2x(docstring const & s, int const pos, bool const rtl) const
int GuiFontMetrics::pos2x(docstring const & s, int const pos, bool const rtl,
double const wordspacing) const
{
QTextLayout tl;
setTextLayout(tl, s, font_, rtl);
QFont copy = font_;
copy.setWordSpacing(wordspacing);
setTextLayout(tl, s, font_, rtl, wordspacing);
return static_cast<int>(tl.lineForTextPosition(pos).cursorToX(pos));
}
int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl) const
int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl,
double const wordspacing) const
{
QTextLayout tl;
setTextLayout(tl, s, font_, rtl);
setTextLayout(tl, s, font_, rtl, wordspacing);
int pos = tl.lineForTextPosition(0).xToCursor(x);
// correct x value to the actual cursor position.
x = static_cast<int>(tl.lineForTextPosition(0).cursorToX(pos));
@ -183,6 +188,31 @@ int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl) const
}
bool GuiFontMetrics::breakAt(docstring & s, int & x, bool const rtl, bool const force) const
{
if (s.empty())
return false;
QTextLayout tl;
tl.setText(toqstr(s));
tl.setFont(font_);
// Note that both setFlags and the enums are undocumented
tl.setFlags(rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight);
QTextOption to;
to.setWrapMode(force ? QTextOption::WrapAnywhere : QTextOption::WordWrap);
tl.setTextOption(to);
tl.beginLayout();
QTextLine line = tl.createLine();
line.setLineWidth(x);
tl.createLine();
tl.endLayout();
if (int(line.naturalTextWidth()) > x)
return false;
x = int(line.naturalTextWidth());
s = s.substr(0, line.textLength());
return true;
}
void GuiFontMetrics::rectText(docstring const & str,
int & w, int & ascent, int & descent) const
{

View File

@ -43,8 +43,9 @@ public:
virtual int rbearing(char_type c) const;
virtual int width(docstring const & s) const;
virtual int signedWidth(docstring const & s) const;
virtual int pos2x(docstring const & s, int pos, bool rtl) const;
virtual int x2pos(docstring const & s, int & x, bool rtl) const;
virtual int pos2x(docstring const & s, int pos, bool rtl, double ws) const;
virtual int x2pos(docstring const & s, int & x, bool rtl, double ws) const;
virtual bool breakAt(docstring & s, int & x, bool rtl, bool force) const;
virtual Dimension const dimension(char_type c) const;
virtual void rectText(docstring const & str,

View File

@ -290,7 +290,8 @@ int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
int GuiPainter::text(int x, int y, docstring const & s,
FontInfo const & f, bool const rtl)
FontInfo const & f, bool const rtl,
double const wordspacing)
{
//LYXERR0("text: x=" << x << ", s=" << s);
if (s.empty())
@ -316,7 +317,8 @@ int GuiPainter::text(int x, int y, docstring const & s,
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
@ -418,14 +420,16 @@ int GuiPainter::text(int x, int y, docstring const & s,
}
int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
double const wordspacing)
{
return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
}
int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
Color other, size_type from, size_type to)
Color other, size_type const from, size_type const to,
double const wordspacing)
{
GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
FontInfo fi = f.fontInfo();
@ -434,8 +438,8 @@ int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
// 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, rtl, wordspacing);
int xmax = fm.pos2x(str, to, rtl, wordspacing);
if (xmin > xmax)
swap(xmin, xmax);
@ -444,7 +448,7 @@ int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
fi.setPaintColor(other);
QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
setClipRegion(clip);
int const textwidth = text(x, y, str, fi, rtl);
int const textwidth = text(x, y, str, fi, rtl, wordspacing);
// Then the part in normal color
// Note that in Qt5, it is not possible to use Qt::UniteClip,
@ -452,7 +456,7 @@ int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
fi.setPaintColor(orig);
QRegion region(viewport());
setClipRegion(region - clip);
text(x, y, str, fi, rtl);
text(x, y, str, fi, rtl, wordspacing);
setClipping(false);
return textwidth;

View File

@ -91,13 +91,15 @@ public:
* text direction is given by \c rtl.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, FontInfo const & f, bool rtl = false);
virtual int text(int x, int y, docstring const & str, FontInfo const & f,
bool rtl = false, double wordspacing = 0.0);
/** draw a string at position x, y (y is the baseline). The
* text direction is enforced by the \c Font.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f);
virtual int text(int x, int y, docstring const & str, Font const & f,
double wordspacing = 0.0);
/** draw a string at position x, y (y is the baseline), but
* make sure that the part between \c from and \c to is in
@ -105,7 +107,8 @@ public:
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f,
Color other, size_type from, size_type to);
Color other, size_type from, size_type to,
double const wordspacing);
/// draw a char at position x, y (y is the baseline)
virtual int text(int x, int y, char_type c, FontInfo const & f);