[LyX features/breakrows] Improve row flushing
Jean-Marc Lasgouttes
lasgouttes at lyx.org
Thu Sep 23 22:06:36 UTC 2021
The branch, breakrows, has been updated.
discards 8006aa710f2bc52082538f1c37252cc98b2b5c46 (commit)
discards 68c4867ba2f1de9489b9e20e2f708ede7111701e (commit)
discards eec0ec60dd5bec229f3128f956ae937bdc441735 (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 (8006aa710f2bc52082538f1c37252cc98b2b5c46)
\
N -- N -- N (4d44f49b52fa9eedb4ee756bcac236f180c7c79c)
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 4d44f49b52fa9eedb4ee756bcac236f180c7c79c
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date: Wed Sep 22 15:09:26 2021 +0200
Improve row flushing
Add new row flags Flush and FlushBefore to let insets indicate whether
they cause flushing of current row (eg. newline) or of previous row
(e.g. display insets).
diff --git a/src/RowFlags.h b/src/RowFlags.h
index f94f0c6..01c4dd2 100644
--- a/src/RowFlags.h
+++ b/src/RowFlags.h
@@ -32,22 +32,26 @@ enum RowFlags {
BreakBefore = 1 << 0,
// Avoid breaking row before this element
NoBreakBefore = 1 << 1,
+ // flush the row before this element (useful with BreakBefore)
+ FlushBefore = 1 << 2,
// force new (maybe empty) row after this element
- AlwaysBreakAfter = 1 << 2,
+ AlwaysBreakAfter = 1 << 3,
// break row after this element if there are more elements
- BreakAfter = 1 << 3,
+ BreakAfter = 1 << 4,
// break row whenever needed after this element
- CanBreakAfter = 1 << 4,
+ CanBreakAfter = 1 << 5,
// Avoid breaking row after this element
- NoBreakAfter = 1 << 5,
+ NoBreakAfter = 1 << 6,
// The contents of the row may be broken in two (e.g. string)
- CanBreakInside = 1 << 6,
+ CanBreakInside = 1 << 7,
+ // Flush the row that ends with this element
+ Flush = 1 << 8,
// specify an alignment (left, right) for a display element
// (default is center)
- AlignLeft = 1 << 7,
- AlignRight = 1 << 8,
+ AlignLeft = 1 << 9,
+ AlignRight = 1 << 10,
// A display element breaks row at both ends
- Display = BreakBefore | BreakAfter,
+ Display = FlushBefore | BreakBefore | BreakAfter,
// Flags that concern breaking after element
AfterFlags = AlwaysBreakAfter | BreakAfter | CanBreakAfter | NoBreakAfter
};
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 9d0dabf..2e691d9 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1067,7 +1067,6 @@ void cleanupRow(Row & row, bool at_end)
}
row.endpos(row.back().endpos);
- row.flushed(at_end);
// remove trailing spaces on row break
if (!at_end)
row.back().rtrim();
@@ -1117,8 +1116,11 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
int const f2 = (fcit == end) ? (end_label ? Inline : NoBreakBefore)
: fcit->row_flags;
if (rows.empty() || needsRowBreak(f1, f2)) {
- if (!rows.empty())
+ if (!rows.empty()) {
cleanupRow(rows.back(), false);
+ // Flush row as requested by row flags
+ rows.back().flushed((f1 & Flush) || (f2 & FlushBefore));
+ }
pos_type pos = rows.empty() ? 0 : rows.back().endpos();
rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl));
// the width available for the row.
diff --git a/src/insets/InsetNewline.cpp b/src/insets/InsetNewline.cpp
index f5fcd42..ecfacf4 100644
--- a/src/insets/InsetNewline.cpp
+++ b/src/insets/InsetNewline.cpp
@@ -39,6 +39,15 @@ InsetNewline::InsetNewline() : Inset(nullptr)
{}
+int InsetNewline::rowFlags() const
+{
+ if (params_.kind == InsetNewlineParams::LINEBREAK)
+ return AlwaysBreakAfter;
+ else
+ return AlwaysBreakAfter | Flush;
+}
+
+
void InsetNewlineParams::write(ostream & os) const
{
switch (kind) {
diff --git a/src/insets/InsetNewline.h b/src/insets/InsetNewline.h
index 1ef0ae5..c85a97d 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; }
///
- int rowFlags() const override { return AlwaysBreakAfter; }
+ int rowFlags() const override;
///
static void string2params(std::string const &, InsetNewlineParams &);
///
diff --git a/src/insets/InsetSeparator.h b/src/insets/InsetSeparator.h
index 9352bdf..0c12d95 100644
--- a/src/insets/InsetSeparator.h
+++ b/src/insets/InsetSeparator.h
@@ -65,7 +65,7 @@ public:
return docstring();
}
///
- int rowFlags() const override { return BreakAfter; }
+ int rowFlags() const override { return BreakAfter | Flush; }
private:
///
InsetCode lyxCode() const override { return SEPARATOR_CODE; }
commit 708090d8e4d77a4f65c9c5e8506b7d1c0b4c0364
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date: Wed Sep 22 13:17:46 2021 +0200
Simplify setting of RTL in rows
Set RTL status at row creation, which allows to remove a parameter from
cleanupRow.
diff --git a/src/Row.cpp b/src/Row.cpp
index 6b0faa3..4b9836e 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -624,7 +624,7 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
}
-void Row::reverseRTL(bool const rtl_par)
+void Row::reverseRTL()
{
pos_type i = 0;
pos_type const end = elements_.size();
@@ -636,14 +636,13 @@ void Row::reverseRTL(bool const rtl_par)
++j;
// if the direction is not the same as the paragraph
// direction, the sequence has to be reverted.
- if (rtl != rtl_par)
+ if (rtl != rtl_)
reverse(elements_.begin() + i, elements_.begin() + j);
i = j;
}
// If the paragraph itself is RTL, reverse everything
- if (rtl_par)
+ if (rtl_)
reverse(elements_.begin(), elements_.end());
- rtl_ = rtl_par;
}
Row::const_iterator const
diff --git a/src/Row.h b/src/Row.h
index bf49eb1..2c60638 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -301,10 +301,12 @@ public:
* Find sequences of right-to-left elements and reverse them.
* This should be called once the row is completely built.
*/
- void reverseRTL(bool rtl_par);
+ void reverseRTL();
///
bool isRTL() const { return rtl_; }
///
+ void setRTL(bool rtl) { rtl_ = rtl; }
+ ///
bool needsChangeBar() const { return changebar_; }
///
void needsChangeBar(bool ncb) { changebar_ = ncb; }
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index 6bf5c32..9d0dabf 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1049,6 +1049,7 @@ Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
nrow.pos(pos);
nrow.left_margin = tm.leftMargin(pit, pos);
nrow.right_margin = tm.rightMargin(pit);
+ nrow.setRTL(is_rtl);
if (is_rtl)
swap(nrow.left_margin, nrow.right_margin);
// Remember that the row width takes into account the left_margin
@@ -1058,7 +1059,7 @@ Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
}
-void cleanupRow(Row & row, bool at_end, bool is_rtl)
+void cleanupRow(Row & row, bool at_end)
{
if (row.empty()) {
row.endpos(0);
@@ -1073,7 +1074,7 @@ void cleanupRow(Row & row, bool at_end, bool is_rtl)
// boundary exists when there was no space at the end of row
row.right_boundary(!at_end && row.back().endpos == row.endpos());
// make sure that the RTL elements are in reverse ordering
- row.reverseRTL(is_rtl);
+ row.reverseRTL();
}
@@ -1117,7 +1118,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
: fcit->row_flags;
if (rows.empty() || needsRowBreak(f1, f2)) {
if (!rows.empty())
- cleanupRow(rows.back(), false, is_rtl);
+ cleanupRow(rows.back(), false);
pos_type pos = rows.empty() ? 0 : rows.back().endpos();
rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl));
// the width available for the row.
@@ -1151,7 +1152,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
}
if (!rows.empty()) {
- cleanupRow(rows.back(), true, is_rtl);
+ cleanupRow(rows.back(), true);
// Last row in paragraph is flushed
rows.back().flushed(true);
}
commit 53cff168c421fe9bcef814d5b6f563ac550c1cb7
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date: Mon Sep 6 14:52:42 2021 +0200
Break multi-row strings in one pass
Replace FontMetrics::breakAt, which returned the next break point,
with FontMetrics::breakString, which returns a vector of break points.
To this end, an additional parameter gives the available width for
next rows.
Rename various variables and methods accordingly. Factor the code in
breakString_helper to be more manageable.
Adapt Row::Element::splitAt to return a bool on sucess and provide
remaining row elements in a vector. The width noted above has been
added as parameters.
Rename the helper function splitFrom to moveElements and rewrite the
code to be more efficient.
Remove type of row element INVALID, which is not needed anymore.
The code in TextMetrics::breakParagraph is now much simpler.
In Row::finalize, remove the code that computed inconditionnally the
current element size, and make sure that this width will be computed
in all code paths of Row::Element::splitAt.
diff --git a/src/Row.cpp b/src/Row.cpp
index b7e4c07..6b0faa3 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -123,41 +123,81 @@ 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;
}
-Row::Element Row::Element::splitAt(int w, bool force)
+bool Row::Element::splitAt(int const width, int next_width, bool force,
+ Row::Elements & tail)
{
- if (type != STRING || !(row_flags & CanBreakInside))
- return Element();
+ // Not a string or already OK.
+ if (type != STRING || (dim.wid > 0 && dim.wid < width))
+ return false;
FontMetrics const & fm = theFontMetrics(font);
- dim.wid = w;
- int const i = fm.breakAt(str, dim.wid, isRTL(), force);
- if (i != -1) {
- //Create a second row element to return
- Element ret(STRING, pos + i, font, change);
- ret.str = str.substr(i);
- ret.endpos = ret.pos + ret.str.length();
- // Copy the after flags of the original element to the second one.
- ret.row_flags = row_flags & (CanBreakInside | AfterFlags);
-
- // Now update ourselves
- str.erase(i);
- endpos = pos + i;
- // Row should be broken after the original element
- row_flags = (row_flags & ~AfterFlags) | BreakAfter;
- //LYXERR0("breakAt(" << w << ") Row element Broken at " << w << "(w(str)=" << fm.width(str) << "): e=" << *this);
- return ret;
+
+ // A a string that is not breakable
+ if (!(row_flags & CanBreakInside)) {
+ // has width been computed yet?
+ if (dim.wid == 0)
+ dim.wid = fm.width(str);
+ return false;
+ }
+
+ bool const wrap_any = !font.language()->wordWrap();
+ FontMetrics::Breaks breaks = fm.breakString(str, width, next_width,
+ isRTL(), wrap_any | force);
+
+ // if breaking did not really work, give up
+ if (!force && breaks.front().wid > width) {
+ if (dim.wid == 0)
+ dim.wid = fm.width(str);
+ return false;
+ }
+
+ Element first_e(STRING, pos, font, change);
+ // should next element eventually replace *this?
+ bool first = true;
+ docstring::size_type i = 0;
+ for (FontMetrics::Break const & brk : breaks) {
+ Element e(STRING, pos + i, font, change);
+ e.str = str.substr(i, brk.len);
+ e.endpos = e.pos + brk.len;
+ e.dim.wid = brk.wid;
+ e.row_flags = CanBreakInside | BreakAfter;
+ if (first) {
+ // this element eventually goes to *this
+ e.row_flags |= row_flags & ~AfterFlags;
+ first_e = e;
+ first = false;
+ } else
+ tail.push_back(e);
+ i += brk.len;
}
- return Element();
+ if (!tail.empty()) {
+ // Avoid having a last empty element. This happens when
+ // breaking at the trailing space of string
+ if (tail.back().str.empty())
+ tail.pop_back();
+ else {
+ // Copy the after flags of the original element to the last one.
+ tail.back().row_flags &= ~BreakAfter;
+ tail.back().row_flags |= row_flags & AfterFlags;
+ }
+ // first_e row should be broken after the original element
+ first_e.row_flags |= BreakAfter;
+ } else {
+ // Restore the after flags of the original element.
+ first_e.row_flags &= ~BreakAfter;
+ first_e.row_flags |= row_flags & AfterFlags;
+ }
+
+ // update ourselves
+ swap(first_e, *this);
+ return true;
}
@@ -265,10 +305,6 @@ ostream & operator<<(ostream & os, Row::Element const & e)
break;
case Row::SPACE:
os << "SPACE: ";
- break;
- case Row::INVALID:
- os << "INVALID: ";
- break;
}
os << "width=" << e.full_width() << ", row_flags=" << e.row_flags;
return os;
@@ -393,11 +429,6 @@ void Row::finalizeLast()
elt.final = true;
if (elt.change.changed())
changebar_ = true;
-
- if (elt.type == STRING && elt.dim.wid == 0) {
- elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
- dim_.wid += elt.dim.wid;
- }
}
@@ -474,19 +505,14 @@ void Row::pop_back()
namespace {
-// Remove stuff after \c it from \c elts, and return it.
-// if \c init is provided, it will prepended to the rest
-Row::Elements splitFrom(Row::Elements & elts, Row::Elements::iterator const & it,
- Row::Element const & init = Row::Element())
+// Move stuff after \c it from \c from and the end of \c to.
+void moveElements(Row::Elements & from, Row::Elements::iterator const & it,
+ Row::Elements & to)
{
- Row::Elements ret;
- if (init.isValid())
- ret.push_back(init);
- ret.insert(ret.end(), it, elts.end());
- elts.erase(it, elts.end());
- if (!elts.empty())
- elts.back().row_flags = (elts.back().row_flags & ~AfterFlags) | BreakAfter;
- return ret;
+ to.insert(to.end(), it, from.end());
+ from.erase(it, from.end());
+ if (!from.empty())
+ from.back().row_flags = (from.back().row_flags & ~AfterFlags) | BreakAfter;
}
}
@@ -522,6 +548,7 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
Elements::iterator cit_brk = cit;
int wid_brk = wid + cit_brk->dim.wid;
++cit_brk;
+ Elements tail;
while (cit_brk != beg) {
--cit_brk;
// make a copy of the element to work on it.
@@ -533,28 +560,18 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
if (wid_brk <= w && brk.row_flags & CanBreakAfter) {
end_ = brk.endpos;
dim_.wid = wid_brk;
- return splitFrom(elements_, cit_brk + 1);
+ moveElements(elements_, cit_brk + 1, tail);
+ return tail;
}
// assume now that the current element is not there
wid_brk -= brk.dim.wid;
- /*
- * Some Asian languages split lines anywhere (no notion of
- * word). It seems that QTextLayout is not aware of this fact.
- * See for reference:
- * https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages
- *
- * FIXME: Something shall be done about characters which are
- * not allowed at the beginning or end of line.
- */
- bool const word_wrap = brk.font.language()->wordWrap();
/* 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
+ * Try to break it cleanly at a length that is both
* - less than the available space on the row
* - shorter than the natural width of the element, in order to enforce
* break-up.
*/
- Element remainder = brk.splitAt(min(w - wid_brk, brk.dim.wid - 2), !word_wrap);
- if (brk.row_flags & BreakAfter) {
+ if (brk.splitAt(min(w - wid_brk, brk.dim.wid - 2), next_width, false, tail)) {
/* 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
@@ -568,12 +585,10 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
*cit_brk = brk;
dim_.wid = wid_brk + brk.dim.wid;
// If there are other elements, they should be removed.
- // remainder can be empty when splitting at trailing space
- if (remainder.str.empty())
- return splitFrom(elements_, next(cit_brk, 1));
- else
- return splitFrom(elements_, next(cit_brk, 1), remainder);
+ moveElements(elements_, cit_brk + 1, tail);
+ return tail;
}
+ LATTEST(tail.empty());
}
if (cit != beg && cit->row_flags & NoBreakBefore) {
@@ -588,20 +603,23 @@ Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
// been added. We can cut right here.
end_ = cit->pos;
dim_.wid = wid;
- return splitFrom(elements_, cit);
+ moveElements(elements_, cit, tail);
+ return tail;
}
/* 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.
+ * shorten the row. Let's try to break it again, but force
+ * splitting this time.
*/
- Element remainder = cit->splitAt(w - wid, true);
- if (cit->row_flags & BreakAfter) {
+ if (cit->splitAt(w - wid, next_width, true, tail)) {
+ LYXERR0(*cit);
end_ = cit->endpos;
dim_.wid = wid + cit->dim.wid;
// If there are other elements, they should be removed.
- return splitFrom(elements_, next(cit, 1), remainder);
+ moveElements(elements_, cit + 1, tail);
+ return tail;
}
+
return Elements();
}
diff --git a/src/Row.h b/src/Row.h
index 1272a0f..bf49eb1 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -50,9 +50,7 @@ public:
// An inset
INSET,
// Some spacing described by its width, not a string
- SPACE,
- // Something that should not happen (for error handling)
- INVALID
+ SPACE
};
/**
@@ -61,8 +59,6 @@ public:
*/
struct Element {
//
- Element() = default;
- //
Element(Type const t, pos_type p, Font const & f, Change const & ch)
: type(t), pos(p), endpos(p + 1), font(f), change(ch) {}
@@ -94,13 +90,16 @@ public:
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.
+ * \return a vector of elements containing the remainder of
+ * the text (empty if nothing happened).
+ * \param width maximum width of the row.
+ * \param next_width available width on next row.
+ * \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.
*/
- Element splitAt(int w, bool force);
+ // FIXME: ideally last parameter should be Elements&, but it is not possible.
+ bool splitAt(int width, int next_width, bool force, std::vector<Element> & tail);
// remove trailing spaces (useful for end of row)
void rtrim();
@@ -108,8 +107,6 @@ public:
bool isRTL() const { return font.isVisibleRightToLeft(); }
// This is true for virtual elements.
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; };
@@ -117,11 +114,11 @@ public:
pos_type right_pos() const { return isRTL() ? pos : endpos; };
// The kind of row element
- Type type = INVALID;
+ Type type;
// position of the element in the paragraph
- pos_type pos = 0;
+ pos_type pos;
// first position after the element in the paragraph
- pos_type endpos = 0;
+ pos_type endpos;
// The dimension of the chunk (does not contains the
// separator correction)
Dimension dim;
@@ -289,8 +286,8 @@ public:
* separator and update endpos if necessary. If all that
* remains is a large word, cut it to \param width.
* \param width maximum width of the row.
- * \param available width on next row.
- * \return true if the row has been shortened.
+ * \param next_width available width on next row.
+ * \return list of elements remaining after breaking.
*/
Elements shortenIfNeeded(int const width, int const next_width);
diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp
index 656f89a..400b7b6 100644
--- a/src/RowPainter.cpp
+++ b/src/RowPainter.cpp
@@ -565,10 +565,6 @@ void RowPainter::paintText()
case Row::SPACE:
paintTextDecoration(e);
- break;
-
- case Row::INVALID:
- LYXERR0("Trying to paint INVALID row element.");
}
// The markings of foreign languages
diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp
index f055074..6bf5c32 100644
--- a/src/TextMetrics.cpp
+++ b/src/TextMetrics.cpp
@@ -1058,7 +1058,7 @@ Row newRow(TextMetrics const & tm, pit_type pit, pos_type pos, bool is_rtl)
}
-void cleanupRow(Row & row, pos_type real_endpos, bool is_rtl)
+void cleanupRow(Row & row, bool at_end, bool is_rtl)
{
if (row.empty()) {
row.endpos(0);
@@ -1066,11 +1066,12 @@ void cleanupRow(Row & row, pos_type real_endpos, bool is_rtl)
}
row.endpos(row.back().endpos);
+ row.flushed(at_end);
// remove trailing spaces on row break
- if (row.endpos() < real_endpos)
+ if (!at_end)
row.back().rtrim();
// boundary exists when there was no space at the end of row
- row.right_boundary(row.endpos() < real_endpos && row.back().endpos == row.endpos());
+ row.right_boundary(!at_end && row.back().endpos == row.endpos());
// make sure that the RTL elements are in reverse ordering
row.reverseRTL(is_rtl);
}
@@ -1097,6 +1098,8 @@ 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;
+ int const next_width = max_width_ - leftMargin(bigrow.pit(), bigrow.endpos())
+ - rightMargin(bigrow.pit());
int width = 0;
flexible_const_iterator<Row> fcit = flexible_begin(bigrow);
@@ -1114,7 +1117,7 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
: fcit->row_flags;
if (rows.empty() || needsRowBreak(f1, f2)) {
if (!rows.empty())
- cleanupRow(rows.back(), bigrow.endpos(), is_rtl);
+ cleanupRow(rows.back(), false, is_rtl);
pos_type pos = rows.empty() ? 0 : rows.back().endpos();
rows.push_back(newRow(*this, bigrow.pit(), pos, is_rtl));
// the width available for the row.
@@ -1129,45 +1132,26 @@ RowList TextMetrics::breakParagraph(Row const & bigrow) const
// Next element to consider is either the top of the temporary
// pile, or the place when we were in main row
Row::Element elt = *fcit;
- Row::Element next_elt = elt.splitAt(width - rows.back().width(),
- !elt.font.language()->wordWrap());
- 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);
- }
- } else {
- // a new element in the row
- rows.back().push_back(elt);
- rows.back().finalizeLast();
-
- // 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
- if (!next_elt.str.empty())
- fcit.put(next_elt);
- }
+ Row::Elements tail;
+ elt.splitAt(width - rows.back().width(), next_width, false, tail);
+ Row & rb = rows.back();
+ rb.push_back(elt);
+ rb.finalizeLast();
+ if (rb.width() > width) {
+ LATTEST(tail.empty());
+ // if the row is too large, try to cut at last separator.
+ tail = rb.shortenIfNeeded(width, next_width);
}
+
+ // Go to next element
+ ++fcit;
+
+ // Handle later the elements returned by splitAt or shortenIfNeeded.
+ fcit.put(tail);
}
if (!rows.empty()) {
- cleanupRow(rows.back(), bigrow.endpos(), is_rtl);
+ cleanupRow(rows.back(), true, is_rtl);
// Last row in paragraph is flushed
rows.back().flushed(true);
}
diff --git a/src/frontends/FontMetrics.h b/src/frontends/FontMetrics.h
index b562ebf..b78bc2d 100644
--- a/src/frontends/FontMetrics.h
+++ b/src/frontends/FontMetrics.h
@@ -16,6 +16,8 @@
#include "support/strfwd.h"
+#include <vector>
+
/**
* A class holding helper functions for determining
* the screen dimensions of fonts.
@@ -121,15 +123,27 @@ public:
* \param ws is the amount of extra inter-word space applied text justification.
*/
virtual int x2pos(docstring const & s, int & x, bool rtl, double ws) const = 0;
+
+ // The places where to break a string and the width of the resulting lines.
+ struct Break {
+ Break(int l, int w) : len(l), wid(w) {}
+ int len = 0;
+ int wid = 0;
+ };
+ typedef std::vector<Break> Breaks;
/**
- * Break string s at width at most x.
- * \return break position (-1 if not successful)
- * \param position x is updated to real width
- * \param rtl is true for right-to-left layout
+ * Break a string in multiple fragments according to width limits.
+ * \return a sequence of Break elements.
+ * \param s is the string to break.
+ * \param first_wid is the available width for first line.
+ * \param wid is the available width for the next lines.
+ * \param rtl is true for right-to-left layout.
* \param force is false for breaking at word separator, true for
* arbitrary position.
*/
- virtual int breakAt(docstring const & s, int & x, bool rtl, bool force) const = 0;
+ virtual Breaks
+ breakString(docstring const & s, int first_wid, int wid, bool rtl, bool force) const = 0;
+
/// return char dimension for the font.
virtual Dimension const dimension(char_type c) const = 0;
/**
diff --git a/src/frontends/qt/GuiFontMetrics.cpp b/src/frontends/qt/GuiFontMetrics.cpp
index 83c2c0a..9a5a3cf 100644
--- a/src/frontends/qt/GuiFontMetrics.cpp
+++ b/src/frontends/qt/GuiFontMetrics.cpp
@@ -19,6 +19,7 @@
#include "support/convert.h"
#include "support/lassert.h"
+#include "support/lstrings.h"
#include "support/lyxlib.h"
#include "support/debug.h"
@@ -77,14 +78,14 @@ namespace frontend {
/*
- * Limit (strwidth|breakat)_cache_ size to 512kB of string data.
+ * Limit (strwidth|breakstr)_cache_ size to 512kB of string data.
* Limit qtextlayout_cache_ size to 500 elements (we do not know the
* size of the QTextLayout objects anyway).
* Note that all these numbers are arbitrary.
* Also, setting size to 0 is tantamount to disabling the cache.
*/
int cache_metrics_width_size = 1 << 19;
-int cache_metrics_breakat_size = 1 << 19;
+int cache_metrics_breakstr_size = 1 << 19;
// Qt 5.x already has its own caching of QTextLayout objects
// but it does not seem to work well on MacOS X.
#if (QT_VERSION < 0x050000) || defined(Q_OS_MAC)
@@ -119,7 +120,7 @@ inline QChar const ucs4_to_qchar(char_type const ucs4)
GuiFontMetrics::GuiFontMetrics(QFont const & font)
: font_(font), metrics_(font, 0),
strwidth_cache_(cache_metrics_width_size),
- breakat_cache_(cache_metrics_breakat_size),
+ breakstr_cache_(cache_metrics_breakstr_size),
qtextlayout_cache_(cache_metrics_qtextlayout_size)
{
// Determine italic slope
@@ -471,11 +472,13 @@ int GuiFontMetrics::countExpanders(docstring const & str) const
}
-pair<int, int>
-GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
- bool const rtl, bool const force) const
+namespace {
+
+const int brkStrOffset = 1 + BIDI_OFFSET;
+
+
+QString createBreakableString(docstring const & s, bool rtl, QTextLayout & tl)
{
- QTextLayout tl;
/* Qt will not break at a leading or trailing space, and we need
* that sometimes, see http://www.lyx.org/trac/ticket/9921.
*
@@ -504,34 +507,23 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
// Left-to-right override: forces to draw text left-to-right
qs = QChar(0x202D) + qs;
#endif
- int const offset = 1 + BIDI_OFFSET;
+ return qs;
+}
- tl.setText(qs);
- tl.setFont(font_);
- QTextOption to;
- to.setWrapMode(force ? QTextOption::WrapAtWordBoundaryOrAnywhere
- : QTextOption::WordWrap);
- // Let QTextLine::naturalTextWidth() account for trailing spaces
- // (horizontalAdvance() still does not).
- to.setFlags(QTextOption::IncludeTrailingSpaces);
- tl.setTextOption(to);
- tl.beginLayout();
- QTextLine line = tl.createLine();
- line.setLineWidth(x);
- tl.createLine();
- tl.endLayout();
- int line_wid = iround(line.horizontalAdvance());
- if ((force && line.textLength() == offset) || line_wid > x)
- return {-1, line_wid};
+
+docstring::size_type brkstr2str_pos(QString brkstr, docstring const & str, int pos)
+{
/* Since QString is UTF-16 and docstring is UCS-4, the offsets may
* not be the same when there are high-plan unicode characters
* (bug #10443).
*/
- // The variable `offset' is here to account for the extra leading characters.
+ // The variable `brkStrOffset' is here to account for the extra leading characters.
// The ending character zerow_nbsp has to be ignored if the line is complete.
- int const qlen = line.textLength() - offset - (line.textLength() == qs.length());
+ int const qlen = pos - brkStrOffset - (pos == brkstr.length());
#if QT_VERSION < 0x040801 || QT_VERSION >= 0x050100
- int len = qstring_to_ucs4(qs.mid(offset, qlen)).length();
+ auto const len = qstring_to_ucs4(brkstr.mid(brkStrOffset, qlen)).length();
+ // Avoid warning
+ (void)str;
#else
/* Due to QTBUG-25536 in 4.8.1 <= Qt < 5.1.0, the string returned
* by QString::toUcs4 (used by qstring_to_ucs4) may have wrong
@@ -541,47 +533,103 @@ GuiFontMetrics::breakAt_helper(docstring const & s, int const x,
* worthwhile to implement a dichotomy search if this shows up
* under a profiler.
*/
- int len = min(qlen, static_cast<int>(s.length()));
- while (len >= 0 && toqstr(s.substr(0, len)).length() != qlen)
+ int len = min(qlen, static_cast<int>(str.length()));
+ while (len >= 0 && toqstr(str.substr(0, len)).length() != qlen)
--len;
LASSERT(len > 0 || qlen == 0, /**/);
#endif
- // Do not cut is the string is already short enough. We rely on
- // naturalTextWidth() to catch the case where we cut at the trailing
- // space.
- if (len == static_cast<int>(s.length())
- && line.naturalTextWidth() <= x) {
- len = -1;
-#if QT_VERSION < 0x050000
+ return len;
+}
+
+}
+
+FontMetrics::Breaks
+GuiFontMetrics::breakString_helper(docstring const & s, int first_wid, int wid,
+ bool rtl, bool force) const
+{
+ QTextLayout tl;
+ QString qs = createBreakableString(s, rtl, tl);
+ tl.setText(qs);
+ tl.setFont(font_);
+ QTextOption to;
+ /*
+ * Some Asian languages split lines anywhere (no notion of
+ * word). It seems that QTextLayout is not aware of this fact.
+ * See for reference:
+ * https://en.wikipedia.org/wiki/Line_breaking_rules_in_East_Asian_languages
+ *
+ * FIXME: Something shall be done about characters which are
+ * not allowed at the beginning or end of line.
+ */
+ to.setWrapMode(force ? QTextOption::WrapAtWordBoundaryOrAnywhere
+ : QTextOption::WordWrap);
+ // Let QTextLine::naturalTextWidth() account for trailing spaces
+ // (horizontalAdvance() still does not).
+ to.setFlags(QTextOption::IncludeTrailingSpaces);
+ tl.setTextOption(to);
+
+ bool first = true;
+ tl.beginLayout();
+ while(true) {
+ QTextLine line = tl.createLine();
+ if (!line.isValid())
+ break;
+ line.setLineWidth(first ? first_wid : wid);
+ tl.createLine();
+ first = false;
+ }
+ tl.endLayout();
+
+ Breaks breaks;
+ int pos = 0;
+ for (int i = 0 ; i < tl.lineCount() ; ++i) {
+ QTextLine const & line = tl.lineAt(i);
+ int const epos = brkstr2str_pos(qs, s, line.textStart() + line.textLength());
+#if QT_VERSION >= 0x050000
+ int const wid = i + 1 < tl.lineCount() ? iround(line.horizontalAdvance())
+ : iround(line.naturalTextWidth());
+#else
// With some monospace fonts, the value of horizontalAdvance()
// can be wrong with Qt4. One hypothesis is that the invisible
// characters that we use are given a non-null width.
- line_wid = width(s);
+ // FIXME: this is slower than it could be but we'll get rid of Qt4 anyway
+ int const wid = i + 1 < tl.lineCount() ? width(rtrim(s.substr(pos, epos - pos)))
+ : width(s.substr(pos, epos - pos));
+#endif
+ breaks.emplace_back(epos - pos, wid);
+ pos = epos;
+#if 0
+ // FIXME: should it be kept in some form?
+ if ((force && line.textLength() == brkStrOffset) || line_wid > x)
+ return {-1, line_wid};
#endif
+
}
- return {len, line_wid};
+
+ return breaks;
}
-int GuiFontMetrics::breakAt(docstring const & s, int & x, bool const rtl, bool const force) const
+FontMetrics::Breaks GuiFontMetrics::breakString(docstring const & s, int first_wid, int wid,
+ bool rtl, bool force) const
{
- PROFILE_THIS_BLOCK(breakAt);
+ PROFILE_THIS_BLOCK(breakString);
if (s.empty())
- return false;
+ return Breaks();
docstring const s_cache =
- s + convert<docstring>(x) + (rtl ? "r" : "l") + (force ? "f" : "w");
- pair<int, int> pp;
+ s + ":" + convert<docstring>(first_wid) + ":" + convert<docstring>(wid)
+ + (rtl ? "r" : "l") + (force ? "f" : "w");
+ Breaks brks;
- if (breakat_cache_.contains(s_cache))
- pp = breakat_cache_[s_cache];
+ if (breakstr_cache_.contains(s_cache))
+ brks = breakstr_cache_[s_cache];
else {
- PROFILE_CACHE_MISS(breakAt);
- pp = breakAt_helper(s, x, rtl, force);
- breakat_cache_.insert(s_cache, pp, s_cache.size() * sizeof(char_type));
+ PROFILE_CACHE_MISS(breakString);
+ brks = breakString_helper(s, first_wid, wid, rtl, force);
+ breakstr_cache_.insert(s_cache, brks, s_cache.size() * sizeof(char_type));
}
- x = pp.second;
- return pp.first;
+ return brks;
}
diff --git a/src/frontends/qt/GuiFontMetrics.h b/src/frontends/qt/GuiFontMetrics.h
index 70bb7f6..6999df6 100644
--- a/src/frontends/qt/GuiFontMetrics.h
+++ b/src/frontends/qt/GuiFontMetrics.h
@@ -53,7 +53,7 @@ public:
int signedWidth(docstring const & s) const override;
int pos2x(docstring const & s, int pos, bool rtl, double ws) const override;
int x2pos(docstring const & s, int & x, bool rtl, double ws) const override;
- int breakAt(docstring const & s, int & x, bool rtl, bool force) const override;
+ Breaks breakString(docstring const & s, int first_wid, int wid, bool rtl, bool force) const override;
Dimension const dimension(char_type c) const override;
void rectText(docstring const & str,
@@ -77,8 +77,8 @@ public:
private:
- std::pair<int, int> breakAt_helper(docstring const & s, int const x,
- bool const rtl, bool const force) const;
+ Breaks breakString_helper(docstring const & s, int first_wid, int wid,
+ bool rtl, bool force) const;
/// The font
QFont font_;
@@ -93,8 +93,8 @@ private:
mutable QHash<char_type, int> width_cache_;
/// Cache of string widths
mutable Cache<docstring, int> strwidth_cache_;
- /// Cache for breakAt
- mutable Cache<docstring, std::pair<int, int>> breakat_cache_;
+ /// Cache for breakString
+ mutable Cache<docstring, Breaks> breakstr_cache_;
/// Cache for QTextLayout
mutable Cache<docstring, std::shared_ptr<QTextLayout>> qtextlayout_cache_;
commit 85b1a324afc682686983e4792d2daee4184f7143
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date: Mon Sep 20 17:32:18 2021 +0200
Add operator<< for Row::Elements
This is useful for debugging.
diff --git a/src/Row.cpp b/src/Row.cpp
index 8233a96..b7e4c07 100644
--- a/src/Row.cpp
+++ b/src/Row.cpp
@@ -275,6 +275,17 @@ ostream & operator<<(ostream & os, Row::Element const & e)
}
+ostream & operator<<(ostream & os, Row::Elements const & elts)
+{
+ double x = 0;
+ for (Row::Element const & e : elts) {
+ os << "x=" << x << " => " << e << endl;
+ x += e.full_width();
+ }
+ return os;
+}
+
+
ostream & operator<<(ostream & os, Row const & row)
{
os << " pos: " << row.pos_ << " end: " << row.end_
@@ -286,11 +297,11 @@ ostream & operator<<(ostream & os, Row const & row)
<< " separator: " << row.separator
<< " label_hfill: " << row.label_hfill
<< " row_boundary: " << row.right_boundary() << "\n";
+ // We cannot use the operator above, unfortunately
double x = row.left_margin;
- Row::Elements::const_iterator it = row.elements_.begin();
- for ( ; it != row.elements_.end() ; ++it) {
- os << "x=" << x << " => " << *it << endl;
- x += it->full_width();
+ for (Row::Element const & e : row.elements_) {
+ os << "x=" << x << " => " << e << endl;
+ x += e.full_width();
}
return os;
}
diff --git a/src/Row.h b/src/Row.h
index 7466797..1272a0f 100644
--- a/src/Row.h
+++ b/src/Row.h
@@ -378,6 +378,8 @@ private:
bool changebar_ = false;
};
+std::ostream & operator<<(std::ostream & os, Row::Elements const & elts);
+
/**
* Each paragraph is broken up into a number of rows on the screen.
-----------------------------------------------------------------------
Summary of changes:
src/Row.cpp | 43 +++++++++++++---------------------
src/Row.h | 16 ++++++------
src/RowFlags.h | 20 +++++++++------
src/TextMetrics.cpp | 19 +++++++++------
src/frontends/qt/GuiFontMetrics.cpp | 9 +++++++
src/insets/InsetNewline.cpp | 9 +++++++
src/insets/InsetNewline.h | 2 +-
src/insets/InsetSeparator.h | 2 +-
8 files changed, 68 insertions(+), 52 deletions(-)
hooks/post-receive
--
Repository for new features
More information about the lyx-cvs
mailing list