[LyX features/breakrows] Workaround for Qt 4

Jean-Marc Lasgouttes lasgouttes at lyx.org
Tue Aug 31 08:18:38 UTC 2021


The branch, breakrows, has been updated.
  discards  421adf6f4d46854eab042f1a06cf0fd48a70af5a (commit)
  discards  5553b62f2ee8c769ca00439393686bebcd5f01d3 (commit)
  discards  d6b6533b971afc6918f1685d8d44c73b6ed1c009 (commit)
  discards  13617d062146289273f3ef6f6d7b5c029a1cfe66 (commit)
  discards  129956fcdb3cda44b035044aa6eb3102f912fa02 (commit)
  discards  4c5aec9189e7365140906a2f81f4226af7707609 (commit)
  discards  5715ac1515cef1504da11082e90696c82655eacf (commit)
  discards  a282faad29bb8de11b69b95e3479110cca3784bf (commit)
  discards  fdfbbde36271bced06d148c7dd1e3af290049f40 (commit)
  discards  cc2dabc540e620ef1b2e9a6c7ca74f8743239048 (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 (421adf6f4d46854eab042f1a06cf0fd48a70af5a)
            \
             N -- N -- N (5e0358f3d3ad6001f471a7144db7c46cd1b06ee6)

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 5e0358f3d3ad6001f471a7144db7c46cd1b06ee6
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.
    
    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..fb4f27d 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,14 @@ 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
+		// At least in some cases, the value of lineWidth() can be wrong with Qt4.
+		line_wid = width(s);
+#endif
+	}
 	return {len, line_wid};
 }
 

commit d3183dd0f098d499921bd7bbc0ca180e62879c6d
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 7013a2f..73791e3 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,20 +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;
+			}
 		}
 	}
 
@@ -1146,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 aa8b2842aea04422636aee1178ac13d6bc068dd8
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 7d60901..7013a2f 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;
@@ -1107,13 +1138,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;
@@ -1227,9 +1254,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
@@ -1254,7 +1280,6 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 		++i;
 		++fi;
 	}
-	//--------------------------------------------------------------------vvv
 	row.finalizeLast();
 	row.endpos(i);
 

commit 5fd50ad8adb1f834cb96b7d4dbc1735aa300923c
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 76d781f..7d60901 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1092,6 +1092,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
 
 		// a new element in the row
 		rows.back().push_back(elt);
+		rows.back().finalizeLast();
 		pos = elt.endpos;
 
 		// Go to next element

commit 1fb689b431c1f28ac6e060fd24f79c89ad861267
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 cb944f5..76d781f 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,26 @@ 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 1b66319af14f56c6a5026ea6dfb1794ff0212954
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 dd4d2e5..cb944f5 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 f2157eee172ac020e8f941c16cda08d31fc45cf9
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 18da8b1..dd4d2e5 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 ebd53055fe9c474d26bf9d5e9a08c743c3f26892
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..18da8b1 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)

commit 3b378493b7c381d78098d6c0fb4e5c0c0a3f01e7
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Mon Jul 12 00:07:59 2021 +0200

    Implement Row::Element::row_flags
    
    Move the enum definition RowFlags in its own include file, to avoid
    loading Inset.h. Document it more thoroughly.
    
    Rename RowAfter to AlwaysBreakAfter.
    
    Add CanBreakInside (rows that can be themselves broken). This allow to
    differentiate elements before bodyPos() and allows to remove a
    parameter to shortenIfNeeded().
    
    Make the Inset::rowFlags() method return int instead of RowFlags, as
    should be done for all the bitwise flags. Remove the hand-made bitwise
    operators.
    
    Set R::E::row_flags when creating elements.
    * INSET elements use the inset's rowFLags();
    * virtual element forbid breaking before them, and inherit the *After
      flags from the previous element of the row;
    * STRING elements usr CanBreakInside, except before bodyPos.
    
    More stuff may be added later.

diff --git a/src/Makefile.am b/src/Makefile.am
index 31701f2..99a0f98 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -273,6 +273,7 @@ HEADERFILESCORE = \
 	ParIterator.h \
 	PDFOptions.h \
 	Row.h \
+	RowFlags.h \
 	RowPainter.h \
 	Server.h \
 	ServerSocket.h \
diff --git a/src/Row.cpp b/src/Row.cpp
index da2d885..705a147 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -135,7 +135,7 @@ pos_type Row::Element::x2pos(int &x) const
 
 Row::Element Row::Element::splitAt(int w, bool force)
 {
-	if (type != STRING)
+	if (type != STRING || !(row_flags & CanBreakInside))
 		return Element();
 
 	FontMetrics const & fm = theFontMetrics(font);
@@ -145,6 +145,7 @@ Row::Element Row::Element::splitAt(int w, bool force)
 		Element ret(STRING, pos + i, font, change);
 		ret.str = str.substr(i);
 		ret.endpos = ret.pos + ret.str.length();
+		ret.row_flags = row_flags & (CanBreakInside | AfterFlags);
 		str.erase(i);
 		endpos = pos + i;
 		//lyxerr << "breakAt(" << w << ")  Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl;
@@ -378,12 +379,13 @@ void Row::finalizeLast()
 
 
 void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
-	      Font const & f, Change const & ch)
+              Font const & f, Change const & ch)
 {
 	finalizeLast();
 	Element e(INSET, pos, f, ch);
 	e.inset = ins;
 	e.dim = dim;
+	e.row_flags = ins->rowFlags();
 	elements_.push_back(e);
 	dim_.wid += dim.wid;
 	changebar_ |= ins->isChanged();
@@ -391,11 +393,12 @@ void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
 
 
 void Row::add(pos_type const pos, char_type const c,
-	      Font const & f, Change const & ch)
+              Font const & f, Change const & ch, bool can_break)
 {
 	if (!sameString(f, ch)) {
 		finalizeLast();
 		Element e(STRING, pos, f, ch);
+		e.row_flags = can_break ? CanBreakInside : Inline;
 		elements_.push_back(e);
 	}
 	if (back().str.length() % 30 == 0) {
@@ -420,6 +423,10 @@ void Row::addVirtual(pos_type const pos, docstring const & s,
 	e.dim.wid = theFontMetrics(f).width(s);
 	dim_.wid += e.dim.wid;
 	e.endpos = pos;
+	// Copy after* flags from previous elements, forbid break before element
+	int const prev_row_flags = elements_.empty() ? Inline : elements_.back().row_flags;
+	int const can_inherit = AfterFlags & ~AlwaysBreakAfter;
+	e.row_flags = (prev_row_flags & can_inherit) | NoBreakBefore;
 	elements_.push_back(e);
 	finalizeLast();
 }
@@ -450,7 +457,7 @@ void Row::pop_back()
 }
 
 
-bool Row::shortenIfNeeded(pos_type const keep, int const w, int const next_width)
+bool Row::shortenIfNeeded(int const w, int const next_width)
 {
 	if (empty() || width() <= w)
 		return false;
@@ -482,11 +489,10 @@ bool Row::shortenIfNeeded(pos_type const keep, int const w, int const next_width
 		// make a copy of the element to work on it.
 		Element brk = *cit_brk;
 		/* If the current element is an inset that allows breaking row
-		 * after itself, and it the row is already short enough after
+		 * after itself, and if the row is already short enough after
 		 * this inset, then cut right after this element.
 		 */
-		if (wid_brk <= w && brk.type == INSET
-		    && brk.inset->rowFlags() & Inset::CanBreakAfter) {
+		if (wid_brk <= w && brk.row_flags & CanBreakAfter) {
 			end_ = brk.endpos;
 			dim_.wid = wid_brk;
 			elements_.erase(cit_brk + 1, end);
@@ -504,10 +510,6 @@ bool Row::shortenIfNeeded(pos_type const keep, int const w, int const next_width
 		 * not allowed at the beginning or end of line.
 		*/
 		bool const word_wrap = brk.font.language()->wordWrap();
-		// When there is text before the body part (think description
-		// environment), do not try to break.
-		if (brk.pos < keep)
-			continue;
 		/* We have found a suitable separable element. This is the common case.
 		 * Try to break it cleanly (at word boundary) at a length that is both
 		 * - less than the available space on the row
diff --git a/src/Row.h b/src/Row.h
index 3048cf1..4fdcee4 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -18,6 +18,7 @@
 #include "Changes.h"
 #include "Dimension.h"
 #include "Font.h"
+#include "RowFlags.h"
 
 #include "support/docstring.h"
 #include "support/types.h"
@@ -143,6 +144,8 @@ public:
 		Change change;
 		// is it possible to add contents to this element?
 		bool final = false;
+		// properties with respect to row breaking (made of RowFlag enums)
+		int row_flags = Inline;
 
 		friend std::ostream & operator<<(std::ostream & os, Element const & row);
 	};
@@ -247,7 +250,7 @@ public:
 		 Font const & f, Change const & ch);
 	///
 	void add(pos_type pos, char_type const c,
-		 Font const & f, Change const & ch);
+	         Font const & f, Change const & ch, bool can_break);
 	///
 	void addVirtual(pos_type pos, docstring const & s,
 			Font const & f, Change const & ch);
@@ -287,12 +290,11 @@ public:
 	 * if row width is too large, remove all elements after last
 	 * separator and update endpos if necessary. If all that
 	 * remains is a large word, cut it to \param width.
-	 * \param body_pos minimum amount of text to keep.
 	 * \param width maximum width of the row.
 	 * \param available width on next row.
 	 * \return true if the row has been shortened.
 	 */
-	bool shortenIfNeeded(pos_type const body_pos, int const width, int const next_width);
+	bool shortenIfNeeded(int const width, int const next_width);
 
 	/**
 	 * If last element of the row is a string, compute its width
diff --git a/src/RowFlags.h b/src/RowFlags.h
new file mode 100644
index 0000000..f94f0c6
--- /dev/null
+++ b/src/RowFlags.h
@@ -0,0 +1,57 @@
+// -*- C++ -*-
+/**
+ * \file RowFlags.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Jean-Marc Lasgouttes
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#ifndef ROWFLAGS_H
+#define ROWFLAGS_H
+
+// Do not include anything here
+
+namespace lyx {
+
+/* The list of possible flags, that can be combined.
+ * Some flags that should logically be here (e.g.,
+ * CanBreakBefore), do not exist. This is because the need has not
+ * been identitfied yet.
+ *
+ * Priorities when before/after disagree:
+ *      AlwaysBreak* > NoBreak* > Break* or CanBreak*.
+ */
+enum RowFlags {
+	// Do not break before or after this element, except if really
+	// needed (between NoBreak* and CanBreak*).
+	Inline = 0,
+	// break row before this element if the row is not empty
+	BreakBefore = 1 << 0,
+	// Avoid breaking row before this element
+	NoBreakBefore = 1 << 1,
+	// force new (maybe empty) row after this element
+	AlwaysBreakAfter = 1 << 2,
+	// break row after this element if there are more elements
+	BreakAfter = 1 << 3,
+	// break row whenever needed after this element
+	CanBreakAfter = 1 << 4,
+	// Avoid breaking row after this element
+	NoBreakAfter = 1 << 5,
+	// The contents of the row may be broken in two (e.g. string)
+	CanBreakInside = 1 << 6,
+	// specify an alignment (left, right) for a display element
+	// (default is center)
+	AlignLeft = 1 << 7,
+	AlignRight = 1 << 8,
+	// A display element breaks row at both ends
+	Display = BreakBefore | BreakAfter,
+	// Flags that concern breaking after element
+	AfterFlags = AlwaysBreakAfter | BreakAfter | CanBreakAfter | NoBreakAfter
+};
+
+} // namespace lyx
+
+#endif
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 6035c7e..8728a6a 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -633,10 +633,10 @@ LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const
 
 	// Display-style insets should always be on a centered row
 	if (Inset const * inset = par.getInset(row.pos())) {
-		if (inset->rowFlags() & Inset::Display) {
-			if (inset->rowFlags() & Inset::AlignLeft)
+		if (inset->rowFlags() & Display) {
+			if (inset->rowFlags() & AlignLeft)
 				align = LYX_ALIGN_BLOCK;
-			else if (inset->rowFlags() & Inset::AlignRight)
+			else if (inset->rowFlags() & AlignRight)
 				align = LYX_ALIGN_RIGHT;
 			else
 				align = LYX_ALIGN_CENTER;
@@ -927,7 +927,7 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
 			row.addSpace(i, add, *fi, par.lookupChange(i));
 		} else if (c == '\t')
 			row.addSpace(i, theFontMetrics(*fi).width(from_ascii("    ")),
-				     *fi, par.lookupChange(i));
+			             *fi, par.lookupChange(i));
 		else if (c == 0x2028 || c == 0x2029) {
 			/**
 			 * U+2028 LINE SEPARATOR
@@ -943,9 +943,10 @@ Row TextMetrics::tokenizeParagraph(pit_type const pit) const
 			// ⤶ 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));
+			row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
 		} else
-			row.add(i, c, *fi, par.lookupChange(i));
+			// row elements before body are unbreakable
+			row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
 
 		// add inline completion width
 		// draw logically behind the previous character
@@ -1079,9 +1080,9 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 			// ⤶ 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));
+			row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
 		} else
-			row.add(i, c, *fi, par.lookupChange(i));
+			row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
 
 		// add inline completion width
 		// draw logically behind the previous character
@@ -1104,8 +1105,8 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 		// - 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() & Inset::BreakBefore)
-		    || (prevInset && prevInset->rowFlags() & Inset::BreakAfter)) {
+		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
@@ -1113,8 +1114,8 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 			// - 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() & Inset::RowAfter
-				 || (prevInset->rowFlags() & Inset::BreakAfter && i + 1 == end
+				(prevInset->rowFlags() & AlwaysBreakAfter
+				 || (prevInset->rowFlags() & BreakAfter && i + 1 == end
 				     && text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL));
 			++i;
 			break;
@@ -1155,7 +1156,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 	int const next_width = max_width_ - leftMargin(row.pit(), row.endpos())
 		- rightMargin(row.pit());
 
-	if (row.shortenIfNeeded(body_pos, width, next_width))
+	if (row.shortenIfNeeded(width, next_width))
 		row.flushed(false);
 	row.right_boundary(!row.empty() && row.endpos() < end
 	                   && row.back().endpos == row.endpos());
@@ -1899,7 +1900,7 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
 	    // display style insets do not need indentation
 	    && !(!par.empty()
 	         && par.isInset(0)
-	         && par.getInset(0)->rowFlags() & Inset::Display)
+	         && par.getInset(0)->rowFlags() & Display)
 	    && (!(tclass.isDefaultLayout(par.layout())
 	        || tclass.isPlainLayout(par.layout()))
 	        || buffer.params().paragraph_separation
diff --git a/src/insets/Inset.h b/src/insets/Inset.h
index af24423..372b95a 100644
--- a/src/insets/Inset.h
+++ b/src/insets/Inset.h
@@ -20,6 +20,7 @@
 #include "LayoutEnums.h"
 #include "OutputEnums.h"
 #include "OutputParams.h"
+#include "RowFlags.h"
 
 #include "support/docstring.h"
 #include "support/strfwd.h"
@@ -478,26 +479,8 @@ public:
 
 	virtual CtObject getCtObject(OutputParams const &) const;
 
-	enum RowFlags {
-		Inline = 0,
-		// break row before this inset
-		BreakBefore = 1 << 0,
-		// break row after this inset
-		BreakAfter = 1 << 1,
-		// it is possible to break after this inset
-		CanBreakAfter = 1 << 2,
-		// force new (maybe empty) row after this inset
-		RowAfter = 1 << 3,
-		// specify an alignment (left, right) for a display inset
-		// (default is center)
-		AlignLeft = 1 << 4,
-		AlignRight = 1 << 5,
-		// A display inset breaks row at both ends
-		Display = BreakBefore | BreakAfter
-	};
-
-	/// How should this inset be displayed in its row?
-	virtual RowFlags rowFlags() const { return Inline; }
+	// properties with respect to row breaking (made of RowFLag enums)
+	virtual int rowFlags() const { return Inline; }
 	/// indentation before this inset (only needed for displayed hull insets with fleqn option)
 	virtual int indent(BufferView const &) const { return 0; }
 	///
@@ -655,20 +638,6 @@ protected:
 };
 
 
-inline Inset::RowFlags operator|(Inset::RowFlags const d1,
-                                    Inset::RowFlags const d2)
-{
-	return static_cast<Inset::RowFlags>(int(d1) | int(d2));
-}
-
-
-inline Inset::RowFlags operator&(Inset::RowFlags const d1,
-                                    Inset::RowFlags const d2)
-{
-	return static_cast<Inset::RowFlags>(int(d1) & int(d2));
-}
-
-
 } // namespace lyx
 
 #endif
diff --git a/src/insets/InsetBibtex.h b/src/insets/InsetBibtex.h
index be7659f..55451f5 100644
--- a/src/insets/InsetBibtex.h
+++ b/src/insets/InsetBibtex.h
@@ -47,7 +47,7 @@ public:
 	///
 	InsetCode lyxCode() const override { return BIBTEX_CODE; }
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	///
 	void latex(otexstream &, OutputParams const &) const override;
 	///
diff --git a/src/insets/InsetCaption.h b/src/insets/InsetCaption.h
index ed6dbbb..c1bcd17 100644
--- a/src/insets/InsetCaption.h
+++ b/src/insets/InsetCaption.h
@@ -40,7 +40,7 @@ private:
 	///
 	void write(std::ostream & os) const override;
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	///
 	bool neverIndent() const override { return true; }
 	///
diff --git a/src/insets/InsetFloatList.h b/src/insets/InsetFloatList.h
index 489b0fe..ce6caa5 100644
--- a/src/insets/InsetFloatList.h
+++ b/src/insets/InsetFloatList.h
@@ -32,7 +32,7 @@ public:
 	///
 	InsetCode lyxCode() const override { return FLOAT_LIST_CODE; }
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	///
 	void write(std::ostream &) const override;
 	///
diff --git a/src/insets/InsetInclude.cpp b/src/insets/InsetInclude.cpp
index aeae2fb..10ea52b 100644
--- a/src/insets/InsetInclude.cpp
+++ b/src/insets/InsetInclude.cpp
@@ -1251,7 +1251,7 @@ string InsetInclude::contextMenuName() const
 }
 
 
-Inset::RowFlags InsetInclude::rowFlags() const
+int InsetInclude::rowFlags() const
 {
 	return type(params()) == INPUT ? Inline : Display;
 }
diff --git a/src/insets/InsetInclude.h b/src/insets/InsetInclude.h
index 8585222..d62c751 100644
--- a/src/insets/InsetInclude.h
+++ b/src/insets/InsetInclude.h
@@ -75,7 +75,7 @@ public:
 	///
 	void draw(PainterInfo & pi, int x, int y) const override;
 	///
-	RowFlags rowFlags() const override;
+	int rowFlags() const override;
 	///
 	InsetCode lyxCode() const override { return INCLUDE_CODE; }
 	///
diff --git a/src/insets/InsetIndex.h b/src/insets/InsetIndex.h
index bddb8ba..b064cc7 100644
--- a/src/insets/InsetIndex.h
+++ b/src/insets/InsetIndex.h
@@ -119,7 +119,7 @@ public:
 	///
 	bool hasSettings() const override;
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	//@}
 
 	/// \name Static public methods obligated for InsetCommand derived classes
diff --git a/src/insets/InsetListings.cpp b/src/insets/InsetListings.cpp
index e8fe8b1..57df06e 100644
--- a/src/insets/InsetListings.cpp
+++ b/src/insets/InsetListings.cpp
@@ -64,7 +64,7 @@ InsetListings::~InsetListings()
 }
 
 
-Inset::RowFlags InsetListings::rowFlags() const
+int InsetListings::rowFlags() const
 {
 	return params().isInline() || params().isFloat() ? Inline : Display | AlignLeft;
 }
diff --git a/src/insets/InsetListings.h b/src/insets/InsetListings.h
index 41be439..9d4eeb1 100644
--- a/src/insets/InsetListings.h
+++ b/src/insets/InsetListings.h
@@ -46,7 +46,7 @@ private:
 	///
 	InsetCode lyxCode() const override { return LISTINGS_CODE; }
 	/// lstinline is inlined, normal listing is displayed
-	RowFlags rowFlags() const override;
+	int rowFlags() const override;
 	///
 	docstring layoutName() const override;
 	///
diff --git a/src/insets/InsetNewline.h b/src/insets/InsetNewline.h
index 3d540a8..1ef0ae5 100644
--- a/src/insets/InsetNewline.h
+++ b/src/insets/InsetNewline.h
@@ -47,7 +47,7 @@ public:
 	explicit InsetNewline(InsetNewlineParams par) : Inset(0)
 	{ params_.kind = par.kind; }
 	///
-	RowFlags rowFlags() const override { return BreakAfter | RowAfter; }
+	int rowFlags() const override { return AlwaysBreakAfter; }
 	///
 	static void string2params(std::string const &, InsetNewlineParams &);
 	///
diff --git a/src/insets/InsetNewpage.h b/src/insets/InsetNewpage.h
index f020488..d086276 100644
--- a/src/insets/InsetNewpage.h
+++ b/src/insets/InsetNewpage.h
@@ -76,7 +76,7 @@ private:
 	///
 	void write(std::ostream & os) const override;
 	///
-	RowFlags rowFlags() const override { return (params_.kind == InsetNewpageParams::NOPAGEBREAK) ? Inline : Display; }
+	int rowFlags() const override { return (params_.kind == InsetNewpageParams::NOPAGEBREAK) ? Inline : Display; }
 	///
 	docstring insetLabel() const;
 	///
diff --git a/src/insets/InsetNomencl.h b/src/insets/InsetNomencl.h
index 1778e01..362cd46 100644
--- a/src/insets/InsetNomencl.h
+++ b/src/insets/InsetNomencl.h
@@ -94,7 +94,7 @@ public:
 	///
 	bool hasSettings() const override { return true; }
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	///
 	void latex(otexstream &, OutputParams const &) const override;
 	///
diff --git a/src/insets/InsetSeparator.h b/src/insets/InsetSeparator.h
index f7e0ab9..9352bdf 100644
--- a/src/insets/InsetSeparator.h
+++ b/src/insets/InsetSeparator.h
@@ -65,7 +65,7 @@ public:
 		return docstring();
 	}
 	///
-	RowFlags rowFlags() const override { return BreakAfter; }
+	int rowFlags() const override { return BreakAfter; }
 private:
 	///
 	InsetCode lyxCode() const override { return SEPARATOR_CODE; }
diff --git a/src/insets/InsetSpace.cpp b/src/insets/InsetSpace.cpp
index 9585596..1a2eb09 100644
--- a/src/insets/InsetSpace.cpp
+++ b/src/insets/InsetSpace.cpp
@@ -192,7 +192,7 @@ bool InsetSpace::getStatus(Cursor & cur, FuncRequest const & cmd,
 }
 
 
-Inset::RowFlags InsetSpace::rowFlags() const
+int InsetSpace::rowFlags() const
 {
 	switch (params_.kind) {
 		case InsetSpaceParams::PROTECTED:
diff --git a/src/insets/InsetSpace.h b/src/insets/InsetSpace.h
index 5cf7aa7..e401d6d 100644
--- a/src/insets/InsetSpace.h
+++ b/src/insets/InsetSpace.h
@@ -115,7 +115,7 @@ public:
 	///
 	docstring toolTip(BufferView const & bv, int x, int y) const override;
 	/// unprotected spaces allow line breaking after them
-	RowFlags rowFlags() const override;
+	int rowFlags() const override;
 	///
 	void metrics(MetricsInfo &, Dimension &) const override;
 	///
diff --git a/src/insets/InsetSpecialChar.cpp b/src/insets/InsetSpecialChar.cpp
index 3ecdaf1..88af653 100644
--- a/src/insets/InsetSpecialChar.cpp
+++ b/src/insets/InsetSpecialChar.cpp
@@ -83,7 +83,7 @@ docstring InsetSpecialChar::toolTip(BufferView const &, int, int) const
 }
 
 
-Inset::RowFlags InsetSpecialChar::rowFlags() const
+int InsetSpecialChar::rowFlags() const
 {
 	switch (kind_) {
 	case ALLOWBREAK:
diff --git a/src/insets/InsetSpecialChar.h b/src/insets/InsetSpecialChar.h
index 3056b10..0c8cc36 100644
--- a/src/insets/InsetSpecialChar.h
+++ b/src/insets/InsetSpecialChar.h
@@ -63,7 +63,7 @@ public:
 	///
 	docstring toolTip(BufferView const & bv, int x, int y) const override;
 	/// some special chars allow line breaking after them
-	RowFlags rowFlags() const override;
+	int rowFlags() const override;
 	///
 	void metrics(MetricsInfo &, Dimension &) const override;
 	///
diff --git a/src/insets/InsetTOC.h b/src/insets/InsetTOC.h
index 045ae07..ca3f463 100644
--- a/src/insets/InsetTOC.h
+++ b/src/insets/InsetTOC.h
@@ -37,7 +37,7 @@ public:
 	///
 	docstring layoutName() const override;
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	///
 	void validate(LaTeXFeatures &) const override;
 	///
diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp
index d6d9c57..9b22e38 100644
--- a/src/insets/InsetTabular.cpp
+++ b/src/insets/InsetTabular.cpp
@@ -6139,21 +6139,21 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd,
 }
 
 
-Inset::RowFlags InsetTabular::rowFlags() const
-{
-		if (tabular.is_long_tabular) {
-			switch (tabular.longtabular_alignment) {
-			case Tabular::LYX_LONGTABULAR_ALIGN_LEFT:
-				return Display | AlignLeft;
-			case Tabular::LYX_LONGTABULAR_ALIGN_CENTER:
-				return Display;
-			case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT:
-				return Display | AlignRight;
-			default:
-				return Display;
-			}
-		} else
-			return Inline;
+int InsetTabular::rowFlags() const
+{
+	if (tabular.is_long_tabular) {
+		switch (tabular.longtabular_alignment) {
+		case Tabular::LYX_LONGTABULAR_ALIGN_LEFT:
+			return Display | AlignLeft;
+		case Tabular::LYX_LONGTABULAR_ALIGN_CENTER:
+			return Display;
+		case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT:
+			return Display | AlignRight;
+		default:
+			return Display;
+		}
+	} else
+		return Inline;
 }
 
 
diff --git a/src/insets/InsetTabular.h b/src/insets/InsetTabular.h
index 8d1be1b..0d4e7af 100644
--- a/src/insets/InsetTabular.h
+++ b/src/insets/InsetTabular.h
@@ -989,7 +989,7 @@ public:
 	//
 	bool isTable() const override { return true; }
 	///
-	RowFlags rowFlags() const override;
+	int rowFlags() const override;
 	///
 	void latex(otexstream &, OutputParams const &) const override;
 	///
diff --git a/src/insets/InsetVSpace.h b/src/insets/InsetVSpace.h
index 9b95f00..51c2b5b 100644
--- a/src/insets/InsetVSpace.h
+++ b/src/insets/InsetVSpace.h
@@ -62,7 +62,7 @@ private:
 	///
 	void write(std::ostream & os) const override;
 	///
-	RowFlags rowFlags() const override { return Display; }
+	int rowFlags() const override { return Display; }
 	///
 	void doDispatch(Cursor & cur, FuncRequest & cmd) override;
 	///
diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp
index 97d95da..26ac40b 100644
--- a/src/mathed/InsetMathHull.cpp
+++ b/src/mathed/InsetMathHull.cpp
@@ -988,7 +988,7 @@ bool InsetMathHull::outerDisplay() const
 }
 
 
-Inset::RowFlags InsetMathHull::rowFlags() const
+int InsetMathHull::rowFlags() const
 {
 	switch (type_) {
 	case hullUnknown:
diff --git a/src/mathed/InsetMathHull.h b/src/mathed/InsetMathHull.h
index 0b865be..b019188 100644
--- a/src/mathed/InsetMathHull.h
+++ b/src/mathed/InsetMathHull.h
@@ -288,7 +288,7 @@ public:
 	///
 	Inset * editXY(Cursor & cur, int x, int y) override;
 	///
-	RowFlags rowFlags() const override;
+	int rowFlags() const override;
 	/// helper function
 	bool display() const { return rowFlags() & Display; }
 

commit 053f1c4934bc77d491e3ea37da4ebfa35cd9f2bf
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Sun Jul 11 15:19:37 2021 +0200

    Create new method TM::tokenizeParagraph
    
    This contains large parts of breakRow, but creates a unique row for the paragraph.
    
    The parts taken or not in redoParagraph are annotated.
    
    The new method is not used yet.

diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index b0d1ff0..6035c7e 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -867,21 +867,142 @@ private:
 
 } // namespace
 
+
+Row TextMetrics::tokenizeParagraph(pit_type const pit) const
+{
+	Row row;
+	row.pit(pit);
+	Paragraph const & par = text_->getPar(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 body_pos = par.beginOfBody();
+
+	// 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() == 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 = 0;
+	FontIterator fi = FontIterator(*this, par, pit, 0);
+	// 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)
+			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(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));
+		} else
+			row.add(i, c, *fi, par.lookupChange(i));
+
+		// 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());
+		}
+
+		++i;
+		++fi;
+	}
+	row.finalizeLast();
+	row.endpos(end);
+
+	// 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())
+	    && i == end && 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.
+		Font f(text_->layoutFont(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);
+	}
+
+	return row;
+}
+
+
 /** 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());
+	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 end = par.size();//
 	pos_type const pos = row.pos();
-	pos_type const body_pos = par.beginOfBody();
+	pos_type const body_pos = par.beginOfBody();//
 	bool const is_rtl = text_->isRTL(row.pit());
 	bool need_new_row = false;
 
@@ -896,14 +1017,14 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 	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();
+	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;
+	pos_type i = pos;//---------------------------------------------------vvv
 	FontIterator fi = FontIterator(*this, par, row.pit(), pos);
 	// The real stopping condition is a few lines below.
 	while (true) {
@@ -920,7 +1041,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 
 		// 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))
+		if (i >= end || (i != pos && row.width() > width))//^width
 			break;
 
 		char_type c = par.getChar(i);
@@ -975,8 +1096,9 @@ 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
@@ -1001,6 +1123,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 		++i;
 		++fi;
 	}
+	//--------------------------------------------------------------------vvv
 	row.finalizeLast();
 	row.endpos(i);
 
@@ -1009,7 +1132,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 	ParagraphList const & pars = text_->paragraphs();
 	Change const & change = par.lookupChange(i);
 	if ((lyxrc.paragraph_markers || change.changed())
-	    && !need_new_row
+	    && !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
@@ -1024,6 +1147,8 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
 	// 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.
diff --git a/src/TextMetrics.h b/src/TextMetrics.h
index 1501250..1666cd0 100644
--- a/src/TextMetrics.h
+++ b/src/TextMetrics.h
@@ -151,6 +151,8 @@ private:
 	/// FIXME??
 	int labelEnd(pit_type const pit) const;
 
+	Row tokenizeParagraph(pit_type pit) 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)

commit 920af321c10d351e63c11178ff5d8a50f341a927
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Sun Jul 11 15:33:33 2021 +0200

    Small Row cleanups
    
    Move declaration of RowList to Row.h
    
    Move initialization of POD members of Row and Row::Element to declaration.
    
    Make method isVirtual() depend on type.
    
    Add new row element type INVALID and method isValid()
    
    Make methods R::E::left/right_pos inline.
    
    Add method R::E::splitAt() that returns an element containing the
    remaining stuff, or an invalid element if nothing was split. breakAt
    is now a simple wrapper around this function.
    
    Add method R::push_back().

diff --git a/src/ParagraphMetrics.h b/src/ParagraphMetrics.h
index 9889473..1d690aa 100644
--- a/src/ParagraphMetrics.h
+++ b/src/ParagraphMetrics.h
@@ -20,17 +20,8 @@
 #include "Dimension.h"
 #include "Row.h"
 
-#include <vector>
-
 namespace lyx {
 
-/**
- * Each paragraph is broken up into a number of rows on the screen.
- * This is a list of such on-screen rows, ordered from the top row
- * downwards.
- */
-typedef std::vector<Row> RowList;
-
 class BufferView;
 class Paragraph;
 
diff --git a/src/Row.cpp b/src/Row.cpp
index 2f6db3d..da2d885 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -124,53 +124,43 @@ pos_type Row::Element::x2pos(int &x) const
 			x = 0;
 			i = isRTL();
 		}
+		break;
+	case INVALID:
+		LYXERR0("x2pos: INVALID row element !");
 	}
 	//lyxerr << "=> p=" << pos + i << " x=" << x << endl;
 	return pos + i;
 }
 
 
-bool Row::Element::breakAt(int w, bool force)
+Row::Element Row::Element::splitAt(int w, bool force)
 {
 	if (type != STRING)
-		return false;
+		return Element();
 
 	FontMetrics const & fm = theFontMetrics(font);
 	dim.wid = w;
 	int const i = fm.breakAt(str, dim.wid, isRTL(), force);
 	if (i != -1) {
+		Element ret(STRING, pos + i, font, change);
+		ret.str = str.substr(i);
+		ret.endpos = ret.pos + ret.str.length();
 		str.erase(i);
 		endpos = pos + i;
 		//lyxerr << "breakAt(" << w << ")  Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl;
+		return ret;
 	}
 
-	return i != - 1;
+	return Element();
 }
 
 
-pos_type Row::Element::left_pos() const
-{
-	return isRTL() ? endpos : pos;
-}
-
-
-pos_type Row::Element::right_pos() const
+bool Row::Element::breakAt(int w, bool force)
 {
-	return isRTL() ? pos : endpos;
+	return splitAt(w, force).isValid();
 }
 
 
-Row::Row()
-	: separator(0), label_hfill(0), left_margin(0), right_margin(0),
-	  sel_beg(-1), sel_end(-1),
-	  begin_margin_sel(false), end_margin_sel(false),
-	  changed_(true),
-	  pit_(0), pos_(0), end_(0),
-	  right_boundary_(false), flushed_(false), rtl_(false),
-	  changebar_(false)
-{}
-
-
 bool Row::isMarginSelected(bool left, DocIterator const & beg,
 		DocIterator const & end) const
 {
@@ -262,6 +252,9 @@ ostream & operator<<(ostream & os, Row::Element const & e)
 	case Row::SPACE:
 		os << "SPACE: ";
 		break;
+	case Row::INVALID:
+		os << "INVALID: ";
+		break;
 	}
 	os << "width=" << e.full_width();
 	return os;
@@ -443,6 +436,13 @@ void Row::addSpace(pos_type const pos, int const width,
 }
 
 
+void Row::push_back(Row::Element const & e)
+{
+	dim_.wid += e.dim.wid;
+	elements_.push_back(e);
+}
+
+
 void Row::pop_back()
 {
 	dim_.wid -= elements_.back().dim.wid;
diff --git a/src/Row.h b/src/Row.h
index b54a233..3048cf1 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -49,7 +49,9 @@ public:
 		// An inset
 		INSET,
 		// Some spacing described by its width, not a string
-		SPACE
+		SPACE,
+		// Something that should not happen (for error handling)
+		INVALID
 	};
 
 /**
@@ -57,9 +59,12 @@ public:
  * by other methods that need to parse the Row contents.
  */
 	struct Element {
+		//
+		Element() = default;
+		//
 		Element(Type const t, pos_type p, Font const & f, Change const & ch)
-			: type(t), pos(p), endpos(p + 1), inset(0),
-			  extra(0), font(f), change(ch), final(false) {}
+			: type(t), pos(p), endpos(p + 1), font(f), change(ch) {}
+
 
 		// Return the number of separator in the element (only STRING type)
 		int countSeparators() const;
@@ -86,40 +91,49 @@ public:
 		 *  adjusted to the actual pixel position.
 		*/
 		pos_type x2pos(int &x) const;
+		/** Break the element in two if possible, so that its width is less
+		 * than \param w.
+		 * \return an element containing the remainder of the text, or
+		 *   an invalid element if nothing happened.
+		 * \param w: the desired maximum width
+		 * \param force: if true, the string is cut at any place, otherwise it
+		 *   respects the row breaking rules of characters.
+		 */
+		Element splitAt(int w, bool force);
 		/** 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
+		 * is true, the string is cut at any place, otherwise it
 		 * respects the row breaking rules of characters.
 		 */
 		bool breakAt(int w, bool force);
 
-		// Returns the position on left side of the element.
-		pos_type left_pos() const;
-		// Returns the position on right side of the element.
-		pos_type right_pos() const;
-
 		//
 		bool isRTL() const { return font.isVisibleRightToLeft(); }
 		// This is true for virtual elements.
-		// Note that we do not use the type here. The two definitions
-		// should be equivalent
-		bool isVirtual() const { return pos == endpos; }
+		bool isVirtual() const { return type == VIRTUAL; }
+		// Invalid element, for error handling
+		bool isValid() const { return type !=INVALID; }
+
+		// Returns the position on left side of the element.
+		pos_type left_pos() const { return isRTL() ? endpos : pos; };
+		// Returns the position on right side of the element.
+		pos_type right_pos() const { return isRTL() ? pos : endpos; };
 
 		// The kind of row element
-		Type type;
+		Type type = INVALID;
 		// position of the element in the paragraph
-		pos_type pos;
+		pos_type pos = 0;
 		// first position after the element in the paragraph
-		pos_type endpos;
+		pos_type endpos = 0;
 		// The dimension of the chunk (does not contains the
 		// separator correction)
 		Dimension dim;
 
 		// Non-zero only if element is an inset
-		Inset const * inset;
+		Inset const * inset = nullptr;
 
 		// Only non-null for justified rows
-		double extra;
+		double extra = 0;
 
 		// Non-empty if element is a string or is virtual
 		docstring str;
@@ -128,14 +142,15 @@ public:
 		//
 		Change change;
 		// is it possible to add contents to this element?
-		bool final;
+		bool final = false;
 
 		friend std::ostream & operator<<(std::ostream & os, Element const & row);
 	};
 
 
 	///
-	Row();
+	Row() {}
+
 	/**
 	 * Helper function: set variable \c var to value \c val, and mark
 	 * row as changed is the values were different. This is intended
@@ -264,7 +279,9 @@ public:
 	Element & back() { return elements_.back(); }
 	///
 	Element const & back() const { return elements_.back(); }
-	/// remove last element
+	/// add element at the end and update width
+	void push_back(Element const &);
+	/// remove last element and update width
 	void pop_back();
 	/**
 	 * if row width is too large, remove all elements after last
@@ -301,21 +318,21 @@ public:
 	friend std::ostream & operator<<(std::ostream & os, Row const & row);
 
 	/// additional width for separators in justified rows (i.e. space)
-	double separator;
+	double separator = 0;
 	/// width of hfills in the label
-	double label_hfill;
+	double label_hfill = 0;
 	/// the left margin position of the row
-	int left_margin;
+	int left_margin = 0;
 	/// the right margin of the row
-	int right_margin;
+	int right_margin = 0;
 	///
-	mutable pos_type sel_beg;
+	mutable pos_type sel_beg = -1;
 	///
-	mutable pos_type sel_end;
+	mutable pos_type sel_end = -1;
 	///
-	mutable bool begin_margin_sel;
+	mutable bool begin_margin_sel = false;
 	///
-	mutable bool end_margin_sel;
+	mutable bool end_margin_sel = false;
 
 private:
 	/// Decides whether the margin is selected.
@@ -340,28 +357,35 @@ private:
 	Elements elements_;
 
 	/// has the Row appearance changed since last drawing?
-	mutable bool changed_;
+	mutable bool changed_ = true;
 	/// Index of the paragraph that contains this row
-	pit_type pit_;
+	pit_type pit_ = 0;
 	/// first pos covered by this row
-	pos_type pos_;
+	pos_type pos_ = 0;
 	/// one behind last pos covered by this row
-	pos_type end_;
+	pos_type end_ = 0;
 	// Is there a boundary at the end of the row (display inset...)
-	bool right_boundary_;
+	bool right_boundary_ = false;
 	// Shall the row be flushed when it is supposed to be justified?
-	bool flushed_;
+	bool flushed_ = false;
 	/// Row dimension.
 	Dimension dim_;
 	/// Row contents dimension. Does not contain the space above/below row.
 	Dimension contents_dim_;
 	/// true when this row lives in a right-to-left paragraph
-	bool rtl_;
+	bool rtl_ = false;
 	/// true when a changebar should be drawn in the margin
-	bool changebar_;
+	bool changebar_ = false;
 };
 
 
+/**
+ * Each paragraph is broken up into a number of rows on the screen.
+ * This is a list of such on-screen rows, ordered from the top row
+ * downwards.
+ */
+typedef std::vector<Row> RowList;
+
 } // namespace lyx
 
 #endif
diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp
index 400b7b6..656f89a 100644
--- a/src/RowPainter.cpp
+++ b/src/RowPainter.cpp
@@ -565,6 +565,10 @@ void RowPainter::paintText()
 
 		case Row::SPACE:
 			paintTextDecoration(e);
+			break;
+
+		case Row::INVALID:
+			LYXERR0("Trying to paint INVALID row element.");
 		}
 
 		// The markings of foreign languages

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

Summary of changes:
 src/Row.h                           |    2 +-
 src/frontends/qt/GuiFontMetrics.cpp |   11 ++++++++---
 2 files changed, 9 insertions(+), 4 deletions(-)


hooks/post-receive
-- 
Repository for new features


More information about the lyx-cvs mailing list