[LyX features/breakrows] Centralize the code that removes trailing spaces from end row element.

Jean-Marc Lasgouttes lasgouttes at lyx.org
Tue Aug 31 19:06:44 UTC 2021


The branch, breakrows, has been updated.
  discards  5e0358f3d3ad6001f471a7144db7c46cd1b06ee6 (commit)
  discards  d3183dd0f098d499921bd7bbc0ca180e62879c6d (commit)
  discards  aa8b2842aea04422636aee1178ac13d6bc068dd8 (commit)
  discards  5fd50ad8adb1f834cb96b7d4dbc1735aa300923c (commit)
  discards  1fb689b431c1f28ac6e060fd24f79c89ad861267 (commit)
  discards  1b66319af14f56c6a5026ea6dfb1794ff0212954 (commit)
  discards  f2157eee172ac020e8f941c16cda08d31fc45cf9 (commit)
  discards  ebd53055fe9c474d26bf9d5e9a08c743c3f26892 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (5e0358f3d3ad6001f471a7144db7c46cd1b06ee6)
            \
             N -- N -- N (7a0018216746aaa0b71b266f8b99e753bea93bed)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

- Log -----------------------------------------------------------------

commit 7a0018216746aaa0b71b266f8b99e753bea93bed
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Tue Aug 31 19:23:55 2021 +0200

    Centralize the code that removes trailing spaces from end row element.
    
    Move to Row::Element::splitAt the code in Row::shortenIfNeeded that
    removes trailing spaces from last element in row, so that it also
    applies to direct call to splitAt in TextMetrics::breakParagraph.
    
    Fixes bug found by Kornel.

diff --git a/src/Row.cpp b/src/Row.cpp
index 920dd9e..32a350b 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -151,7 +151,13 @@ Row::Element Row::Element::splitAt(int w, bool force)
 
 		// Now update ourselves
 		str.erase(i);
-		endpos = pos + i;
+		/* 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, and
+		 * decrease endpos, since spaces at row break are invisible.
+		 */
+		str = rtrim(str);
+		endpos = pos + str.length();
 		// Row should be broken after the original element
 		row_flags = (row_flags & ~AfterFlags) | BreakAfter;
 		//LYXERR0("breakAt(" << w << ")  Row element Broken at " << w << "(w(str)=" << fm.width(str) << "): e=" << *this);
@@ -544,15 +550,7 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
 			    && dim_.wid - (wid_brk + brk.dim.wid) >= next_width) {
 				break;
 			}
-			end_ = brk.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 end of the row, since spaces at row
-			 * break are invisible.
-			 */
-			brk.str = rtrim(brk.str);
-			brk.endpos = brk.pos + brk.str.length();
+			end_ = remainder.pos;
 			*cit_brk = brk;
 			dim_.wid = wid_brk + brk.dim.wid;
 			// If there are other elements, they should be removed.
@@ -584,11 +582,8 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
 	 * boundary this time.
 	 */
 	Element remainder = cit->splitAt(w - wid, true);
-	if (remainder.isValid()) {
-		end_ = cit->endpos;
-		// See comment above.
-		cit->str = rtrim(cit->str);
-		cit->endpos = cit->pos + cit->str.length();
+	if (cit->row_flags & BreakAfter) {
+		end_ = remainder.pos;
 		dim_.wid = wid + cit->dim.wid;
 		// If there are other elements, they should be removed.
 		return splitFrom(elements_, next(cit, 1), remainder);

commit 96465d1b778824836179257319945c6b3c4ebdea
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Tue Aug 31 15:58:56 2021 +0200

    Handle the case where breakAt cuts after trailing space
    
    In this case, the extra element returned should empty but valid. The
    row flag BreakAfter is set to indicate that we have a break there
    (this principle will be used more generally in a forthcoming commit).
    
    To detect that we cut at the trailing space, it is necessary to rely
    on the difference between QTextLine::horizontalAdvance() and
    QTextLine::naturalTextWidth() when the flag
    QTextOption::IncludeTrailingSpaces is used: the trailing space is
    taken into account in the later, but not in the former.
    
    Somme comments have been added to make code intent clearer.

diff --git a/src/Row.cpp b/src/Row.cpp
index 647ad47..920dd9e 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -142,13 +142,19 @@ Row::Element Row::Element::splitAt(int w, bool force)
 	dim.wid = w;
 	int const i = fm.breakAt(str, dim.wid, isRTL(), force);
 	if (i != -1) {
+		//Create a second row element to return
 		Element ret(STRING, pos + i, font, change);
 		ret.str = str.substr(i);
 		ret.endpos = ret.pos + ret.str.length();
+		// Copy the after flags of the original element to the second one.
 		ret.row_flags = row_flags & (CanBreakInside | AfterFlags);
+
+		// Now update ourselves
 		str.erase(i);
 		endpos = pos + i;
-		//lyxerr << "breakAt(" << w << ")  Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl;
+		// Row should be broken after the original element
+		row_flags = (row_flags & ~AfterFlags) | BreakAfter;
+		//LYXERR0("breakAt(" << w << ")  Row element Broken at " << w << "(w(str)=" << fm.width(str) << "): e=" << *this);
 		return ret;
 	}
 
@@ -257,7 +263,7 @@ ostream & operator<<(ostream & os, Row::Element const & e)
 		os << "INVALID: ";
 		break;
 	}
-	os << "width=" << e.full_width();
+	os << "width=" << e.full_width() << ", row_flags=" << e.row_flags;
 	return os;
 }
 
@@ -528,7 +534,7 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
 		 *   break-up.
 		 */
 		Element remainder = brk.splitAt(min(w - wid_brk, brk.dim.wid - 2), !word_wrap);
-		if (remainder.isValid()) {
+		if (brk.row_flags & BreakAfter) {
 			/* 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
@@ -550,7 +556,11 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
 			*cit_brk = brk;
 			dim_.wid = wid_brk + brk.dim.wid;
 			// If there are other elements, they should be removed.
-			return splitFrom(elements_, next(cit_brk, 1), remainder);
+			// remainder can be empty when splitting at trailing space
+			if (remainder.str.empty())
+				return splitFrom(elements_, next(cit_brk, 1));
+			else
+				return splitFrom(elements_, next(cit_brk, 1), remainder);
 		}
 	}
 
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index f611434..fb2a4ae 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1156,7 +1156,8 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 			// Add a new next element on the pile
 			if (next_elt.isValid()) {
 				// do as if we inserted this element in the original row
-				fcit.put(next_elt);
+				if (!next_elt.str.empty())
+					fcit.put(next_elt);
 				need_new_row = true;
 			}
 		}
diff --git a/src/frontends/qt/GuiFontMetrics.cpp b/src/frontends/qt/GuiFontMetrics.cpp
index f873689..83c2c0a 100644
--- a/src/frontends/qt/GuiFontMetrics.cpp
+++ b/src/frontends/qt/GuiFontMetrics.cpp
@@ -511,6 +511,9 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
 	QTextOption to;
 	to.setWrapMode(force ? QTextOption::WrapAtWordBoundaryOrAnywhere
 	                     : QTextOption::WordWrap);
+	// Let QTextLine::naturalTextWidth() account for trailing spaces
+	// (horizontalAdvance() still does not).
+	to.setFlags(QTextOption::IncludeTrailingSpaces);
 	tl.setTextOption(to);
 	tl.beginLayout();
 	QTextLine line = tl.createLine();
@@ -543,8 +546,11 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
 		--len;
 	LASSERT(len > 0 || qlen == 0, /**/);
 #endif
-	// Do not cut is the string is already short enough
-	if (len == static_cast<int>(s.length())) {
+	// Do not cut is the string is already short enough. We rely on
+	// naturalTextWidth() to catch the case where we cut at the trailing
+	// space.
+	if (len == static_cast<int>(s.length())
+		&& line.naturalTextWidth() <= x) {
 		len = -1;
 #if QT_VERSION < 0x050000
 		// With some monospace fonts, the value of horizontalAdvance()

commit 38798259ed68ff4f8046da517f02d85a335dcbed
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Mon Aug 30 15:48:44 2021 +0200

    Workaround for Qt 4
    
    At least with Qt 4.8.7 on Ubuntu 16.04, QTextLine::lineWidth() can
    return a bogus value, at least with Courier font. One hypothesis is
    that the invisible characters that we use in breakAt_helper are given
    a non-null width.
    
    Work around it, although the exact bug has not been pinpointed.

diff --git a/src/frontends/qt/GuiFontMetrics.cpp b/src/frontends/qt/GuiFontMetrics.cpp
index ba16266..f873689 100644
--- a/src/frontends/qt/GuiFontMetrics.cpp
+++ b/src/frontends/qt/GuiFontMetrics.cpp
@@ -517,7 +517,7 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
 	line.setLineWidth(x);
 	tl.createLine();
 	tl.endLayout();
-	int const line_wid = iround(line.horizontalAdvance());
+	int line_wid = iround(line.horizontalAdvance());
 	if ((force && line.textLength() == offset) || line_wid > x)
 		return {-1, line_wid};
 	/* Since QString is UTF-16 and docstring is UCS-4, the offsets may
@@ -543,9 +543,16 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
 		--len;
 	LASSERT(len > 0 || qlen == 0, /**/);
 #endif
-	// si la chaîne est déjà trop courte, on ne coupe pas
-	if (len == static_cast<int>(s.length()))
+	// Do not cut is the string is already short enough
+	if (len == static_cast<int>(s.length())) {
 		len = -1;
+#if QT_VERSION < 0x050000
+		// With some monospace fonts, the value of horizontalAdvance()
+		// can be wrong with Qt4. One hypothesis is that the invisible
+		// characters that we use are given a non-null width.
+		line_wid = width(s);
+#endif
+	}
 	return {len, line_wid};
 }
 

commit fb81252e525af49c40198d8407abf909deeb4304
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Tue Jul 20 00:07:13 2021 +0200

    Last step: use sortenIfNeeded again.
    
    Change semantics of Row::shortenIfNeeded: instead of breaking the row
    and returning a boolean, it returns the list of row elements that have
    been removed (or broken) from the row. The logic of the method remains
    the same.
    
    Use shortenIfNeeded in breakParagraph. This was the last missing block.
    
    Remove the old breakRow. Only bugs remain now :)

diff --git a/src/Row.cpp b/src/Row.cpp
index 3bc8ef0..647ad47 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -448,10 +448,31 @@ void Row::pop_back()
 }
 
 
-bool Row::shortenIfNeeded(int const w, int const next_width)
+namespace {
+
+// Remove stuff after it from elts, and return it.
+// if init is provided, it will be in front of the rest
+Row::Elements splitFrom(Row::Elements & elts, Row::Elements::iterator const & it,
+                        Row::Element const & init = Row::Element())
+{
+	Row::Elements ret;
+	if (init.isValid())
+		ret.push_back(init);
+	ret.insert(ret.end(), it, elts.end());
+	elts.erase(it, elts.end());
+	return ret;
+}
+
+}
+
+
+Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
 {
+	// FIXME: performance: if the last element is a string, we would
+	// like to avoid computing its length.
+	finalizeLast();
 	if (empty() || width() <= w)
-		return false;
+		return Elements();
 
 	Elements::iterator const beg = elements_.begin();
 	Elements::iterator const end = elements_.end();
@@ -467,8 +488,8 @@ bool Row::shortenIfNeeded(int const w, int const next_width)
 
 	if (cit == end) {
 		// This should not happen since the row is too long.
-		LYXERR0("Something is wrong cannot shorten row: " << *this);
-		return false;
+		LYXERR0("Something is wrong, cannot shorten row: " << *this);
+		return Elements();
 	}
 
 	// Iterate backwards over breakable elements and try to break them
@@ -486,8 +507,7 @@ bool Row::shortenIfNeeded(int const w, int const next_width)
 		if (wid_brk <= w && brk.row_flags & CanBreakAfter) {
 			end_ = brk.endpos;
 			dim_.wid = wid_brk;
-			elements_.erase(cit_brk + 1, end);
-			return true;
+			return splitFrom(elements_, cit_brk + 1);
 		}
 		// assume now that the current element is not there
 		wid_brk -= brk.dim.wid;
@@ -507,7 +527,8 @@ bool Row::shortenIfNeeded(int const w, int const next_width)
 		 * - shorter than the natural width of the element, in order to enforce
 		 *   break-up.
 		 */
-		if (brk.breakAt(min(w - wid_brk, brk.dim.wid - 2), !word_wrap)) {
+		Element remainder = brk.splitAt(min(w - wid_brk, brk.dim.wid - 2), !word_wrap);
+		if (remainder.isValid()) {
 			/* 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
@@ -529,14 +550,13 @@ bool Row::shortenIfNeeded(int const w, int const next_width)
 			*cit_brk = brk;
 			dim_.wid = wid_brk + brk.dim.wid;
 			// If there are other elements, they should be removed.
-			elements_.erase(cit_brk + 1, end);
-			return true;
+			return splitFrom(elements_, next(cit_brk, 1), remainder);
 		}
 	}
 
-	if (cit != beg && cit->type == VIRTUAL) {
-		// It is not possible to separate a virtual element from the
-		// previous one.
+	if (cit != beg && cit->row_flags & NoBreakBefore) {
+		// It is not possible to separate this element from the
+		// previous one. (e.g. VIRTUAL)
 		--cit;
 		wid -= cit->dim.wid;
 	}
@@ -546,25 +566,24 @@ bool Row::shortenIfNeeded(int const w, int const next_width)
 		// been added. We can cut right here.
 		end_ = cit->pos;
 		dim_.wid = wid;
-		elements_.erase(cit, end);
-		return true;
+		return splitFrom(elements_, cit);
 	}
 
 	/* 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 - wid, true)) {
+	Element remainder = cit->splitAt(w - wid, true);
+	if (remainder.isValid()) {
 		end_ = cit->endpos;
 		// 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);
-		return true;
+		return splitFrom(elements_, next(cit, 1), remainder);
 	}
-	return false;
+	return Elements();
 }
 
 
diff --git a/src/Row.h b/src/Row.h
index 72d7a86..d45f852 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -296,7 +296,7 @@ public:
 	 * \param available width on next row.
 	 * \return true if the row has been shortened.
 	 */
-	bool shortenIfNeeded(int const width, int const next_width);
+	Elements shortenIfNeeded(int const width, int const next_width);
 
 	/**
 	 * If last element of the row is a string, compute its width
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 5f48d29..f611434 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -491,7 +491,7 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
 
 		// If there is an end of paragraph marker, its size should be
 		// substracted to the available width. The logic here is
-		// almost the same as in breakRow, remember keep them in sync.
+		// almost the same as in tokenizeParagraph, remember keep them in sync.
 		int eop = 0;
 		if (lyxrc.paragraph_markers && e.pos + 1 == par.size()
 		    && size_type(pit + 1) < text_->paragraphs().size()) {
@@ -1005,6 +1005,11 @@ public:
 
 	void put(value_type const & e) { pile_.push_back(e); }
 
+	// Put a sequence of elements on the pile (in reverse order!)
+	void put(vector<value_type> const & elts) {
+		pile_.insert(pile_.end(), elts.rbegin(), elts.rend());
+	}
+
 // This should be private, but declaring the friend functions is too much work
 //private:
 	typename T::const_iterator cit_;
@@ -1120,19 +1125,40 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 		Row::Element elt = *fcit;
 		Row::Element next_elt = elt.splitAt(width - rows.back().width(),
 		                                    !elt.font.language()->wordWrap());
-		// a new element in the row
-		rows.back().push_back(elt);
-		rows.back().finalizeLast();
-		pos = elt.endpos;
-
-		// Go to next element
-		++fcit;
-
-		// Add a new next element on the pile
-		if (next_elt.isValid()) {
-			// do as if we inserted this element in the original row
-			fcit.put(next_elt);
-			need_new_row = true;
+		if (elt.dim.wid > width - rows.back().width()) {
+			Row & rb = rows.back();
+			rb.push_back(*fcit);
+			// if the row is too large, try to cut at last separator. In case
+			// of success, reset indication that the row was broken abruptly.
+			int const next_width = max_width_ - leftMargin(rb.pit(), rb.endpos())
+				- rightMargin(rb.pit());
+
+			Row::Elements next_elts = rb.shortenIfNeeded(width, next_width);
+
+			// Go to next element
+			++fcit;
+
+			// Handle later the elements returned by shortenIfNeeded.
+			if (!next_elts.empty()) {
+				rb.flushed(false);
+				fcit.put(next_elts);
+				need_new_row = true;
+			}
+		} else {
+			// a new element in the row
+			rows.back().push_back(elt);
+			rows.back().finalizeLast();
+			pos = elt.endpos;
+
+			// Go to next element
+			++fcit;
+
+			// Add a new next element on the pile
+			if (next_elt.isValid()) {
+				// do as if we inserted this element in the original row
+				fcit.put(next_elt);
+				need_new_row = true;
+			}
 		}
 	}
 
@@ -1145,185 +1171,6 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 	return rows;
 }
 
-/** This is the function where the hard work is done. The code here is
- * very sensitive to small changes :) Note that part of the
- * intelligence is also in Row::shortenIfNeeded.
- */
-bool TextMetrics::breakRow(Row & row, int const right_margin) const
-{
-	LATTEST(row.empty());//
-	Paragraph const & par = text_->getPar(row.pit());//
-	Buffer const & buf = text_->inset().buffer();//
-	BookmarksSection::BookmarkPosList bpl =//
-		theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id());//
-
-	pos_type const end = par.size();//
-	pos_type const pos = row.pos();//
-	pos_type const body_pos = par.beginOfBody();//
-	bool const is_rtl = text_->isRTL(row.pit());//
-	bool need_new_row = false;//
-
-	row.left_margin = leftMargin(row.pit(), pos);//
-	row.right_margin = right_margin;//
-	if (is_rtl)//
-		swap(row.left_margin, row.right_margin);//
-	// Remember that the row width takes into account the left_margin
-	// but not the right_margin.
-	row.dim().wid = row.left_margin;//
-	// the width available for the row.
-	int const width = max_width_ - row.right_margin;//
-
-	// check for possible inline completion
-	DocIterator const & ic_it = bv_->inlineCompletionPos();//
-	pos_type ic_pos = -1;//
-	if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == row.pit())//
-		ic_pos = ic_it.pos();//
-
-	// Now we iterate through until we reach the right margin
-	// or the end of the par, then build a representation of the row.
-	pos_type i = pos;//---------------------------------------------------vvv
-	FontIterator fi = FontIterator(*this, par, row.pit(), pos);
-	// The real stopping condition is a few lines below.
-	while (true) {
-		// Firstly, check whether there is a bookmark here.
-		if (lyxrc.bookmarks_visibility == LyXRC::BMK_INLINE)
-			for (auto const & bp_p : bpl)
-				if (bp_p.second == i) {
-					Font f = *fi;
-					f.fontInfo().setColor(Color_bookmark);
-					// ❶ U+2776 DINGBAT NEGATIVE CIRCLED DIGIT ONE
-					char_type const ch = 0x2775 + bp_p.first;
-					row.addVirtual(i, docstring(1, ch), f, Change());
-				}
-
-		// The stopping condition is here so that the display of a
-		// bookmark can take place at paragraph start too.
-		if (i >= end || (i != pos && row.width() > width))//^width
-			break;
-
-		char_type c = par.getChar(i);
-		// The most special cases are handled first.
-		if (par.isInset(i)) {
-			Inset const * ins = par.getInset(i);
-			Dimension dim = bv_->coordCache().insets().dim(ins);
-			row.add(i, ins, dim, *fi, par.lookupChange(i));
-		} else if (c == ' ' && i + 1 == body_pos) {
-			// There is a space at i, but it should not be
-			// added as a separator, because it is just
-			// before body_pos. Instead, insert some spacing to
-			// align text
-			FontMetrics const & fm = theFontMetrics(text_->labelFont(par));
-			// this is needed to make sure that the row width is correct
-			row.finalizeLast();
-			int const add = max(fm.width(par.layout().labelsep),
-			                    labelEnd(row.pit()) - row.width());
-			row.addSpace(i, add, *fi, par.lookupChange(i));
-		} else if (c == '\t')
-			row.addSpace(i, theFontMetrics(*fi).width(from_ascii("    ")),
-				     *fi, par.lookupChange(i));
-		else if (c == 0x2028 || c == 0x2029) {
-			/**
-			 * U+2028 LINE SEPARATOR
-			 * U+2029 PARAGRAPH SEPARATOR
-
-			 * These are special unicode characters that break
-			 * lines/pragraphs. Not handling them lead to trouble wrt
-			 * Qt QTextLayout formatting. We add a visible character
-			 * on screen so that the user can see that something is
-			 * happening.
-			*/
-			row.finalizeLast();
-			// ⤶ U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS
-			// ¶ U+00B6 PILCROW SIGN
-			char_type const screen_char = (c == 0x2028) ? 0x2936 : 0x00B6;
-			row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
-		} else
-			row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
-
-		// add inline completion width
-		// draw logically behind the previous character
-		if (ic_pos == i + 1 && !bv_->inlineCompletion().empty()) {
-			docstring const comp = bv_->inlineCompletion();
-			size_t const uniqueTo =bv_->inlineCompletionUniqueChars();
-			Font f = *fi;
-
-			if (uniqueTo > 0) {
-				f.fontInfo().setColor(Color_inlinecompletion);
-				row.addVirtual(i + 1, comp.substr(0, uniqueTo), f, Change());
-			}
-			f.fontInfo().setColor(Color_nonunique_inlinecompletion);
-			row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change());
-		}
-
-		// Handle some situations that abruptly terminate the row
-		// - Before an inset with BreakBefore
-		// - After an inset with BreakAfter
-		Inset const * prevInset = !row.empty() ? row.back().inset : 0;
-		Inset const * nextInset = (i + 1 < end) ? par.getInset(i + 1) : 0;
-		if ((nextInset && nextInset->rowFlags() & BreakBefore)
-		    || (prevInset && prevInset->rowFlags() & BreakAfter)) {
-			row.flushed(true);
-			// Force a row creation after this one if it is ended by
-			// an inset that either
-			// - has row flag RowAfter that enforces that;
-			// - or (1) did force the row breaking, (2) is at end of
-			//   paragraph and (3) the said paragraph has an end label.
-			need_new_row = prevInset &&
-				(prevInset->rowFlags() & AlwaysBreakAfter
-				 || (prevInset->rowFlags() & BreakAfter && i + 1 == end
-				     && text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL));
-			++i;
-			break;
-		}
-
-		++i;
-		++fi;
-	}
-	row.finalizeLast();
-	row.endpos(i);
-
-	// End of paragraph marker. The logic here is almost the
-	// same as in redoParagraph, remember keep them in sync.
-	ParagraphList const & pars = text_->paragraphs();
-	Change const & change = par.lookupChange(i);
-	if ((lyxrc.paragraph_markers || change.changed())
-	    && !need_new_row // not this
-	    && i == end && size_type(row.pit() + 1) < pars.size()) {
-		// add a virtual element for the end-of-paragraph
-		// marker; it is shown on screen, but does not exist
-		// in the paragraph.
-		Font f(text_->layoutFont(row.pit()));
-		f.fontInfo().setColor(Color_paragraphmarker);
-		f.setLanguage(par.getParLanguage(buf.params()));
-		// ¶ U+00B6 PILCROW SIGN
-		row.addVirtual(end, docstring(1, char_type(0x00B6)), f, change);
-	}
-
-	// Is there a end-of-paragaph change?
-	if (i == end && par.lookupChange(end).changed() && !need_new_row)
-		row.needsChangeBar(true);
-    //--------------------------------------------------------------------^^^
-	// FIXME : nothing below this
-
-	// if the row is too large, try to cut at last separator. In case
-	// of success, reset indication that the row was broken abruptly.
-	int const next_width = max_width_ - leftMargin(row.pit(), row.endpos())
-		- rightMargin(row.pit());
-
-	if (row.shortenIfNeeded(width, next_width))
-		row.flushed(false);
-	row.right_boundary(!row.empty() && row.endpos() < end//
-	                   && row.back().endpos == row.endpos());//
-	// Last row in paragraph is flushed
-	if (row.endpos() == end)//
-		row.flushed(true);//
-
-	// make sure that the RTL elements are in reverse ordering
-	row.reverseRTL(is_rtl);//
-	//LYXERR0("breakrow: row is " << row);
-
-	return need_new_row;
-}
 
 int TextMetrics::parTopSpacing(pit_type const pit) const
 {
diff --git a/src/TextMetrics.h b/src/TextMetrics.h
index 3ff1161..e38ba71 100644
--- a/src/TextMetrics.h
+++ b/src/TextMetrics.h
@@ -151,15 +151,12 @@ private:
 	/// FIXME??
 	int labelEnd(pit_type const pit) const;
 
+	// Turn paragraph oh index \c pit into a single row
 	Row tokenizeParagraph(pit_type pit) const;
 
+	// Break the row produced by tokenizeParagraph() into a list of rows.
 	RowList breakParagraph(Row const & row) const;
 
-	/// sets row.end to the pos value *after* which a row should break.
-	/// for example, the pos after which isNewLine(pos) == true
-	/// \return true when another row is required (after a newline)
-	bool breakRow(Row & row, int right_margin) const;
-
 	// Expands the alignment of row \param row in paragraph \param par
 	LyXAlignment getAlign(Paragraph const & par, Row const & row) const;
 	/// Aligns properly the row contents (computes spaces and fills)

commit c9de77fee897cca334928ea1287ce9cd522fb706
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Sun Jul 18 01:09:33 2021 +0200

    Implement handling of row_flags for row breaking
    
    To this end, add the helper function needsRowBreak which computes the
    effect of two consecutive row flags. This function implements the
    priorities described in RowFlags.h.
    
    This function is called with the relevant flags, or NoBreak* when at
    boundaries and updates need_new_row.
    
    Some common code is factored in a new cleanupRow() helper.

diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 12548e0..5f48d29 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -979,22 +979,6 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
 
 namespace {
 
-Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
-{
-	Row nrow;
-	nrow.pit(pit);
-	nrow.pos(pos);
-	nrow.left_margin = tm.leftMargin(pit, pos);
-	nrow.right_margin = tm.rightMargin(pit);
-	if (is_rtl)
-		swap(nrow.left_margin, nrow.right_margin);
-	// Remember that the row width takes into account the left_margin
-	// but not the right_margin.
-	nrow.dim().wid = nrow.left_margin;
-	return nrow;
-}
-
-
 /** Helper template flexible_const_iterator<T>
  * A way to iterate over a const container, but insert fake elements in it.
  * In the case of a row, we will have to break some elements, which
@@ -1017,6 +1001,8 @@ public:
 
 	value_type operator*() const { return pile_.empty() ? *cit_ : pile_.back(); }
 
+	value_type const * operator->() const { return pile_.empty() ? &*cit_ : &pile_.back(); }
+
 	void put(value_type const & e) { pile_.push_back(e); }
 
 // This should be private, but declaring the friend functions is too much work
@@ -1050,6 +1036,44 @@ bool operator==(flexible_const_iterator<T> const & t1,
 	return t1.cit_ == t2.cit_ && t1.pile_.empty() && t2.pile_.empty();
 }
 
+Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
+{
+	Row nrow;
+	nrow.pit(pit);
+	nrow.pos(pos);
+	nrow.left_margin = tm.leftMargin(pit, pos);
+	nrow.right_margin = tm.rightMargin(pit);
+	if (is_rtl)
+		swap(nrow.left_margin, nrow.right_margin);
+	// Remember that the row width takes into account the left_margin
+	// but not the right_margin.
+	nrow.dim().wid = nrow.left_margin;
+	return nrow;
+}
+
+
+void cleanupRow(Row & row, pos_type pos, pos_type real_endpos, bool is_rtl)
+{
+	row.endpos(pos);
+	row.right_boundary(!row.empty() && pos < real_endpos
+	                   && row.back().endpos == pos);
+	// make sure that the RTL elements are in reverse ordering
+	row.reverseRTL(is_rtl);
+}
+
+// Implement the priorities described in RowFlags.h.
+bool needsRowBreak(int f1, int f2)
+{
+	if (f1 & AlwaysBreakAfter /*|| f2 & AlwaysBreakBefore*/)
+		return true;
+	if (f1 & NoBreakAfter || f2 & NoBreakBefore)
+		return false;
+	if (f1 & BreakAfter || f2 & BreakBefore)
+		return true;
+	return false;
+}
+
+
 }
 
 
@@ -1057,6 +1081,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 {
 	RowList rows;
 	bool const is_rtl = text_->isRTL(bigrow.pit());
+	bool const end_label = text_->getEndLabel(bigrow.pit()) != END_LABEL_NO_LABEL;
 
 	bool need_new_row = true;
 	pos_type pos = 0;
@@ -1064,15 +1089,21 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 	flexible_const_iterator<Row> fcit = flexible_begin(bigrow);
 	flexible_const_iterator<Row> const end = flexible_end(bigrow);
 	while (true) {
+		bool const has_row = !rows.empty();
+		bool const row_empty = !has_row || rows.back().empty();
+		// The row flags of previous element, if there is one.
+		// Otherwise we use NoBreakAfter to avoid an empty row before
+		// e.g. a displayed equation.
+		int const f1 = row_empty ? NoBreakAfter : rows.back().back().row_flags;
+		// The row flags of next element, if there is one.
+		// Otherwise we use NoBreakBefore (see above), unless the
+		// paragraph has an end label (for which an empty row is OK).
+		int const f2 = (fcit == end) ? (end_label ? Inline : NoBreakBefore)
+		                             : fcit->row_flags;
+		need_new_row |= needsRowBreak(f1, f2);
 		if (need_new_row) {
-			if (!rows.empty()) {
-				Row & rb = rows.back();
-				rb.endpos(pos);
-				rb.right_boundary(!rb.empty() && rb.endpos() < bigrow.endpos()
-								   && rb.back().endpos == rb.endpos());
-				// make sure that the RTL elements are in reverse ordering
-				rb.reverseRTL(is_rtl);
-			}
+			if (!rows.empty())
+				cleanupRow(rows.back(), pos, bigrow.endpos(), is_rtl);
 			rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl));
 			// the width available for the row.
 			width = max_width_ - rows.back().right_margin;
@@ -1106,13 +1137,9 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 	}
 
 	if (!rows.empty()) {
-		Row & rb = rows.back();
+		cleanupRow(rows.back(), pos, bigrow.endpos(), is_rtl);
 		// Last row in paragraph is flushed
-		rb.flushed(true);
-		rb.endpos(bigrow.endpos());
-		rb.right_boundary(false);
-		// make sure that the RTL elements are in reverse ordering
-		rb.reverseRTL(is_rtl);
+		rows.back().flushed(true);
 	}
 
 	return rows;
@@ -1226,9 +1253,8 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 			}
 			f.fontInfo().setColor(Color_nonunique_inlinecompletion);
 			row.addVirtual(i + 1, comp.substr(uniqueTo), f, Change());
-		}//---------------------------------------------------------------^^^
+		}
 
-		// FIXME: Handle when breaking the rows
 		// Handle some situations that abruptly terminate the row
 		// - Before an inset with BreakBefore
 		// - After an inset with BreakAfter
@@ -1253,7 +1279,6 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 		++i;
 		++fi;
 	}
-	//--------------------------------------------------------------------vvv
 	row.finalizeLast();
 	row.endpos(i);
 

commit 583b7d98623e284733cbfe53e5e9d07352701d4e
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Sat Jul 17 23:16:15 2021 +0200

    Change the way the element's width is updated.
    
    Remove the code that compute the width every 30 characters (yay!).
    Make sure that finalizeLast() is called after inserting a row element in
    a row in breakParagraph.

diff --git a/src/Row.cpp b/src/Row.cpp
index 705a147..3bc8ef0 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -370,8 +370,7 @@ void Row::finalizeLast()
 	if (elt.change.changed())
 		changebar_ = true;
 
-	if (elt.type == STRING) {
-		dim_.wid -= elt.dim.wid;
+	if (elt.type == STRING && elt.dim.wid == 0) {
 		elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
 		dim_.wid += elt.dim.wid;
 	}
@@ -401,16 +400,8 @@ void Row::add(pos_type const pos, char_type const c,
 		e.row_flags = can_break ? CanBreakInside : Inline;
 		elements_.push_back(e);
 	}
-	if (back().str.length() % 30 == 0) {
-		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;
-	} else {
-		back().str += c;
-		back().endpos = pos + 1;
-	}
+	back().str += c;
+	back().endpos = pos + 1;
 }
 
 
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 3354746..12548e0 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1091,6 +1091,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 		                                    !elt.font.language()->wordWrap());
 		// a new element in the row
 		rows.back().push_back(elt);
+		rows.back().finalizeLast();
 		pos = elt.endpos;
 
 		// Go to next element

commit 8a05ecfce19d3c47caf2c4e0205c0e058d9dcbd5
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Sat Jul 17 02:31:49 2021 +0200

    Introduce helper template to simplify breakParagraph code
    
    This is a semi-generic iterator for iterating over a container and
    pretend that we add elements to it along the way.

diff --git a/src/Row.h b/src/Row.h
index 4fdcee4..72d7a86 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -150,6 +150,8 @@ public:
 		friend std::ostream & operator<<(std::ostream & os, Element const & row);
 	};
 
+	///
+	typedef Element value_type;
 
 	///
 	Row() {}
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 2e5bd63..3354746 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -994,6 +994,62 @@ Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
 	return nrow;
 }
 
+
+/** Helper template flexible_const_iterator<T>
+ * A way to iterate over a const container, but insert fake elements in it.
+ * In the case of a row, we will have to break some elements, which
+ * create new ones. This class allows to abstract this.
+ * Only the required parts are implemented for now.
+ */
+template<class T>
+class flexible_const_iterator {
+	typedef typename T::value_type value_type;
+public:
+
+	//
+	flexible_const_iterator operator++() {
+		if (pile_.empty())
+			++cit_;
+		else
+			pile_.pop_back();
+		return *this;
+	}
+
+	value_type operator*() const { return pile_.empty() ? *cit_ : pile_.back(); }
+
+	void put(value_type const & e) { pile_.push_back(e); }
+
+// This should be private, but declaring the friend functions is too much work
+//private:
+	typename T::const_iterator cit_;
+	// A vector that is used as like a pile to store the elements to
+	// consider before incrementing the underlying iterator.
+	vector<value_type> pile_;
+};
+
+
+template<class T>
+flexible_const_iterator<T> flexible_begin(T const & t)
+{
+	return { t.begin(), vector<typename T::value_type>() };
+}
+
+
+template<class T>
+flexible_const_iterator<T> flexible_end(T const & t)
+{
+	return { t.end(), vector<typename T::value_type>() };
+}
+
+
+// Equality is only possible if respective piles are empty
+template<class T>
+bool operator==(flexible_const_iterator<T> const & t1,
+                flexible_const_iterator<T> const & t2)
+{
+	return t1.cit_ == t2.cit_ && t1.pile_.empty() && t2.pile_.empty();
+}
+
 }
 
 
@@ -1005,11 +1061,8 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 	bool need_new_row = true;
 	pos_type pos = 0;
 	int width = 0;
-	Row::const_iterator cit = bigrow.begin();
-	Row::const_iterator const end = bigrow.end();
-	// This is a vector, but we use it like a pile putting and taking
-	// stuff at the back.
-	Row::Elements pile;
+	flexible_const_iterator<Row> fcit = flexible_begin(bigrow);
+	flexible_const_iterator<Row> const end = flexible_end(bigrow);
 	while (true) {
 		if (need_new_row) {
 			if (!rows.empty()) {
@@ -1028,27 +1081,25 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 
 		// The stopping condition is here because we may need a new
 		// empty row at the end.
-		if (cit == end && pile.empty())
+		if (fcit == end)
 			break;
 
 		// Next element to consider is either the top of the temporary
 		// pile, or the place when we were in main row
-		Row::Element elt = pile.empty() ? *cit : pile.back();
-		//LYXERR0("elt=" << elt);
+		Row::Element elt = *fcit;
 		Row::Element next_elt = elt.splitAt(width - rows.back().width(),
 		                                    !elt.font.language()->wordWrap());
-		//LYXERR0("next_elt=" << next_elt);
 		// a new element in the row
 		rows.back().push_back(elt);
 		pos = elt.endpos;
+
 		// Go to next element
-		if (pile.empty())
-			++cit;
-		else
-			pile.pop_back();
+		++fcit;
+
 		// Add a new next element on the pile
 		if (next_elt.isValid()) {
-			pile.push_back(next_elt);
+			// do as if we inserted this element in the original row
+			fcit.put(next_elt);
 			need_new_row = true;
 		}
 	}

commit 1b98b91bc8b1cc7966abfa4b42e68194ee92843d
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Fri Jul 16 00:10:25 2021 +0200

    A set of easy fixes and missing features
    
    * show changebar when end of paragraph is changed.
    
    * when row is finished, set endpos and right_boundary
    
    * handle bidi.

diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index d8e6226..2e5bd63 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -954,12 +954,15 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
 	row.finalizeLast();
 	row.endpos(end);
 
-	// End of paragraph marker. The logic here is almost the
+	// End of paragraph marker, either if LyXRc requires it, or there
+	// is an end of paragraph change. The logic here is almost the
 	// same as in redoParagraph, remember keep them in sync.
 	ParagraphList const & pars = text_->paragraphs();
-	Change const & change = par.lookupChange(i);
-	if ((lyxrc.paragraph_markers || change.changed())
-	    && i == end && size_type(pit + 1) < pars.size()) {
+	Change const & endchange = par.lookupChange(end);
+	if (endchange.changed())
+		row.needsChangeBar(true);
+	if ((lyxrc.paragraph_markers || endchange.changed())
+	    && size_type(pit + 1) < pars.size()) {
 		// add a virtual element for the end-of-paragraph
 		// marker; it is shown on screen, but does not exist
 		// in the paragraph.
@@ -967,7 +970,7 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
 		f.fontInfo().setColor(Color_paragraphmarker);
 		f.setLanguage(par.getParLanguage(buf.params()));
 		// ¶ U+00B6 PILCROW SIGN
-		row.addVirtual(end, docstring(1, char_type(0x00B6)), f, change);
+		row.addVirtual(end, docstring(1, char_type(0x00B6)), f, endchange);
 	}
 
 	return row;
@@ -994,24 +997,30 @@ Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
 }
 
 
-RowList TextMetrics::breakParagraph(Row const & row) const
+RowList TextMetrics::breakParagraph(Row const & bigrow) const
 {
 	RowList rows;
-	bool const is_rtl = text_->isRTL(row.pit());
+	bool const is_rtl = text_->isRTL(bigrow.pit());
 
 	bool need_new_row = true;
 	pos_type pos = 0;
 	int width = 0;
-	Row::const_iterator cit = row.begin();
-	Row::const_iterator const end = row.end();
+	Row::const_iterator cit = bigrow.begin();
+	Row::const_iterator const end = bigrow.end();
 	// This is a vector, but we use it like a pile putting and taking
 	// stuff at the back.
 	Row::Elements pile;
 	while (true) {
 		if (need_new_row) {
-			if (!rows.empty())
-				rows.back().endpos(pos);
-			rows.push_back(newRow(*this, row.pit(), pos, is_rtl));
+			if (!rows.empty()) {
+				Row & rb = rows.back();
+				rb.endpos(pos);
+				rb.right_boundary(!rb.empty() && rb.endpos() < bigrow.endpos()
+								   && rb.back().endpos == rb.endpos());
+				// make sure that the RTL elements are in reverse ordering
+				rb.reverseRTL(is_rtl);
+			}
+			rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl));
 			// the width available for the row.
 			width = max_width_ - rows.back().right_margin;
 			need_new_row = false;
@@ -1044,6 +1053,16 @@ RowList TextMetrics::breakParagraph(Row const & row) const
 		}
 	}
 
+	if (!rows.empty()) {
+		Row & rb = rows.back();
+		// Last row in paragraph is flushed
+		rb.flushed(true);
+		rb.endpos(bigrow.endpos());
+		rb.right_boundary(false);
+		// make sure that the RTL elements are in reverse ordering
+		rb.reverseRTL(is_rtl);
+	}
+
 	return rows;
 }
 
@@ -1216,14 +1235,14 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 
 	if (row.shortenIfNeeded(width, next_width))
 		row.flushed(false);
-	row.right_boundary(!row.empty() && row.endpos() < end
-	                   && row.back().endpos == row.endpos());
+	row.right_boundary(!row.empty() && row.endpos() < end//
+	                   && row.back().endpos == row.endpos());//
 	// Last row in paragraph is flushed
-	if (row.endpos() == end)
-		row.flushed(true);
+	if (row.endpos() == end)//
+		row.flushed(true);//
 
 	// make sure that the RTL elements are in reverse ordering
-	row.reverseRTL(is_rtl);
+	row.reverseRTL(is_rtl);//
 	//LYXERR0("breakrow: row is " << row);
 
 	return need_new_row;

commit b2b11baa07669fccd154a65d4cc254d59f4273e6
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Wed Jul 14 00:48:03 2021 +0200

    Use the new tokenizing and breaking code instead of breakRow.

diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 12f0b42..d8e6226 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -515,43 +515,28 @@ bool TextMetrics::redoParagraph(pit_type const pit, bool const align_rows)
 		}
 	}
 
-	pos_type first = 0;
-	size_t row_index = 0;
-	bool need_new_row = false;
-	// maximum pixel width of a row
-	do {
-		if (row_index == pm.rows().size())
-			pm.rows().push_back(Row());
-		else
-			pm.rows()[row_index] = Row();
-		Row & row = pm.rows()[row_index];
-		row.pit(pit);
-		row.pos(first);
-		need_new_row = breakRow(row, right_margin);
+	// Transform the paragraph into a single row containing all the elements.
+	Row const bigrow = tokenizeParagraph(pit);
+	// Split the row in several rows fitting in available width
+	pm.rows() = breakParagraph(bigrow);
+
+	/* If there is more than one row, expand the text to the full
+	 * allowable width. This setting here is needed for the
+	 * setRowAlignment() below. We do nothing when tight insets are
+	 * requested.
+	 */
+	if (pm.rows().size() > 1 && !tight_ && dim_.wid < max_width_)
+			dim_.wid = max_width_;
+
+	// Compute height and alignment of the rows.
+	for (Row & row : pm.rows()) {
 		setRowHeight(row);
-		row.changed(true);
-		if ((row_index || row.endpos() < par.size() || row.right_boundary())
-		    && !tight_) {
-			/* If there is more than one row or the row has been
-			 * broken by a display inset or a newline, expand the text
-			 * to the full allowable width. This setting here is
-			 * needed for the setRowAlignment() below.
-			 * We do nothing when tight insets are requested.
-			 */
-			if (dim_.wid < max_width_)
-				dim_.wid = max_width_;
-		}
 		if (align_rows)
 			setRowAlignment(row, max(dim_.wid, row.width()));
-		first = row.endpos();
-		++row_index;
 
 		pm.dim().wid = max(pm.dim().wid, row.width() + row.right_margin);
 		pm.dim().des += row.height();
-	} while (first < par.size() || need_new_row);
-
-	if (row_index < pm.rows().size())
-		pm.rows().resize(row_index);
+	}
 
 	// This type of margin can only be handled at the global paragraph level
 	if (par.layout().margintype == MARGIN_RIGHT_ADDRESS_BOX) {

commit d6c2f362de95341ecef5497ab3287f39b26f7ede
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Wed Jul 14 00:47:42 2021 +0200

    Break the paragraph's big row according to margins
    
    Still many features missing:
    - handle insets that break rows (display math, newline, ...)
    - handle rows that are too long by replacing the single call to
      breakAt with a call to a reworked Row::shortenIfNeeded.
    - some easy things at the end of breakRow (bidi text, etc.).

diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 8728a6a..12f0b42 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -989,6 +989,79 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
 }
 
 
+namespace {
+
+Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
+{
+	Row nrow;
+	nrow.pit(pit);
+	nrow.pos(pos);
+	nrow.left_margin = tm.leftMargin(pit, pos);
+	nrow.right_margin = tm.rightMargin(pit);
+	if (is_rtl)
+		swap(nrow.left_margin, nrow.right_margin);
+	// Remember that the row width takes into account the left_margin
+	// but not the right_margin.
+	nrow.dim().wid = nrow.left_margin;
+	return nrow;
+}
+
+}
+
+
+RowList TextMetrics::breakParagraph(Row const & row) const
+{
+	RowList rows;
+	bool const is_rtl = text_->isRTL(row.pit());
+
+	bool need_new_row = true;
+	pos_type pos = 0;
+	int width = 0;
+	Row::const_iterator cit = row.begin();
+	Row::const_iterator const end = row.end();
+	// This is a vector, but we use it like a pile putting and taking
+	// stuff at the back.
+	Row::Elements pile;
+	while (true) {
+		if (need_new_row) {
+			if (!rows.empty())
+				rows.back().endpos(pos);
+			rows.push_back(newRow(*this, row.pit(), pos, is_rtl));
+			// the width available for the row.
+			width = max_width_ - rows.back().right_margin;
+			need_new_row = false;
+		}
+
+		// The stopping condition is here because we may need a new
+		// empty row at the end.
+		if (cit == end && pile.empty())
+			break;
+
+		// Next element to consider is either the top of the temporary
+		// pile, or the place when we were in main row
+		Row::Element elt = pile.empty() ? *cit : pile.back();
+		//LYXERR0("elt=" << elt);
+		Row::Element next_elt = elt.splitAt(width - rows.back().width(),
+		                                    !elt.font.language()->wordWrap());
+		//LYXERR0("next_elt=" << next_elt);
+		// a new element in the row
+		rows.back().push_back(elt);
+		pos = elt.endpos;
+		// Go to next element
+		if (pile.empty())
+			++cit;
+		else
+			pile.pop_back();
+		// Add a new next element on the pile
+		if (next_elt.isValid()) {
+			pile.push_back(next_elt);
+			need_new_row = true;
+		}
+	}
+
+	return rows;
+}
+
 /** This is the function where the hard work is done. The code here is
  * very sensitive to small changes :) Note that part of the
  * intelligence is also in Row::shortenIfNeeded.
@@ -1002,20 +1075,20 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 		theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id());//
 
 	pos_type const end = par.size();//
-	pos_type const pos = row.pos();
+	pos_type const pos = row.pos();//
 	pos_type const body_pos = par.beginOfBody();//
-	bool const is_rtl = text_->isRTL(row.pit());
-	bool need_new_row = false;
+	bool const is_rtl = text_->isRTL(row.pit());//
+	bool need_new_row = false;//
 
-	row.left_margin = leftMargin(row.pit(), pos);
-	row.right_margin = right_margin;
-	if (is_rtl)
-		swap(row.left_margin, row.right_margin);
+	row.left_margin = leftMargin(row.pit(), pos);//
+	row.right_margin = right_margin;//
+	if (is_rtl)//
+		swap(row.left_margin, row.right_margin);//
 	// Remember that the row width takes into account the left_margin
 	// but not the right_margin.
-	row.dim().wid = row.left_margin;
+	row.dim().wid = row.left_margin;//
 	// the width available for the row.
-	int const width = max_width_ - row.right_margin;
+	int const width = max_width_ - row.right_margin;//
 
 	// check for possible inline completion
 	DocIterator const & ic_it = bv_->inlineCompletionPos();//
diff --git a/src/TextMetrics.h b/src/TextMetrics.h
index 1666cd0..3ff1161 100644
--- a/src/TextMetrics.h
+++ b/src/TextMetrics.h
@@ -153,6 +153,8 @@ private:
 
 	Row tokenizeParagraph(pit_type pit) const;
 
+	RowList breakParagraph(Row const & row) const;
+
 	/// sets row.end to the pos value *after* which a row should break.
 	/// for example, the pos after which isNewLine(pos) == true
 	/// \return true when another row is required (after a newline)

-----------------------------------------------------------------------

Summary of changes:
 src/Row.cpp                         |   43 +++++++++++++++++++---------------
 src/TextMetrics.cpp                 |    5 ++-
 src/frontends/qt/GuiFontMetrics.cpp |   14 +++++++++--
 3 files changed, 38 insertions(+), 24 deletions(-)


hooks/post-receive
-- 
Repository for new features



More information about the lyx-cvs mailing list