Fix line breaking algorithm

Break words longer than the screen width. The code is more complicated
than I would like, but I have no better idea right now.

Implement properly the notion of a row broken by a display inset. This is useful in different places.

Also fix a bug with last line of a paragraph spotted by Kornel.
This commit is contained in:
Jean-Marc Lasgouttes 2013-07-23 16:24:01 +02:00
parent ff608f46fd
commit 6258cebb77
4 changed files with 75 additions and 61 deletions

View File

@ -1,35 +1,41 @@
This branch is where I (jmarc) try to implement string_wise metrics
computation. This is done through a series of cleanups. The expected
speed improvement will only be visible at the end of the road: indeed
for now we intend to keep unchanged behavior for testing purposes.
computation. This is done through a series of cleanups. The goal is to
have both good metrics computation (and font with proper kerning and
ligatures) and better performance than what we have with
force_paint_single_char.
Currently everything is supposed to work for LTR text, and RTL text
should work too except possibly metrics with Arabic and Hebrew fonts.
We'll see what to do after some feedback.
What is done:
* Make TextMetrics methods operate on Row objects: breakRow and
setRowHeight instead of rowBreakPoint and rowHeight.
* change breakRow operation to operate on text strings on which
* Change breakRow operation to operate on text strings on which
metrics are computed. The list of elements is stored in the row
object in visual ordering, not logical.
* re-implement cursorX using row elements
* re-implement getColumnNearX using row elements.
* Re-implement cursorX and getColumnNearX using row elements
* Implement proper string metrics computation (with cache), when
lyxrc.force_paint_single_char is false. In this case, remove also
useless workarounds which disable kerning and ligatures.
Next steps:
* get rid of old code of cursorX and getColumnNearX (which have been
Next possible steps:
* Get rid of old code of cursorX and getColumnNearX (which have been
kept for comparison purpose, guarded with KEEP_OLD_METRICS_CODE in
order to check computations).
* re-implement row painting using row elements (can it be done?)
* Re-implement row painting using row elements. This is not difficult
in principle, but the code is intricate and needs some careful
analysis.
* profile and see how performance can be improved.
* Profile and see how performance can be improved.
* Document the code
Difference in behavior (aka bug fixes)
@ -39,7 +45,7 @@ Difference in behavior (aka bug fixes)
* When cursor is after a LTR separator just before a RTL chunk, the
cursor posiiton is computed better with the new code.
Other differences (aka real bugs)
* words longer than the screen are no monger broken at an arbitrary
point. This is a problem for languages like chinese that do not use
separators.
You tell me.

View File

@ -54,7 +54,7 @@ double Row::Element::pos2x(pos_type const i) const
}
pos_type Row::Element::x2pos(double &x, bool const low) const
pos_type Row::Element::x2pos(double &x, bool const low) const
{
//lyxerr << "x2pos: x=" << x << " w=" << width() << " " << *this;
// if element is rtl, flip x value
@ -104,9 +104,9 @@ double Row::Element::pos2x(pos_type const i) const
Row::Row()
: separator(0), label_hfill(0), x(0), right_margin(0),
sel_beg(-1), sel_end(-1),
begin_margin_sel(false), end_margin_sel(false),
changed_(false), crc_(0), pos_(0), end_(0)
sel_beg(-1), sel_end(-1),
begin_margin_sel(false), end_margin_sel(false),
changed_(false), crc_(0), pos_(0), end_(0), right_boundary_(false)
{}
@ -117,18 +117,6 @@ void Row::setCrc(size_type crc) const
}
void Row::pos(pos_type p)
{
pos_ = p;
}
void Row::endpos(pos_type p)
{
end_ = p;
}
bool Row::isMarginSelected(bool left_margin, DocIterator const & beg,
DocIterator const & end) const
{
@ -337,6 +325,10 @@ void Row::shorten_if_needed(pos_type const keep, int const w)
{
if (empty() || width() < w)
return;
/** First, we try to remove elements one by one from the end
* until a separator is found.
*/
int i = elements_.size();
int new_end = end_;
int new_wid = dim_.wid;
@ -352,23 +344,35 @@ void Row::shorten_if_needed(pos_type const keep, int const w)
new_wid -= elements_[i].dim.wid;
}
if (i == 0) {
if (elements_.size() != 1) {
LYXERR0("Row is too large but has more than one element. " << *this);
/* If we are here, it means that we have not found a
* separator to shorten the row. There is one case
* where we can do something: when we have one big
* string, maybe with a paragraph marker after it.
*/
Element & front = elements_.front();
if (!(front.type == STRING
&& (elements_.size() == 1
|| (elements_.size() == 2
&& back().type == VIRTUAL))))
return;
}
#if 1
return;
#else
// does not work yet
if (back().type != STRING)
// If this is a string element, we can try to split it.
if (front.type != STRING)
return;
double xstr = w - x;
pos_type new_pos = back().x2pos(xstr, true);
back().str = back().str.substr(0, new_pos);
back().endpos = new_pos;
// If there is a paragraph marker, it should be taken in account
if (elements_.size() == 2)
xstr -= back().width();
pos_type new_pos = front.x2pos(xstr, true);
front.str = front.str.substr(0, new_pos - pos_);
front.dim.wid = xstr;
front.endpos = new_pos;
end_ = new_pos;
dim_.wid = x + xstr;
#endif
// If there is a paragraph marker, it should be removed.
if (elements_.size() == 2)
elements_.pop_back();
return;
}
end_ = new_end;
dim_.wid = new_wid;

View File

@ -128,13 +128,18 @@ public:
DocIterator const & end) const;
///
void pos(pos_type p);
void pos(pos_type p) { pos_ = p; }
///
pos_type pos() const { return pos_; }
///
void endpos(pos_type p);
void endpos(pos_type p) { end_ = p; }
///
pos_type endpos() const { return end_; }
///
void right_boundary(bool b) { right_boundary_ = b; }
///
bool right_boundary() const { return right_boundary_; }
///
Dimension const & dimension() const { return dim_; }
///
@ -256,6 +261,8 @@ private:
pos_type pos_;
/// one behind last pos covered by this row
pos_type end_;
// Is there is a boundary at the end of the row (display inset...)
bool right_boundary_;
/// Row dimension.
Dimension dim_;
};

View File

@ -619,18 +619,11 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
switch (align) {
case LYX_ALIGN_BLOCK: {
int const ns = numberOfSeparators(row);
bool disp_inset = false;
if (row.endpos() < par.size()) {
Inset const * in = par.getInset(row.endpos());
if (in)
disp_inset = in->display();
}
// If we have separators, this is not the last row of a
// par, does not end in newline, and is not row above a
// display inset... then stretch it
if (ns && row.endpos() < par.size()
&& !par.isNewline(row.endpos() - 1)
&& !disp_inset) {
/** If we have separators, and this row has
* not be broken abruptly by a display inset
* or newline, then stretch it */
if (ns && !row.right_boundary()
&& row.endpos() != par.size()) {
setSeparatorWidth(row, w / ns);
row.dimension().wid = width;
//lyxerr << "row.separator " << row.separator << endl;
@ -803,6 +796,10 @@ private:
} // anon 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::shorten_if_needed
*/
void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit) const
{
Paragraph const & par = text_->getPar(pit);
@ -812,6 +809,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
pos_type const body_pos = par.beginOfBody();
row.clear();
row.dimension().wid = leftMargin(max_width_, pit, pos);
row.x = row.width();
row.right_margin = right_margin;
if (pos >= end || row.width() > width) {
@ -893,6 +891,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
&& inset->display())
|| (!row.empty() && row.back().inset
&& row.back().inset->display())) {
row.right_boundary(true);
++i;
break;
}
@ -1153,13 +1152,11 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
/** This tests for the case where the cursor is set at the end
* of a row which has been broken due to a display inset on
* next row. This can be recognized because the end of the
* last element is the same as the end of the row (there is no
* separator at the end of the row)
* next row. This is indicated by Row::right_boundary.
*/
if (!row.empty() && pos == row.back().endpos
&& row.back().endpos == row.endpos())
boundary = true;
boundary = row.right_boundary();
#if !defined(KEEP_OLD_METRICS_CODE)
return pos - row.pos();