[LyX features/breakrows] Last step: use sortenIfNeeded again.

Jean-Marc Lasgouttes lasgouttes at lyx.org
Thu Aug 26 15:45:16 UTC 2021


The branch, breakrows, has been updated.
  discards  4729043d2d788be30c2ce289293720e1137c58c2 (commit)
  discards  1302da4b19dfa4750e57b4c46f40452ec7cadb38 (commit)
  discards  7f670555c83c33e76b0fe32c962d31d6a3597f7a (commit)
  discards  3583dc8c61da313958a566acb9ee1be83a6d1121 (commit)
  discards  3d8cc4245fb6a0eeb16c5e48d35b121fb140db33 (commit)
  discards  aade1116c1217086e4cb26bd3de42b61e8723c02 (commit)
  discards  9a590a4f581574c0019788f631596eb6cc5f2f27 (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 (4729043d2d788be30c2ce289293720e1137c58c2)
            \
             N -- N -- N (421adf6f4d46854eab042f1a06cf0fd48a70af5a)

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 421adf6f4d46854eab042f1a06cf0fd48a70af5a
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 be0f5df..8f0ccad 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 5553b62f2ee8c769ca00439393686bebcd5f01d3
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 d6b6533b971afc6918f1685d8d44c73b6ed1c009
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 13617d062146289273f3ef6f6d7b5c029a1cfe66
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 a99c1cc..be0f5df 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 129956fcdb3cda44b035044aa6eb3102f912fa02
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 4c5aec9189e7365140906a2f81f4226af7707609
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/Row.h b/src/Row.h
index 06f1836..a99c1cc 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -61,6 +61,8 @@ public:
  */
 	struct Element {
 		//
+		Element() {}
+		//
 		Element(Type const t, pos_type p, Font const & f, Change const & ch)
 			: type(t), pos(p), endpos(p + 1), font(f), change(ch) {}
 
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) {

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

Summary of changes:


hooks/post-receive
-- 
Repository for new features


More information about the lyx-cvs mailing list