Avoid row breaking at inconvenient places.

When it turns out that breaking a STRING row element was not
sufficient in Row::shortenIfNeeded, we still remember the shortest
width that one can obtain. Later, when we try to split a previous
element of the row, we have a better idea of how much of the row
remains after it.

To this end, change the signature of Element::splitAt to use an enum:
FIT (was: force=false), FORCE (was: force= true) and BEST_EFFORT
(split at max_width, but do not return an error if the string is too
large).

Fixes bug #12660.
This commit is contained in:
Jean-Marc Lasgouttes 2023-09-25 12:35:40 +02:00
parent 1ca43e1938
commit 71d9f6e90d
3 changed files with 29 additions and 12 deletions

View File

@ -123,7 +123,7 @@ pos_type Row::Element::x2pos(int &x) const
} }
bool Row::Element::splitAt(int const width, int next_width, bool force, bool Row::Element::splitAt(int const width, int next_width, SplitType split_type,
Row::Elements & tail) Row::Elements & tail)
{ {
// Not a string or already OK. // Not a string or already OK.
@ -142,13 +142,13 @@ bool Row::Element::splitAt(int const width, int next_width, bool force,
bool const wrap_any = !font.language()->wordWrap(); bool const wrap_any = !font.language()->wordWrap();
FontMetrics::Breaks breaks = fm.breakString(str, width, next_width, FontMetrics::Breaks breaks = fm.breakString(str, width, next_width,
isRTL(), wrap_any | force); isRTL(), wrap_any || split_type == FORCE);
/** if breaking did not really work, give up /** if breaking did not really work, give up
* case 1: we do not force break and the first element is longer than the limit; * case 1: split type is FIT and the first element is longer than the limit;
* case 2: the first break occurs at the front of the string * case 2: the first break occurs at the front of the string
*/ */
if ((!force && breaks.front().nspc_wid > width) if ((split_type == FIT && breaks.front().nspc_wid > width)
|| (breaks.size() > 1 && breaks.front().len == 0)) { || (breaks.size() > 1 && breaks.front().len == 0)) {
if (dim.wid == 0) if (dim.wid == 0)
dim.wid = fm.width(str); dim.wid = fm.width(str);
@ -548,6 +548,8 @@ Row::Elements Row::shortenIfNeeded(int const max_width, int const next_width)
Elements::iterator const beg = elements_.begin(); Elements::iterator const beg = elements_.begin();
Elements::iterator const end = elements_.end(); Elements::iterator const end = elements_.end();
int wid = left_margin; int wid = left_margin;
// the smallest row width we know we can achieve by breaking a string.
int min_row_wid = dim_.wid;
// Search for the first element that goes beyond right margin // Search for the first element that goes beyond right margin
Elements::iterator cit = beg; Elements::iterator cit = beg;
@ -600,14 +602,23 @@ Row::Elements Row::shortenIfNeeded(int const max_width, int const next_width)
* - shorter than the natural width of the element, in order to enforce * - shorter than the natural width of the element, in order to enforce
* break-up. * break-up.
*/ */
if (brk.splitAt(min(max_width - wid_brk, brk.dim.wid - 2), next_width, false, tail)) { int const split_width = min(max_width - wid_brk, brk.dim.wid - 2);
if (brk.splitAt(split_width, next_width, BEST_EFFORT, tail)) {
// if we did not manage to fit a part of the element into
// the split_width limit, at least remember that we can
// shorten the row if needed.
if (brk.dim.wid > split_width) {
min_row_wid = wid_brk + brk.dim.wid;
tail.clear();
continue;
}
/* if this element originally did not cause a row overflow /* if this element originally did not cause a row overflow
* in itself, and the remainder of the row would still be * in itself, and the remainder of the row would still be
* too large after breaking, then we will have issues in * too large after breaking, then we will have issues in
* next row. Thus breaking does not help. * next row. Thus breaking does not help.
*/ */
if (wid_brk + cit_brk->dim.wid < max_width if (wid_brk + cit_brk->dim.wid < max_width
&& dim_.wid - (wid_brk + brk.dim.wid) >= next_width) { && min_row_wid - (wid_brk + brk.dim.wid) >= next_width) {
tail.clear(); tail.clear();
break; break;
} }
@ -641,7 +652,7 @@ Row::Elements Row::shortenIfNeeded(int const max_width, int const next_width)
* shorten the row. Let's try to break it again, but force * shorten the row. Let's try to break it again, but force
* splitting this time. * splitting this time.
*/ */
if (cit->splitAt(max_width - wid, next_width, true, tail)) { if (cit->splitAt(max_width - wid, next_width, FORCE, tail)) {
end_ = cit->endpos; end_ = cit->endpos;
dim_.wid = wid + cit->dim.wid; dim_.wid = wid + cit->dim.wid;
// If there are other elements, they should be removed. // If there are other elements, they should be removed.

View File

@ -55,6 +55,14 @@ public:
// by the initial width // by the initial width
MARGINSPACE MARGINSPACE
}; };
enum SplitType {
// split string to fit requested width, fail if string remains too long
FIT,
// if the requested width is too small, accept the first possible break
BEST_EFFORT,
// cut string at any place, even for languages that wrap at word delimiters
FORCE
};
/** /**
* One element of a Row. It has a set of attributes that can be used * One element of a Row. It has a set of attributes that can be used
@ -94,14 +102,12 @@ public:
* not needed or not possible. * not needed or not possible.
* \param width: maximum width of the row. * \param width: maximum width of the row.
* \param next_width: available width on next rows. * \param next_width: available width on next rows.
* \param force: if true, cut string at any place, even for * \param split_type: indicate how the string should be split.
* languages that wrap at word delimiters; if false, do not
* break at all if first element would larger than \c width.
* \param tail: a vector of elements where the remainder of * \param tail: a vector of elements where the remainder of
* the text will be appended (empty if nothing happened). * the text will be appended (empty if nothing happened).
*/ */
// FIXME: ideally last parameter should be Elements&, but it is not possible. // FIXME: ideally last parameter should be Elements&, but it is not possible.
bool splitAt(int width, int next_width, bool force, std::vector<Element> & tail); bool splitAt(int width, int next_width, SplitType split_type, std::vector<Element> & tail);
// remove trailing spaces (useful for end of row) // remove trailing spaces (useful for end of row)
void rtrim(); void rtrim();

View File

@ -1150,7 +1150,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
// pile, or the place when we were in main row // pile, or the place when we were in main row
Row::Element elt = *fcit; Row::Element elt = *fcit;
Row::Elements tail; Row::Elements tail;
elt.splitAt(width - rows.back().width(), next_width, false, tail); elt.splitAt(width - rows.back().width(), next_width, Row::FIT, tail);
Row & rb = rows.back(); Row & rb = rows.back();
if (elt.type == Row::MARGINSPACE) if (elt.type == Row::MARGINSPACE)
elt.dim.wid = max(elt.dim.wid, leftMargin(bigrow.pit()) - rb.width()); elt.dim.wid = max(elt.dim.wid, leftMargin(bigrow.pit()) - rb.width());