[LyX/master] Avoid row breaking at inconvenient places.
Jean-Marc Lasgouttes
lasgouttes at lyx.org
Mon Sep 25 09:33:10 UTC 2023
commit 71d9f6e90d52d8f85c9e8b530027a9fd59c697e8
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date: Mon Sep 25 12:35:40 2023 +0200
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.
---
src/Row.cpp | 25 ++++++++++++++++++-------
src/Row.h | 14 ++++++++++----
src/TextMetrics.cpp | 2 +-
3 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/src/Row.cpp b/src/Row.cpp
index a7f00d5..e911fef 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -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)
{
// 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();
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
- * 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
*/
- if ((!force && breaks.front().nspc_wid > width)
+ if ((split_type == FIT && breaks.front().nspc_wid > width)
|| (breaks.size() > 1 && breaks.front().len == 0)) {
if (dim.wid == 0)
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 end = elements_.end();
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
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
* 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
* in itself, and the remainder of the row would still be
* too large after breaking, then we will have issues in
* next row. Thus breaking does not help.
*/
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();
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
* 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;
dim_.wid = wid + cit->dim.wid;
// If there are other elements, they should be removed.
diff --git a/src/Row.h b/src/Row.h
index 795e3e4..babe115 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -55,6 +55,14 @@ public:
// by the initial width
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
@@ -94,14 +102,12 @@ public:
* not needed or not possible.
* \param width: maximum width of the row.
* \param next_width: available width on next rows.
- * \param force: if true, cut string at any place, even for
- * languages that wrap at word delimiters; if false, do not
- * break at all if first element would larger than \c width.
+ * \param split_type: indicate how the string should be split.
* \param tail: a vector of elements where the remainder of
* the text will be appended (empty if nothing happened).
*/
// 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)
void rtrim();
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 51f73fd..eb99de0 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1150,7 +1150,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
// pile, or the place when we were in main row
Row::Element elt = *fcit;
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();
if (elt.type == Row::MARGINSPACE)
elt.dim.wid = max(elt.dim.wid, leftMargin(bigrow.pit()) - rb.width());
More information about the lyx-cvs
mailing list