From 71d9f6e90d52d8f85c9e8b530027a9fd59c697e8 Mon Sep 17 00:00:00 2001 From: Jean-Marc Lasgouttes Date: Mon, 25 Sep 2023 12:35:40 +0200 Subject: [PATCH] Avoid row breaking at inconvenient places. When it turns out that breaking a STRING row element was not sufficient in Row::shortenIfNeeded, we still remember the shortest width that one can obtain. Later, when we try to split a previous element of the row, we have a better idea of how much of the row remains after it. To this end, change the signature of Element::splitAt to use an enum: FIT (was: force=false), FORCE (was: force= true) and BEST_EFFORT (split at max_width, but do not return an error if the string is too large). Fixes bug #12660. --- src/Row.cpp | 25 ++++++++++++++++++------- src/Row.h | 14 ++++++++++---- src/TextMetrics.cpp | 2 +- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Row.cpp b/src/Row.cpp index a7f00d536d..e911feff11 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -123,7 +123,7 @@ pos_type Row::Element::x2pos(int &x) const } -bool Row::Element::splitAt(int const width, int next_width, bool force, +bool Row::Element::splitAt(int const width, int next_width, SplitType split_type, Row::Elements & tail) { // Not a string or already OK. @@ -142,13 +142,13 @@ bool Row::Element::splitAt(int const width, int next_width, bool force, bool const wrap_any = !font.language()->wordWrap(); FontMetrics::Breaks breaks = fm.breakString(str, width, next_width, - isRTL(), wrap_any | force); + isRTL(), wrap_any || split_type == FORCE); /** if breaking did not really work, give up - * case 1: we do not force break and the first element is longer than the limit; + * case 1: split type is FIT and the first element is longer than the limit; * case 2: the first break occurs at the front of the string */ - if ((!force && breaks.front().nspc_wid > width) + if ((split_type == FIT && breaks.front().nspc_wid > width) || (breaks.size() > 1 && breaks.front().len == 0)) { if (dim.wid == 0) dim.wid = fm.width(str); @@ -548,6 +548,8 @@ Row::Elements Row::shortenIfNeeded(int const max_width, int const next_width) Elements::iterator const beg = elements_.begin(); Elements::iterator const end = elements_.end(); int wid = left_margin; + // the smallest row width we know we can achieve by breaking a string. + int min_row_wid = dim_.wid; // Search for the first element that goes beyond right margin Elements::iterator cit = beg; @@ -600,14 +602,23 @@ Row::Elements Row::shortenIfNeeded(int const max_width, int const next_width) * - shorter than the natural width of the element, in order to enforce * break-up. */ - if (brk.splitAt(min(max_width - wid_brk, brk.dim.wid - 2), next_width, false, tail)) { + int const split_width = min(max_width - wid_brk, brk.dim.wid - 2); + if (brk.splitAt(split_width, next_width, BEST_EFFORT, tail)) { + // if we did not manage to fit a part of the element into + // the split_width limit, at least remember that we can + // shorten the row if needed. + if (brk.dim.wid > split_width) { + min_row_wid = wid_brk + brk.dim.wid; + tail.clear(); + continue; + } /* if this element originally did not cause a row overflow * in itself, and the remainder of the row would still be * too large after breaking, then we will have issues in * next row. Thus breaking does not help. */ if (wid_brk + cit_brk->dim.wid < max_width - && dim_.wid - (wid_brk + brk.dim.wid) >= next_width) { + && min_row_wid - (wid_brk + brk.dim.wid) >= next_width) { tail.clear(); break; } @@ -641,7 +652,7 @@ Row::Elements Row::shortenIfNeeded(int const max_width, int const next_width) * shorten the row. Let's try to break it again, but force * splitting this time. */ - if (cit->splitAt(max_width - wid, next_width, true, tail)) { + if (cit->splitAt(max_width - wid, next_width, FORCE, tail)) { end_ = cit->endpos; dim_.wid = wid + cit->dim.wid; // If there are other elements, they should be removed. diff --git a/src/Row.h b/src/Row.h index 795e3e4e59..babe11510f 100644 --- a/src/Row.h +++ b/src/Row.h @@ -55,6 +55,14 @@ public: // by the initial width MARGINSPACE }; + enum SplitType { + // split string to fit requested width, fail if string remains too long + FIT, + // if the requested width is too small, accept the first possible break + BEST_EFFORT, + // cut string at any place, even for languages that wrap at word delimiters + FORCE + }; /** * One element of a Row. It has a set of attributes that can be used @@ -94,14 +102,12 @@ public: * not needed or not possible. * \param width: maximum width of the row. * \param next_width: available width on next rows. - * \param force: if true, cut string at any place, even for - * languages that wrap at word delimiters; if false, do not - * break at all if first element would larger than \c width. + * \param split_type: indicate how the string should be split. * \param tail: a vector of elements where the remainder of * the text will be appended (empty if nothing happened). */ // FIXME: ideally last parameter should be Elements&, but it is not possible. - bool splitAt(int width, int next_width, bool force, std::vector & tail); + bool splitAt(int width, int next_width, SplitType split_type, std::vector & tail); // remove trailing spaces (useful for end of row) void rtrim(); diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index 51f73fd44a..eb99de066b 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -1150,7 +1150,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const // pile, or the place when we were in main row Row::Element elt = *fcit; Row::Elements tail; - elt.splitAt(width - rows.back().width(), next_width, false, tail); + elt.splitAt(width - rows.back().width(), next_width, Row::FIT, tail); Row & rb = rows.back(); if (elt.type == Row::MARGINSPACE) elt.dim.wid = max(elt.dim.wid, leftMargin(bigrow.pit()) - rb.width()); -- 2.39.5