Do not break row elements at spaces

The goal of this commit is to make painting faster by reducing the
number of strings to paint. To this end, it is necessary to include
spaces in row elements.

Also importantly, this commit should fix existing problems with line
breaking in chinese text.

* TextMetrics::breakRow: do not do anything special for word separators.

* Row::add: when adding a character to a row element, keep the string
  width updated. If need be, it is possible to tweak this by updating
  every 10 characters, for example.

* GuiFontMetrics::breakAt (new): use QTextLayout to break text either
  at word boundary or at an arbitrary width.

* Row::Element::breakAt: use the above method.

* Row::shortenIfNeeded: simplify now that because there is no need for
  handling separator elements. This will be taken care of by the
  improved breakAt.

Two things remain to be done:

* remove all traces of separator row element

* re-implement text justification.
This commit is contained in:
Jean-Marc Lasgouttes 2015-07-19 01:22:10 +02:00
parent 038f003be6
commit f65f3adbf7
6 changed files with 84 additions and 49 deletions

View File

@ -24,6 +24,7 @@
#include "support/debug.h" #include "support/debug.h"
#include "support/lassert.h" #include "support/lassert.h"
#include "support/lstrings.h"
#include "support/lyxalgo.h" #include "support/lyxalgo.h"
#include <ostream> #include <ostream>
@ -32,6 +33,7 @@ using namespace std;
namespace lyx { namespace lyx {
using support::rtrim;
using frontend::FontMetrics; using frontend::FontMetrics;
double Row::Element::pos2x(pos_type const i) const double Row::Element::pos2x(pos_type const i) const
@ -96,24 +98,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) if (type != STRING || dim.wid <= w)
return false; return false;
bool const rtl = font.isVisibleRightToLeft(); bool const rtl = font.isVisibleRightToLeft();
if (rtl) FontMetrics const & fm = theFontMetrics(font);
w = dim.wid - w; int x = w;
pos_type new_pos = x2pos(w); if(fm.breakAt(str, x, rtl, force)) {
if (new_pos == pos) dim.wid = x;
return false; endpos = pos + str.length();
str = str.substr(0, new_pos - pos); //lyxerr << "breakAt(" << w << ") Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl;
if (rtl)
dim.wid -= w;
else
dim.wid = w;
endpos = new_pos;
return true; return true;
}
return false;
} }
@ -278,6 +277,7 @@ void Row::finalizeLast()
elt.final = true; elt.final = true;
if (elt.type == STRING) { if (elt.type == STRING) {
dim_.wid -= elt.dim.wid;
elt.dim.wid = theFontMetrics(elt.font).width(elt.str); elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
dim_.wid += elt.dim.wid; dim_.wid += elt.dim.wid;
} }
@ -304,8 +304,11 @@ void Row::add(pos_type const pos, char_type const c,
Element e(STRING, pos, f, ch); Element e(STRING, pos, f, ch);
elements_.push_back(e); elements_.push_back(e);
} }
dim_.wid -= back().dim.wid;
back().str += c; back().str += c;
back().endpos = pos + 1; back().endpos = pos + 1;
back().dim.wid = theFontMetrics(back().font).width(back().str);
dim_.wid += back().dim.wid;
} }
@ -357,33 +360,17 @@ void Row::shortenIfNeeded(pos_type const keep, int const w)
{ {
if (empty() || width() <= w) if (empty() || width() <= w)
return; return;
Elements::iterator const beg = elements_.begin(); Elements::iterator const beg = elements_.begin();
Elements::iterator const end = elements_.end(); Elements::iterator const end = elements_.end();
Elements::iterator last_sep = elements_.end();
int last_width = 0;
int wid = left_margin; int wid = left_margin;
Elements::iterator cit = beg; Elements::iterator cit = beg;
for ( ; cit != end ; ++cit) { for ( ; cit != end ; ++cit) {
if (cit->type == SEPARATOR && cit->pos >= keep) { if (cit->endpos >= keep && wid + cit->dim.wid > w)
last_sep = cit;
last_width = wid;
}
if (wid + cit->dim.wid > w)
break; break;
wid += cit->dim.wid; 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) { if (cit == end) {
// This should not happen since the row is too long. // This should not happen since the row is too long.
LYXERR0("Something is wrong cannot shorten row: " << *this); LYXERR0("Something is wrong cannot shorten row: " << *this);
@ -397,23 +384,41 @@ void Row::shortenIfNeeded(pos_type const keep, int const w)
wid -= cit->dim.wid; 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) { if (cit != beg) {
// There is no separator, but several elements (probably // There is no separator, but several elements have been
// insets) have been added. We can cut at this place. // added. We can cut right here.
end_ = cit->pos; end_ = cit->pos;
dim_.wid = wid; dim_.wid = wid;
elements_.erase(cit, end); elements_.erase(cit, end);
return; return;
} }
/* If we are here, it means that we have not found a separator /* If we are here, it means that we have not found a separator to
* to shorten the row. There is one case where we can do * shorten the row. Let's try to break it again, but not at word
* something: when we have one big string, maybe with some * boundary this time.
* other things after it.
*/ */
if (cit->breakAt(w - left_margin)) { if (cit->breakAt(w - wid, true)) {
end_ = cit->endpos; 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. // If there are other elements, they should be removed.
elements_.erase(next(cit, 1), end); elements_.erase(next(cit, 1), end);
} }

View File

@ -86,10 +86,12 @@ public:
* adjusted to the actual pixel position. * adjusted to the actual pixel position.
*/ */
pos_type x2pos(int &x) const; pos_type x2pos(int &x) const;
/** Break the element if possible, so that its width is /** Break the element if possible, so that its width is less
* less then \param w. Returns true on success. * 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. // Returns the position on left side of the element.
pos_type left_pos() const; pos_type left_pos() const;

View File

@ -819,7 +819,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. // or the end of the par, then build a representation of the row.
pos_type i = pos; pos_type i = pos;
FontIterator fi = FontIterator(*this, par, pit, 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); char_type c = par.getChar(i);
// The most special cases are handled first. // The most special cases are handled first.
if (par.isInset(i)) { if (par.isInset(i)) {
@ -837,13 +837,6 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
int const add = max(fm.width(par.layout().labelsep), int const add = max(fm.width(par.layout().labelsep),
labelEnd(pit) - row.width()); labelEnd(pit) - row.width());
row.addSpace(i, add, *fi, par.lookupChange(i)); 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') } else if (c == '\t')
row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")), row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")),
*fi, par.lookupChange(i)); *fi, par.lookupChange(i));
@ -905,6 +898,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
// make sure that the RTL elements are in reverse ordering // make sure that the RTL elements are in reverse ordering
row.reverseRTL(is_rtl); row.reverseRTL(is_rtl);
//LYXERR0("breakrow: row is " << row);
} }

View File

@ -93,6 +93,14 @@ public:
* the offset x is updated to match the closest position in the string. * the offset x is updated to match the closest position in the string.
*/ */
virtual int x2pos(docstring const & s, int & x, bool rtl) const = 0; virtual int x2pos(docstring const & s, int & x, bool rtl) 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. /// return char dimension for the font.
virtual Dimension const dimension(char_type c) const = 0; virtual Dimension const dimension(char_type c) const = 0;
/** /**

View File

@ -183,6 +183,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, void GuiFontMetrics::rectText(docstring const & str,
int & w, int & ascent, int & descent) const int & w, int & ascent, int & descent) const
{ {

View File

@ -45,6 +45,7 @@ public:
virtual int signedWidth(docstring const & s) const; virtual int signedWidth(docstring const & s) const;
virtual int pos2x(docstring const & s, int pos, bool rtl) 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 x2pos(docstring const & s, int & x, bool rtl) const;
virtual bool breakAt(docstring & s, int & x, bool rtl, bool force) const;
virtual Dimension const dimension(char_type c) const; virtual Dimension const dimension(char_type c) const;
virtual void rectText(docstring const & str, virtual void rectText(docstring const & str,