[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