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.
This commit is contained in:
Jean-Marc Lasgouttes 2021-09-06 14:52:42 +02:00
parent 71c2e2fda6
commit d723b90344
7 changed files with 256 additions and 197 deletions

View File

@ -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;
}
return Element();
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;
}
@ -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();
}

View File

@ -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
};
/**
@ -60,8 +58,6 @@ public:
* by other methods that need to parse the Row contents.
*/
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);

View File

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

View File

@ -1059,7 +1059,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);
@ -1067,11 +1067,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);
}
@ -1098,6 +1099,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);
@ -1115,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(), 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.
@ -1130,45 +1133,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);
}

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,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;
/**

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"
@ -86,14 +87,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)
@ -128,7 +129,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
@ -485,11 +486,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,34 +521,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
@ -555,52 +547,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
// 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;
}
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);
}
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();
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));
}
x = pp.second;
return pp.first;
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;
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,
@ -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_;