Merge branch 'breakrows'

With this merge, the way paragraphs are typeset changes. Paragraphs
are first tolenized as row elements, and these elements are then
broken into separate rows as needed to fit the margins. This allows to
reduce the amount of metrics computation and make LyX much faster in
the case of large insets.

Moreover, the code relies more on RofFlags enum, which desribes how an
inset or a row element should be typset. This aspect will be extended
in the future.

Some user for whom performance is very bad (which I cannot reproduce
unfortunately) have reported a 4-fold speedup. In general cases, the
speedup will be less impressive but still noticeable.

Related to bugs #12297 and #5861.
This commit is contained in:
Jean-Marc Lasgouttes 2021-12-07 17:06:21 +01:00
commit bf9b4a0836
35 changed files with 719 additions and 421 deletions

View File

@ -11,7 +11,7 @@ Please keep this file up to date as the code evolves!!!
Abbreviations:
bv: BufferView
pm: ParagraphMetrics
tm::TextMetrics
tm: TextMetrics
* Questions / Ideas
@ -76,11 +76,6 @@ a lot the amount of stuff to redraw.
It should not be necessary to access the Paragraph object to draw.
Adding the static elements to Row is a lot of work, but worth it IMO.
** Create a unique row by paragraph and break it afterwards
This should be a performance gain (only if paragraph breaking still
shows as expensive after the rest is done)
** do not add the vertical margin of main text to first/last row
Would make code cleaner. Probably no so difficult.

View File

@ -273,6 +273,7 @@ HEADERFILESCORE = \
ParIterator.h \
PDFOptions.h \
Row.h \
RowFlags.h \
RowPainter.h \
Server.h \
ServerSocket.h \

View File

@ -20,17 +20,8 @@
#include "Dimension.h"
#include "Row.h"
#include <vector>
namespace lyx {
/**
* Each paragraph is broken up into a number of rows on the screen.
* This is a list of such on-screen rows, ordered from the top row
* downwards.
*/
typedef std::vector<Row> RowList;
class BufferView;
class Paragraph;

View File

@ -35,7 +35,6 @@ using namespace std;
namespace lyx {
using support::rtrim;
using frontend::FontMetrics;
@ -130,47 +129,92 @@ pos_type Row::Element::x2pos(int &x) const
}
bool Row::Element::breakAt(int w, bool force)
bool Row::Element::splitAt(int const width, int next_width, bool force,
Row::Elements & tail)
{
if (type != STRING || dim.wid <= w)
// Not a string or already OK.
if (type != STRING || (dim.wid > 0 && dim.wid < width))
return false;
FontMetrics const & fm = theFontMetrics(font);
int x = w;
if(fm.breakAt(str, x, isRTL(), force)) {
dim.wid = x;
endpos = pos + str.length();
//lyxerr << "breakAt(" << w << ") Row element Broken at " << x << "(w(str)=" << fm.width(str) << "): e=" << *this << endl;
return true;
// 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;
}
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;
}
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;
}
pos_type Row::Element::left_pos() const
void Row::Element::rtrim()
{
return isRTL() ? endpos : pos;
if (type != STRING)
return;
/* This is intended for strings that have been created by splitAt.
* They may have trailing spaces, but they are not counted in the
* string length (QTextLayout feature, actually). We remove them,
* and decrease endpos, since spaces at row break are invisible.
*/
str = support::rtrim(str);
endpos = pos + str.length();
}
pos_type Row::Element::right_pos() const
{
return isRTL() ? pos : endpos;
}
Row::Row()
: separator(0), label_hfill(0), left_margin(0), right_margin(0),
sel_beg(-1), sel_end(-1),
begin_margin_sel(false), end_margin_sel(false),
changed_(true),
pit_(0), pos_(0), end_(0),
right_boundary_(false), flushed_(false), rtl_(false),
changebar_(false)
{}
bool Row::isMarginSelected(bool left, DocIterator const & beg,
DocIterator const & end) const
{
@ -261,9 +305,19 @@ ostream & operator<<(ostream & os, Row::Element const & e)
break;
case Row::SPACE:
os << "SPACE: ";
break;
}
os << "width=" << e.full_width();
os << "width=" << e.full_width() << ", row_flags=" << e.row_flags;
return os;
}
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;
}
@ -279,11 +333,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;
}
@ -375,22 +429,17 @@ void Row::finalizeLast()
elt.final = true;
if (elt.change.changed())
changebar_ = true;
if (elt.type == STRING) {
dim_.wid -= elt.dim.wid;
elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
dim_.wid += elt.dim.wid;
}
}
void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
Font const & f, Change const & ch)
Font const & f, Change const & ch)
{
finalizeLast();
Element e(INSET, pos, f, ch);
e.inset = ins;
e.dim = dim;
e.row_flags = ins->rowFlags();
elements_.push_back(e);
dim_.wid += dim.wid;
changebar_ |= ins->isChanged();
@ -398,23 +447,16 @@ void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
void Row::add(pos_type const pos, char_type const c,
Font const & f, Change const & ch)
Font const & f, Change const & ch, bool can_break)
{
if (!sameString(f, ch)) {
finalizeLast();
Element e(STRING, pos, f, ch);
e.row_flags = can_break ? CanBreakInside : Inline;
elements_.push_back(e);
}
if (back().str.length() % 30 == 0) {
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;
}
@ -427,6 +469,10 @@ void Row::addVirtual(pos_type const pos, docstring const & s,
e.dim.wid = theFontMetrics(f).width(s);
dim_.wid += e.dim.wid;
e.endpos = pos;
// Copy after* flags from previous elements, forbid break before element
int const prev_row_flags = elements_.empty() ? Inline : elements_.back().row_flags;
int const can_inherit = AfterFlags & ~AlwaysBreakAfter;
e.row_flags = (prev_row_flags & can_inherit) | NoBreakBefore;
elements_.push_back(e);
finalizeLast();
}
@ -443,6 +489,13 @@ void Row::addSpace(pos_type const pos, int const width,
}
void Row::push_back(Row::Element const & e)
{
dim_.wid += e.dim.wid;
elements_.push_back(e);
}
void Row::pop_back()
{
dim_.wid -= elements_.back().dim.wid;
@ -450,10 +503,28 @@ void Row::pop_back()
}
bool Row::shortenIfNeeded(pos_type const keep, int const w, int const next_width)
namespace {
// 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)
{
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;
}
}
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();
@ -469,52 +540,38 @@ bool Row::shortenIfNeeded(pos_type const keep, 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
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.
Element brk = *cit_brk;
/* If the current element is an inset that allows breaking row
* after itself, and it the row is already short enough after
* after itself, and if the row is already short enough after
* this inset, then cut right after this element.
*/
if (wid_brk <= w && brk.type == INSET
&& brk.inset->rowFlags() & Inset::CanBreakAfter) {
if (wid_brk <= w && brk.row_flags & CanBreakAfter) {
end_ = brk.endpos;
dim_.wid = wid_brk;
elements_.erase(cit_brk + 1, end);
return true;
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();
// When there is text before the body part (think description
// environment), do not try to break.
if (brk.pos < keep)
continue;
/* We have found a suitable separable element. This is the common case.
* Try to break it cleanly (at word boundary) at a length that is both
* 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.
*/
if (brk.breakAt(min(w - wid_brk, brk.dim.wid - 2), !word_wrap)) {
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
@ -525,25 +582,18 @@ bool Row::shortenIfNeeded(pos_type const keep, int const w, int const next_width
break;
}
end_ = brk.endpos;
/* after breakAt, there may be spaces at the end of the
* string, but they are not counted in the string length
* (QTextLayout feature, actually). We remove them, but do
* not change the end of the row, since spaces at row
* break are invisible.
*/
brk.str = rtrim(brk.str);
brk.endpos = brk.pos + brk.str.length();
*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;
moveElements(elements_, cit_brk + 1, tail);
return tail;
}
LATTEST(tail.empty());
}
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;
}
@ -553,29 +603,28 @@ bool Row::shortenIfNeeded(pos_type const keep, 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;
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.
*/
if (cit->breakAt(w - wid, true)) {
if (cit->splitAt(w - wid, next_width, true, tail)) {
LYXERR0(*cit);
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;
moveElements(elements_, cit + 1, tail);
return tail;
}
return false;
return Elements();
}
void Row::reverseRTL(bool const rtl_par)
void Row::reverseRTL()
{
pos_type i = 0;
pos_type const end = elements_.size();
@ -587,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

109
src/Row.h
View File

@ -18,6 +18,7 @@
#include "Changes.h"
#include "Dimension.h"
#include "Font.h"
#include "RowFlags.h"
#include "support/docstring.h"
#include "support/types.h"
@ -57,9 +58,10 @@ public:
* by other methods that need to parse the Row contents.
*/
struct Element {
//
Element(Type const t, pos_type p, Font const & f, Change const & ch)
: type(t), pos(p), endpos(p + 1), inset(0),
extra(0), font(f), change(ch), final(false) {}
: type(t), pos(p), endpos(p + 1), font(f), change(ch) {}
// Return the number of separator in the element (only STRING type)
int countSeparators() const;
@ -86,24 +88,30 @@ public:
* adjusted to the actual pixel position.
*/
pos_type x2pos(int &x) const;
/** Break the element if possible, so that its width is less
* than \param w. Returns true on success. When \param force
* is true, the string is cut at any place, other wise it
* respects the row breaking rules of characters.
/** Break the element in two if possible, so that its width is less
* than \param w.
* \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.
*/
bool breakAt(int w, bool force);
// Returns the position on left side of the element.
pos_type left_pos() const;
// Returns the position on right side of the element.
pos_type right_pos() const;
// 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();
//
bool isRTL() const { return font.isVisibleRightToLeft(); }
// This is true for virtual elements.
// Note that we do not use the type here. The two definitions
// should be equivalent
bool isVirtual() const { return pos == endpos; }
bool isVirtual() const { return type == VIRTUAL; }
// Returns the position on left side of the element.
pos_type left_pos() const { return isRTL() ? endpos : pos; };
// Returns the position on right side of the element.
pos_type right_pos() const { return isRTL() ? pos : endpos; };
// The kind of row element
Type type;
@ -116,10 +124,10 @@ public:
Dimension dim;
// Non-zero only if element is an inset
Inset const * inset;
Inset const * inset = nullptr;
// Only non-null for justified rows
double extra;
double extra = 0;
// Non-empty if element is a string or is virtual
docstring str;
@ -128,14 +136,19 @@ public:
//
Change change;
// is it possible to add contents to this element?
bool final;
bool final = false;
// properties with respect to row breaking (made of RowFlag enums)
int row_flags = Inline;
friend std::ostream & operator<<(std::ostream & os, Element const & row);
};
///
typedef Element value_type;
///
Row();
Row() {}
/**
* Helper function: set variable \c var to value \c val, and mark
* row as changed is the values were different. This is intended
@ -232,7 +245,7 @@ public:
Font const & f, Change const & ch);
///
void add(pos_type pos, char_type const c,
Font const & f, Change const & ch);
Font const & f, Change const & ch, bool can_break);
///
void addVirtual(pos_type pos, docstring const & s,
Font const & f, Change const & ch);
@ -264,18 +277,19 @@ public:
Element & back() { return elements_.back(); }
///
Element const & back() const { return elements_.back(); }
/// remove last element
/// add element at the end and update width
void push_back(Element const &);
/// remove last element and update width
void pop_back();
/**
* if row width is too large, remove all elements after last
* separator and update endpos if necessary. If all that
* remains is a large word, cut it to \param width.
* \param body_pos minimum amount of text to keep.
* \param width maximum width of the row.
* \param available width on next row.
* \return true if the row has been shortened.
* \param next_width available width on next row.
* \return list of elements remaining after breaking.
*/
bool shortenIfNeeded(pos_type const body_pos, 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
@ -287,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; }
@ -301,21 +317,21 @@ public:
friend std::ostream & operator<<(std::ostream & os, Row const & row);
/// additional width for separators in justified rows (i.e. space)
double separator;
double separator = 0;
/// width of hfills in the label
double label_hfill;
double label_hfill = 0;
/// the left margin position of the row
int left_margin;
int left_margin = 0;
/// the right margin of the row
int right_margin;
int right_margin = 0;
///
mutable pos_type sel_beg;
mutable pos_type sel_beg = -1;
///
mutable pos_type sel_end;
mutable pos_type sel_end = -1;
///
mutable bool begin_margin_sel;
mutable bool begin_margin_sel = false;
///
mutable bool end_margin_sel;
mutable bool end_margin_sel = false;
private:
/// Decides whether the margin is selected.
@ -340,27 +356,36 @@ private:
Elements elements_;
/// has the Row appearance changed since last drawing?
mutable bool changed_;
mutable bool changed_ = true;
/// Index of the paragraph that contains this row
pit_type pit_;
pit_type pit_ = 0;
/// first pos covered by this row
pos_type pos_;
pos_type pos_ = 0;
/// one behind last pos covered by this row
pos_type end_;
pos_type end_ = 0;
// Is there a boundary at the end of the row (display inset...)
bool right_boundary_;
bool right_boundary_ = false;
// Shall the row be flushed when it is supposed to be justified?
bool flushed_;
bool flushed_ = false;
/// Row dimension.
Dimension dim_;
/// Row contents dimension. Does not contain the space above/below row.
Dimension contents_dim_;
/// true when this row lives in a right-to-left paragraph
bool rtl_;
bool rtl_ = false;
/// true when a changebar should be drawn in the margin
bool changebar_;
bool changebar_ = false;
};
std::ostream & operator<<(std::ostream & os, Row::Elements const & elts);
/**
* Each paragraph is broken up into a number of rows on the screen.
* This is a list of such on-screen rows, ordered from the top row
* downwards.
*/
typedef std::vector<Row> RowList;
} // namespace lyx

61
src/RowFlags.h Normal file
View File

@ -0,0 +1,61 @@
// -*- C++ -*-
/**
* \file RowFlags.h
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Jean-Marc Lasgouttes
*
* Full author contact details are available in file CREDITS.
*/
#ifndef ROWFLAGS_H
#define ROWFLAGS_H
// Do not include anything here
namespace lyx {
/* The list of possible flags, that can be combined.
* Some flags that should logically be here (e.g.,
* CanBreakBefore), do not exist. This is because the need has not
* been identitfied yet.
*
* Priorities when before/after disagree:
* AlwaysBreak* > NoBreak* > Break* or CanBreak*.
*/
enum RowFlags {
// Do not break before or after this element, except if really
// needed (between NoBreak* and CanBreak*).
Inline = 0,
// break row before this element if the row is not empty
BreakBefore = 1 << 0,
// Avoid breaking row before this element
NoBreakBefore = 1 << 1,
// flush the row before this element (useful with BreakBefore)
FlushBefore = 1 << 2,
// force new (maybe empty) row after this element
AlwaysBreakAfter = 1 << 3,
// break row after this element if there are more elements
BreakAfter = 1 << 4,
// break row whenever needed after this element
CanBreakAfter = 1 << 5,
// Avoid breaking row after this element
NoBreakAfter = 1 << 6,
// The contents of the row may be broken in two (e.g. string)
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 << 9,
AlignRight = 1 << 10,
// A display element breaks row at both ends
Display = FlushBefore | BreakBefore | BreakAfter,
// Flags that concern breaking after element
AfterFlags = AlwaysBreakAfter | BreakAfter | CanBreakAfter | NoBreakAfter
};
} // namespace lyx
#endif

View File

@ -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())
@ -516,43 +516,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) {
@ -634,10 +619,10 @@ LyXAlignment TextMetrics::getAlign(Paragraph const & par, Row const & row) const
// Display-style insets should always be on a centered row
if (Inset const * inset = par.getInset(row.pos())) {
if (inset->rowFlags() & Inset::Display) {
if (inset->rowFlags() & Inset::AlignLeft)
if (inset->rowFlags() & Display) {
if (inset->rowFlags() & AlignLeft)
align = LYX_ALIGN_BLOCK;
else if (inset->rowFlags() & Inset::AlignRight)
else if (inset->rowFlags() & AlignRight)
align = LYX_ALIGN_RIGHT;
else
align = LYX_ALIGN_CENTER;
@ -868,44 +853,29 @@ private:
} // namespace
/** 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
Row TextMetrics::tokenizeParagraph(pit_type const pit) const
{
LATTEST(row.empty());
Paragraph const & par = text_->getPar(row.pit());
Row row;
row.pit(pit);
Paragraph const & par = text_->getPar(pit);
Buffer const & buf = text_->inset().buffer();
BookmarksSection::BookmarkPosList bpl =
theSession().bookmarks().bookmarksInPar(buf.fileName(), par.id());
pos_type const end = par.size();
pos_type const 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())
if (ic_it.inTexted() && ic_it.text() == text_ && ic_it.pit() == pit)
ic_pos = ic_it.pos();
// Now we iterate through until we reach the right margin
// or the end of the par, then build a representation of the row.
pos_type i = pos;
FontIterator fi = FontIterator(*this, par, row.pit(), pos);
pos_type i = 0;
FontIterator fi = FontIterator(*this, par, pit, 0);
// The real stopping condition is a few lines below.
while (true) {
// Firstly, check whether there is a bookmark here.
@ -921,7 +891,7 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
// The stopping condition is here so that the display of a
// bookmark can take place at paragraph start too.
if (i >= end || (i != pos && row.width() > width))
if (i >= end)
break;
char_type c = par.getChar(i);
@ -939,11 +909,11 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
// 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());
labelEnd(pit) - row.width());
row.addSpace(i, add, *fi, par.lookupChange(i));
} else if (c == '\t')
row.addSpace(i, theFontMetrics(*fi).width(from_ascii(" ")),
*fi, par.lookupChange(i));
*fi, par.lookupChange(i));
else if (c == 0x2028 || c == 0x2029) {
/**
* U+2028 LINE SEPARATOR
@ -959,9 +929,10 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
// ⤶ U+2936 ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS
// ¶ U+00B6 PILCROW SIGN
char_type const screen_char = (c == 0x2028) ? 0x2936 : 0x00B6;
row.add(i, screen_char, *fi, par.lookupChange(i));
row.add(i, screen_char, *fi, par.lookupChange(i), i >= body_pos);
} else
row.add(i, c, *fi, par.lookupChange(i));
// row elements before body are unbreakable
row.add(i, c, *fi, par.lookupChange(i), i >= body_pos);
// add inline completion width
// draw logically behind the previous character
@ -978,74 +949,221 @@ bool TextMetrics::breakRow(Row & row, int const right_margin) const
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() & Inset::BreakBefore)
|| (prevInset && prevInset->rowFlags() & Inset::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() & Inset::RowAfter
|| (prevInset->rowFlags() & Inset::BreakAfter && i + 1 == end
&& text_->getEndLabel(row.pit()) != END_LABEL_NO_LABEL));
++i;
break;
}
++i;
++fi;
}
row.finalizeLast();
row.endpos(i);
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())
&& !need_new_row
&& i == end && size_type(row.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.
Font f(text_->layoutFont(row.pit()));
Font f(text_->layoutFont(pit));
f.fontInfo().setColor(Color_paragraphmarker);
f.setLanguage(par.getParLanguage(buf.params()));
// ¶ U+00B6 PILCROW SIGN
row.addVirtual(end, docstring(1, char_type(0x00B6)), f, change);
row.addVirtual(end, docstring(1, char_type(0x00B6)), f, endchange);
}
// Is there a end-of-paragaph change?
if (i == end && par.lookupChange(end).changed() && !need_new_row)
row.needsChangeBar(true);
// 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(body_pos, 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;
return row;
}
namespace {
/** 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(); }
value_type const * operator->() const { return pile_.empty() ? &*cit_ : &pile_.back(); }
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_;
// 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();
}
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);
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
// but not the right_margin.
nrow.dim().wid = nrow.left_margin;
return nrow;
}
void cleanupRow(Row & row, bool at_end)
{
if (row.empty()) {
row.endpos(0);
return;
}
row.endpos(row.back().endpos);
// remove trailing spaces on row break
if (!at_end)
row.back().rtrim();
// 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();
}
// 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;
}
}
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);
flexible_const_iterator<Row> const end = flexible_end(bigrow);
while (true) {
bool const row_empty = rows.empty() || 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;
if (rows.empty() || needsRowBreak(f1, f2)) {
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.
width = max_width_ - rows.back().right_margin;
}
// The stopping condition is here because we may need a new
// empty row at the end.
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 = *fcit;
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(), true);
// Last row in paragraph is flushed
rows.back().flushed(true);
}
return rows;
}
int TextMetrics::parTopSpacing(pit_type const pit) const
{
Paragraph const & par = text_->getPar(pit);
@ -1775,7 +1893,7 @@ int TextMetrics::leftMargin(pit_type const pit, pos_type const pos) const
// display style insets do not need indentation
&& !(!par.empty()
&& par.isInset(0)
&& par.getInset(0)->rowFlags() & Inset::Display)
&& par.getInset(0)->rowFlags() & Display)
&& (!(tclass.isDefaultLayout(par.layout())
|| tclass.isPlainLayout(par.layout()))
|| buffer.params().paragraph_separation

View File

@ -151,10 +151,11 @@ private:
/// FIXME??
int labelEnd(pit_type const pit) const;
/// sets row.end to the pos value *after* which a row should break.
/// for example, the pos after which isNewLine(pos) == true
/// \return true when another row is required (after a newline)
bool breakRow(Row & row, int right_margin) 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;
// Expands the alignment of row \param row in paragraph \param par
LyXAlignment getAlign(Paragraph const & par, Row const & row) const;

View File

@ -16,6 +16,8 @@
#include "support/strfwd.h"
#include <vector>
/**
* A class holding helper functions for determining
* the screen dimensions of fonts.
@ -121,14 +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 at width at most x.
* \return true if successful
* \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 bool breakAt(docstring & 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;
/**

View File

@ -19,6 +19,7 @@
#include "support/convert.h"
#include "support/lassert.h"
#include "support/lstrings.h"
#include "support/lyxlib.h"
#include "support/debug.h"
@ -85,25 +86,27 @@ namespace lyx {
namespace frontend {
/*
* Limit (strwidth|breakat)_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;
namespace {
// Maximal size/cost for various caches. See QCache documentation to
// see what cost means.
// Limit strwidth_cache_ total cost to 1MB of string data.
int const strwidth_cache_max_cost = 1024 * 1024;
// Limit breakat_cache_ total cost to 10MB of string data.
// This is useful for documents with very large insets.
int const breakstr_cache_max_cost = 10 * 1024 * 1024;
// 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)
int cache_metrics_qtextlayout_size = 500;
// Limit qtextlayout_cache_ size to 500 elements (we do not know the
// size of the QTextLayout objects anyway).
int const qtextlayout_cache_max_size = 500;
#else
int cache_metrics_qtextlayout_size = 0;
// Disable the cache
int const qtextlayout_cache_max_size = 0;
#endif
namespace {
/**
* Convert a UCS4 character into a QChar.
* This is a hack (it does only make sense for the common part of the UCS4
@ -127,9 +130,9 @@ 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),
qtextlayout_cache_(cache_metrics_qtextlayout_size)
strwidth_cache_(strwidth_cache_max_cost),
breakstr_cache_(breakstr_cache_max_cost),
qtextlayout_cache_(qtextlayout_cache_max_size)
{
// Determine italic slope
double const defaultSlope = tan(qDegreesToRadians(19.0));
@ -485,11 +488,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.
*
@ -518,31 +523,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);
tl.setTextOption(to);
tl.beginLayout();
QTextLine line = tl.createLine();
line.setLineWidth(x);
tl.createLine();
tl.endLayout();
int const line_wid = iround(line.horizontalAdvance());
if ((force && line.textLength() == offset) || line_wid > x)
return {-1, -1};
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
@ -552,42 +549,108 @@ 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
return {len, line_wid};
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.
// 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 breaks;
}
uint qHash(BreakAtKey const & key)
uint qHash(BreakStringKey const & key)
{
int params = key.force + 2 * key.rtl + 4 * key.x;
// assume widths are less than 10000. This fits in 32 bits.
uint params = key.force + 2 * key.rtl + 4 * key.first_wid + 10000 * key.wid;
return std::qHash(key.s) ^ ::qHash(params);
}
bool GuiFontMetrics::breakAt(docstring & 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();
BreakAtKey key{s, x, rtl, force};
pair<int, int> pp;
if (auto * pp_ptr = breakat_cache_.object_ptr(key))
pp = *pp_ptr;
BreakStringKey key{s, first_wid, wid, rtl, force};
Breaks brks;
if (auto * brks_ptr = breakstr_cache_.object_ptr(key))
brks = *brks_ptr;
else {
PROFILE_CACHE_MISS(breakAt);
pp = breakAt_helper(s, x, rtl, force);
breakat_cache_.insert(key, pp, sizeof(key) + s.size() * sizeof(char_type));
PROFILE_CACHE_MISS(breakString);
brks = breakString_helper(s, first_wid, wid, rtl, force);
breakstr_cache_.insert(key, brks, sizeof(key) + s.size() * sizeof(char_type));
}
if (pp.first == -1)
return false;
s = s.substr(0, pp.first);
x = pp.second;
return true;
return brks;
}

View File

@ -27,14 +27,16 @@
namespace lyx {
namespace frontend {
struct BreakAtKey
struct BreakStringKey
{
bool operator==(BreakAtKey const & key) const {
return key.s == s && key.x == x && key.rtl == rtl && key.force == force;
bool operator==(BreakStringKey const & key) const {
return key.s == s && key.first_wid == first_wid && key.wid == wid
&& key.rtl == rtl && key.force == force;
}
docstring s;
int x;
int first_wid;
int wid;
bool rtl;
bool force;
};
@ -77,7 +79,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;
bool breakAt(docstring & 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,
@ -101,8 +103,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_;
@ -117,8 +119,8 @@ private:
mutable QHash<char_type, int> width_cache_;
/// Cache of string widths
mutable Cache<docstring, int> strwidth_cache_;
/// Cache for breakAt
mutable Cache<BreakAtKey, std::pair<int, int>> breakat_cache_;
/// Cache for breakString
mutable Cache<BreakStringKey, Breaks> breakstr_cache_;
/// Cache for QTextLayout
mutable Cache<TextLayoutKey, std::shared_ptr<QTextLayout>> qtextlayout_cache_;

View File

@ -20,6 +20,7 @@
#include "LayoutEnums.h"
#include "OutputEnums.h"
#include "OutputParams.h"
#include "RowFlags.h"
#include "support/docstring.h"
#include "support/strfwd.h"
@ -478,26 +479,8 @@ public:
virtual CtObject getCtObject(OutputParams const &) const;
enum RowFlags {
Inline = 0,
// break row before this inset
BreakBefore = 1 << 0,
// break row after this inset
BreakAfter = 1 << 1,
// it is possible to break after this inset
CanBreakAfter = 1 << 2,
// force new (maybe empty) row after this inset
RowAfter = 1 << 3,
// specify an alignment (left, right) for a display inset
// (default is center)
AlignLeft = 1 << 4,
AlignRight = 1 << 5,
// A display inset breaks row at both ends
Display = BreakBefore | BreakAfter
};
/// How should this inset be displayed in its row?
virtual RowFlags rowFlags() const { return Inline; }
// properties with respect to row breaking (made of RowFLag enums)
virtual int rowFlags() const { return Inline; }
/// indentation before this inset (only needed for displayed hull insets with fleqn option)
virtual int indent(BufferView const &) const { return 0; }
///
@ -655,20 +638,6 @@ protected:
};
inline Inset::RowFlags operator|(Inset::RowFlags const d1,
Inset::RowFlags const d2)
{
return static_cast<Inset::RowFlags>(int(d1) | int(d2));
}
inline Inset::RowFlags operator&(Inset::RowFlags const d1,
Inset::RowFlags const d2)
{
return static_cast<Inset::RowFlags>(int(d1) & int(d2));
}
} // namespace lyx
#endif

View File

@ -47,7 +47,7 @@ public:
///
InsetCode lyxCode() const override { return BIBTEX_CODE; }
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
///
void latex(otexstream &, OutputParams const &) const override;
///

View File

@ -40,7 +40,7 @@ private:
///
void write(std::ostream & os) const override;
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
///
bool neverIndent() const override { return true; }
///

View File

@ -32,7 +32,7 @@ public:
///
InsetCode lyxCode() const override { return FLOAT_LIST_CODE; }
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
///
void write(std::ostream &) const override;
///

View File

@ -1251,7 +1251,7 @@ string InsetInclude::contextMenuName() const
}
Inset::RowFlags InsetInclude::rowFlags() const
int InsetInclude::rowFlags() const
{
return type(params()) == INPUT ? Inline : Display;
}

View File

@ -75,7 +75,7 @@ public:
///
void draw(PainterInfo & pi, int x, int y) const override;
///
RowFlags rowFlags() const override;
int rowFlags() const override;
///
InsetCode lyxCode() const override { return INCLUDE_CODE; }
///

View File

@ -119,7 +119,7 @@ public:
///
bool hasSettings() const override;
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
//@}
/// \name Static public methods obligated for InsetCommand derived classes

View File

@ -64,7 +64,7 @@ InsetListings::~InsetListings()
}
Inset::RowFlags InsetListings::rowFlags() const
int InsetListings::rowFlags() const
{
return params().isInline() || params().isFloat() ? Inline : Display | AlignLeft;
}

View File

@ -46,7 +46,7 @@ private:
///
InsetCode lyxCode() const override { return LISTINGS_CODE; }
/// lstinline is inlined, normal listing is displayed
RowFlags rowFlags() const override;
int rowFlags() const override;
///
docstring layoutName() const override;
///

View File

@ -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) {

View File

@ -47,7 +47,7 @@ public:
explicit InsetNewline(InsetNewlineParams par) : Inset(0)
{ params_.kind = par.kind; }
///
RowFlags rowFlags() const override { return BreakAfter | RowAfter; }
int rowFlags() const override;
///
static void string2params(std::string const &, InsetNewlineParams &);
///

View File

@ -76,7 +76,7 @@ private:
///
void write(std::ostream & os) const override;
///
RowFlags rowFlags() const override { return (params_.kind == InsetNewpageParams::NOPAGEBREAK) ? Inline : Display; }
int rowFlags() const override { return (params_.kind == InsetNewpageParams::NOPAGEBREAK) ? Inline : Display; }
///
docstring insetLabel() const;
///

View File

@ -94,7 +94,7 @@ public:
///
bool hasSettings() const override { return true; }
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
///
void latex(otexstream &, OutputParams const &) const override;
///

View File

@ -65,7 +65,7 @@ public:
return docstring();
}
///
RowFlags rowFlags() const override { return BreakAfter; }
int rowFlags() const override { return BreakAfter | Flush; }
private:
///
InsetCode lyxCode() const override { return SEPARATOR_CODE; }

View File

@ -192,7 +192,7 @@ bool InsetSpace::getStatus(Cursor & cur, FuncRequest const & cmd,
}
Inset::RowFlags InsetSpace::rowFlags() const
int InsetSpace::rowFlags() const
{
switch (params_.kind) {
case InsetSpaceParams::PROTECTED:

View File

@ -115,7 +115,7 @@ public:
///
docstring toolTip(BufferView const & bv, int x, int y) const override;
/// unprotected spaces allow line breaking after them
RowFlags rowFlags() const override;
int rowFlags() const override;
///
void metrics(MetricsInfo &, Dimension &) const override;
///

View File

@ -83,7 +83,7 @@ docstring InsetSpecialChar::toolTip(BufferView const &, int, int) const
}
Inset::RowFlags InsetSpecialChar::rowFlags() const
int InsetSpecialChar::rowFlags() const
{
switch (kind_) {
case ALLOWBREAK:

View File

@ -63,7 +63,7 @@ public:
///
docstring toolTip(BufferView const & bv, int x, int y) const override;
/// some special chars allow line breaking after them
RowFlags rowFlags() const override;
int rowFlags() const override;
///
void metrics(MetricsInfo &, Dimension &) const override;
///

View File

@ -37,7 +37,7 @@ public:
///
docstring layoutName() const override;
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
///
void validate(LaTeXFeatures &) const override;
///

View File

@ -6144,21 +6144,21 @@ bool InsetTabular::getStatus(Cursor & cur, FuncRequest const & cmd,
}
Inset::RowFlags InsetTabular::rowFlags() const
int InsetTabular::rowFlags() const
{
if (tabular.is_long_tabular) {
switch (tabular.longtabular_alignment) {
case Tabular::LYX_LONGTABULAR_ALIGN_LEFT:
return Display | AlignLeft;
case Tabular::LYX_LONGTABULAR_ALIGN_CENTER:
return Display;
case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT:
return Display | AlignRight;
default:
return Display;
}
} else
return Inline;
if (tabular.is_long_tabular) {
switch (tabular.longtabular_alignment) {
case Tabular::LYX_LONGTABULAR_ALIGN_LEFT:
return Display | AlignLeft;
case Tabular::LYX_LONGTABULAR_ALIGN_CENTER:
return Display;
case Tabular::LYX_LONGTABULAR_ALIGN_RIGHT:
return Display | AlignRight;
default:
return Display;
}
} else
return Inline;
}

View File

@ -989,7 +989,7 @@ public:
//
bool isTable() const override { return true; }
///
RowFlags rowFlags() const override;
int rowFlags() const override;
///
void latex(otexstream &, OutputParams const &) const override;
///

View File

@ -62,7 +62,7 @@ private:
///
void write(std::ostream & os) const override;
///
RowFlags rowFlags() const override { return Display; }
int rowFlags() const override { return Display; }
///
void doDispatch(Cursor & cur, FuncRequest & cmd) override;
///

View File

@ -988,7 +988,7 @@ bool InsetMathHull::outerDisplay() const
}
Inset::RowFlags InsetMathHull::rowFlags() const
int InsetMathHull::rowFlags() const
{
switch (type_) {
case hullUnknown:

View File

@ -288,7 +288,7 @@ public:
///
Inset * editXY(Cursor & cur, int x, int y) override;
///
RowFlags rowFlags() const override;
int rowFlags() const override;
/// helper function
bool display() const { return rowFlags() & Display; }