From 0961eb45355e2efd9f2c6597bab09903334c6b9a Mon Sep 17 00:00:00 2001 From: Jean-Marc Lasgouttes Date: Tue, 20 Jul 2021 00:07:13 +0200 Subject: [PATCH] Last step of transition: 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 Row::breakAt and the old breakRow. Only bugs remain now :) --- src/Row.cpp | 61 +++++++----- src/Row.h | 8 +- src/TextMetrics.cpp | 233 ++++++++------------------------------------ src/TextMetrics.h | 7 +- 4 files changed, 80 insertions(+), 229 deletions(-) diff --git a/src/Row.cpp b/src/Row.cpp index 3bc8ef0d51..6e975a5a65 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -156,12 +156,6 @@ Row::Element Row::Element::splitAt(int w, bool force) } -bool Row::Element::breakAt(int w, bool force) -{ - return splitAt(w, force).isValid(); -} - - bool Row::isMarginSelected(bool left, DocIterator const & beg, DocIterator const & end) const { @@ -448,10 +442,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 +482,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 +501,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 +521,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 +544,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 +560,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 72d7a860b7..b0b17556f4 100644 --- a/src/Row.h +++ b/src/Row.h @@ -101,12 +101,6 @@ public: * 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, otherwise it - * respects the row breaking rules of characters. - */ - bool breakAt(int w, bool force); // bool isRTL() const { return font.isVisibleRightToLeft(); } @@ -296,7 +290,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 1a6927ab98..c7e84f614a 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 (e.pos + 1 == par.size() && (lyxrc.paragraph_markers || par.lookupChange(par.size()).changed()) @@ -1006,6 +1006,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 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_; @@ -1121,19 +1126,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 +1172,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 3ff11617da..e38ba7142c 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) -- 2.39.5