From f65f3adbf73283a288f5d186b51b6b98c9ecb51d Mon Sep 17 00:00:00 2001 From: Jean-Marc Lasgouttes Date: Sun, 19 Jul 2015 01:22:10 +0200 Subject: [PATCH] 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. --- src/Row.cpp | 81 +++++++++++++++------------- src/Row.h | 8 +-- src/TextMetrics.cpp | 10 +--- src/frontends/FontMetrics.h | 8 +++ src/frontends/qt4/GuiFontMetrics.cpp | 25 +++++++++ src/frontends/qt4/GuiFontMetrics.h | 1 + 6 files changed, 84 insertions(+), 49 deletions(-) diff --git a/src/Row.cpp b/src/Row.cpp index 57f02e2987..5118f17f6b 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -24,6 +24,7 @@ #include "support/debug.h" #include "support/lassert.h" +#include "support/lstrings.h" #include "support/lyxalgo.h" #include @@ -32,6 +33,7 @@ using namespace std; namespace lyx { +using support::rtrim; using frontend::FontMetrics; 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) 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; } @@ -278,6 +277,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 +304,11 @@ void Row::add(pos_type const pos, char_type const c, Element e(STRING, pos, f, ch); elements_.push_back(e); } + 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; } @@ -357,33 +360,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 +384,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); } diff --git a/src/Row.h b/src/Row.h index 9039ff8860..490aa6b749 100644 --- a/src/Row.h +++ b/src/Row.h @@ -86,10 +86,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; diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index 2f314b582b..c3017d8fe2 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -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. 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 +837,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)); @@ -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 row.reverseRTL(is_rtl); + //LYXERR0("breakrow: row is " << row); } diff --git a/src/frontends/FontMetrics.h b/src/frontends/FontMetrics.h index 53f57a2880..84000d1c4a 100644 --- a/src/frontends/FontMetrics.h +++ b/src/frontends/FontMetrics.h @@ -93,6 +93,14 @@ public: * 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; + /** + * 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; /** diff --git a/src/frontends/qt4/GuiFontMetrics.cpp b/src/frontends/qt4/GuiFontMetrics.cpp index b488b684eb..804813a562 100644 --- a/src/frontends/qt4/GuiFontMetrics.cpp +++ b/src/frontends/qt4/GuiFontMetrics.cpp @@ -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, int & w, int & ascent, int & descent) const { diff --git a/src/frontends/qt4/GuiFontMetrics.h b/src/frontends/qt4/GuiFontMetrics.h index 7555929c9b..bc6b24aceb 100644 --- a/src/frontends/qt4/GuiFontMetrics.h +++ b/src/frontends/qt4/GuiFontMetrics.h @@ -45,6 +45,7 @@ public: 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 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, -- 2.39.2