Re-implement getColumNearX using row elements

The code is now so much shorter that it is scary... Expect that
further changes will be necessary

Cursor boundary is not handled yet.
This commit is contained in:
Jean-Marc Lasgouttes 2013-07-21 20:22:32 +02:00
parent 01c9bcb432
commit f215bb3b92
4 changed files with 202 additions and 94 deletions

View File

@ -13,15 +13,17 @@ What is done:
* re-implement cursorX using row elements
* re-implement getColumnNearX using row elements (boundary is not
considered yet).
* Implement proper string metrics computation (with cache), when
lyxrc.force_paint_single_char is false.
lyxrc.force_paint_single_char is false. In this case, remove also
useless workarounds which disable kerning and ligatures.
Next steps:
* re-implement getColumnNearX using row elements
* get rid of old code of cursorX and getColumnNearX (which have been
kept for comparison purpose).
kept for comparison purpose, guarded with KEEP_OLD_METRICS_CODE).
* re-implement row painting using row elements (can it be done?)
@ -41,3 +43,5 @@ Other differences that should be considered as 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.
* Boundary is not taken in account properly in getColumnNearX

View File

@ -54,8 +54,58 @@ double Row::Element::pos2x(pos_type const i) const
}
pos_type Row::Element::x2pos(double &x) const
{
//lyxerr << "x2pos: x=" << x << " w=" << width() << " " << *this;
// if element is rtl, flip x value
bool const rtl = font.isVisibleRightToLeft();
double x2 = rtl ? (width() - x) : x;
FontMetrics const & fm = theFontMetrics(font);
double last_w = 0;
double w = 0;
size_t i = 1;
// non-STRING element only contain one position
if (type != STRING) {
i = 0;
w = width();
} else {
// FIXME: implement dichotomy search?
for ( ; i <= str.size() ; ++i) {
last_w = w;
w = fm.width(str.substr(0,i));
if (w > x2) {
--i;
break;
}
}
// if (i == str.size())
// lyxerr << " NOT FOUND ";
}
// round to the closest side
if (x2 - last_w > w - x2) {
x2 = w;
++i;
} else
x2 = last_w;
// is element is rtl, flip values
if (rtl) {
x = last_w - x2;
i = endpos - i;
} else {
x = x2;
i = pos + i;
}
//lyxerr << "=> p=" << i << " x=" << x << endl;
return i;
}
Row::Row()
: separator(0), label_hfill(0), x(0),
: 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)
@ -153,19 +203,19 @@ ostream & operator<<(ostream & os, Row::Element const & e)
os << e.pos << ">>" << e.endpos << " ";
switch (e.type) {
case Row::Element::STRING:
os << "STRING: `" << to_utf8(e.str) << "'";
case Row::STRING:
os << "STRING: `" << to_utf8(e.str) << "' " << e.dim.wid;
break;
case Row::Element::COMPLETION:
os << "COMPLETION: `" << to_utf8(e.str) << "'";
case Row::VIRTUAL:
os << "VIRTUAL: `" << to_utf8(e.str) << "'";
break;
case Row::Element::INSET:
case Row::INSET:
os << "INSET: " << to_utf8(e.inset->layoutName());
break;
case Row::Element::SEPARATOR:
case Row::SEPARATOR:
os << "SEPARATOR: " << e.dim.wid << "+" << e.extra;
break;
case Row::Element::SPACE:
case Row::SPACE:
os << "SPACE: " << e.dim.wid;
break;
}
@ -176,6 +226,7 @@ ostream & operator<<(ostream & os, Row::Element const & e)
ostream & operator<<(ostream & os, Row const & row)
{
os << " pos: " << row.pos_ << " end: " << row.end_
<< " x: " << row.x
<< " width: " << row.dim_.wid
<< " ascent: " << row.dim_.asc
<< " descent: " << row.dim_.des
@ -194,7 +245,7 @@ bool Row::sameString(Font const & f, Change const & ch) const
if (elements_.empty())
return false;
Element const & elt = elements_.back();
return elt.type == Element::STRING && !elt.final
return elt.type == STRING && !elt.final
&& elt.font == f && elt.change == ch;
}
@ -208,7 +259,7 @@ void Row::finalizeLast()
return;
elt.final = true;
if (elt.type == Element::STRING) {
if (elt.type == STRING) {
elt.dim.wid = theFontMetrics(elt.font).width(elt.str);
dim_.wid += elt.dim.wid;
}
@ -219,7 +270,7 @@ void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
Font const & f, Change const & ch)
{
finalizeLast();
Element e(Element::INSET, pos, f, ch);
Element e(INSET, pos, f, ch);
e.inset = ins;
e.dim = dim;
elements_.push_back(e);
@ -232,7 +283,7 @@ void Row::add(pos_type const pos, char_type const c,
{
if (!sameString(f, ch)) {
finalizeLast();
Element e(Element::STRING, pos, f, ch);
Element e(STRING, pos, f, ch);
elements_.push_back(e);
}
//lyxerr << "FONT " <<back().font.language() << endl;
@ -241,11 +292,11 @@ void Row::add(pos_type const pos, char_type const c,
}
void Row::addCompletion(pos_type const pos, docstring const & s,
Font const & f, Change const & ch)
void Row::addVirtual(pos_type const pos, docstring const & s,
Font const & f, Change const & ch)
{
finalizeLast();
Element e(Element::COMPLETION, pos, f, ch);
Element e(VIRTUAL, pos, f, ch);
e.str = s;
// A completion has no size
e.endpos = pos;
@ -258,7 +309,7 @@ void Row::addSeparator(pos_type const pos, char_type const c,
Font const & f, Change const & ch)
{
finalizeLast();
Element e(Element::SEPARATOR, pos, f, ch);
Element e(SEPARATOR, pos, f, ch);
e.str += c;
e.dim.wid = theFontMetrics(f).width(c);
elements_.push_back(e);
@ -270,7 +321,7 @@ void Row::addSpace(pos_type const pos, int const width,
Font const & f, Change const & ch)
{
finalizeLast();
Element e(Element::SPACE, pos, f, ch);
Element e(SPACE, pos, f, ch);
e.dim.wid = width;
elements_.push_back(e);
dim_.wid += e.dim.wid;
@ -291,13 +342,13 @@ void Row::separate_back(pos_type const keep)
int i = elements_.size();
int new_end = end_;
int new_wid = dim_.wid;
if (i > 0 && elements_[i - 1].isSeparator() && new_end > keep) {
if (i > 0 && elements_[i - 1].type == SEPARATOR && new_end > keep) {
--i;
new_end = elements_[i].pos;
new_wid -= elements_[i].dim.wid;
}
while (i > 0 && !elements_[i - 1].isSeparator() && new_end > keep) {
while (i > 0 && elements_[i - 1].type != SEPARATOR && new_end > keep) {
--i;
new_end = elements_[i].pos;
new_wid -= elements_[i].dim.wid;

View File

@ -30,37 +30,50 @@ class DocIterator;
class Inset;
/**
* An on-screen row of text. A paragraph is broken into a
* RowList for display. Each Row contains position pointers
* into the first and last character positions of that row.
* An on-screen row of text. A paragraph is broken into a RowList for
* display. Each Row contains a tokenized description of the contents
* of the line.
*/
class Row {
public:
// Possible types of row elements
enum Type {
// a string of character
STRING,
/**
* Something (completion, end-of-par marker)
* that occupies space one screen but does not
* correspond to any paragraph contents
*/
VIRTUAL,
// A stretchable space, basically
SEPARATOR,
// An inset
INSET,
// Some spacing described by its width, not a string
SPACE
};
/**
* One element of a Row. It has a set of attributes that can be used
* by other methods that need to parse the Row contents.
*/
struct Element {
enum Type {
STRING,
COMPLETION,
SEPARATOR,
INSET,
SPACE
};
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) {}
//
bool isSeparator() const { return type == SEPARATOR; }
// returns total width of element, including separator overhead
double width() const { return dim.wid + extra; };
// returns position in pixels (from the left) of position
// \param i in the row element
double pos2x(pos_type const i) const;
// Return character position that is the closest to
// pixel position \param x. The value \param x is
// rounded to the actual pixel position.
pos_type x2pos(double &x) const;
// The kind of row element
Type type;
// position of the element in the paragraph
@ -139,8 +152,8 @@ public:
void add(pos_type pos, char_type const c,
Font const & f, Change const & ch);
///
void addCompletion(pos_type pos, docstring const & s,
Font const & f, Change const & ch);
void addVirtual(pos_type pos, docstring const & s,
Font const & f, Change const & ch);
///
void addSeparator(pos_type pos, char_type const c,
Font const & f, Change const & ch);
@ -197,8 +210,10 @@ public:
double separator;
/// width of hfills in the label
double label_hfill;
/// the x position of the row
/// the x position of the row (left margin)
double x;
/// the right margin of the row
int right_margin;
///
mutable pos_type sel_beg;
///

View File

@ -15,6 +15,8 @@
* Full author contact details are available in file CREDITS.
*/
//#define KEEP_OLD_METRICS_CODE 1
#include <config.h>
#include "TextMetrics.h"
@ -48,7 +50,7 @@
#include "support/debug.h"
#include "support/lassert.h"
#include <cstdlib>
#include <cmath>
using namespace std;
@ -65,7 +67,7 @@ int numberOfSeparators(Row const & row)
Row::const_iterator cit = row.begin();
Row::const_iterator const end = row.end();
for ( ; cit != end ; ++cit)
if (cit->isSeparator())
if (cit->type == Row::SEPARATOR)
++n;
return n;
}
@ -77,7 +79,7 @@ void setSeparatorWidth(Row & row, double w)
Row::iterator it = row.begin();
Row::iterator const end = row.end();
for ( ; it != end ; ++it)
if (it->isSeparator())
if (it->type == Row::SEPARATOR)
it->extra = w;
}
@ -630,6 +632,7 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
&& !par.isNewline(row.endpos() - 1)
&& !disp_inset) {
setSeparatorWidth(row, w / ns);
row.dimension().wid = width;
//lyxerr << "row.separator " << row.separator << endl;
//lyxerr << "ns " << ns << endl;
} else if (is_rtl) {
@ -672,7 +675,7 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
Row::iterator const cend = row.end();
for ( ; cit != cend; ++cit) {
if (row.label_hfill && cit->endpos == body_pos
&& cit->type == Row::Element::SPACE)
&& cit->type == Row::SPACE)
cit->dim.wid -= row.label_hfill * (nlh - 1);
if (!cit->inset || !cit->inset->isHfill())
continue;
@ -806,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.right_margin = right_margin;
if (pos >= end || row.width() > width) {
row.dimension().wid += right_margin;
@ -864,7 +868,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
// enlarge the last character to hold the end-of-par marker
Font f(text_->layoutFont(pit));
f.fontInfo().setColor(Color_paragraphmarker);
row.add(i, char_type(0x00B6), f, Change());
row.addVirtual(i, docstring(1, char_type(0x00B6)), f, Change());
}
// add inline completion width
@ -872,7 +876,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
!bv_->inlineCompletion().empty()) {
Font f = *fi;
f.fontInfo().setColor(Color_inlinecompletion);
row.addCompletion(i + 1, bv_->inlineCompletion(),
row.addVirtual(i + 1, bv_->inlineCompletion(),
f, Change());
}
@ -897,7 +901,8 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
if (body_pos && i == body_pos) {
FontMetrics const & fm = theFontMetrics(text_->labelFont(par));
pos_type j = i;
if (!row.empty() && row.back().isSeparator()) {
if (!row.empty()
&& row.back().type == Row::SEPARATOR) {
row.pop_back();
--j;
}
@ -916,7 +921,7 @@ void TextMetrics::breakRow(Row & row, int const right_margin, pit_type const pit
// if the row ends with a separator that is not at end of
// paragraph, remove it
if (!row.empty() && row.back().isSeparator()
if (!row.empty() && row.back().type == Row::SEPARATOR
&& row.endpos() < par.size())
row.pop_back();
@ -966,11 +971,11 @@ void TextMetrics::setRowHeight(Row & row, pit_type const pit,
// insets may be taller
ParagraphMetrics const & pm = par_metrics_[pit];
InsetList::const_iterator ii = par.insetList().begin();
InsetList::const_iterator iend = par.insetList().end();
for ( ; ii != iend; ++ii) {
if (ii->pos >= row.pos() && ii->pos < row.endpos()) {
Dimension const & dim = pm.insetDimension(ii->inset);
Row::const_iterator cit = row.begin();
Row::const_iterator cend = row.end();
for ( ; cit != cend; ++cit) {
if (cit->inset) {
Dimension const & dim = pm.insetDimension(cit->inset);
maxasc = max(maxasc, dim.ascent());
maxdesc = max(maxdesc, dim.descent());
}
@ -1109,13 +1114,40 @@ void TextMetrics::setRowHeight(Row & row, pit_type const pit,
pos_type TextMetrics::getColumnNearX(pit_type const pit,
Row const & row, int & x, bool & boundary) const
{
// FIXME: handle properly boundary (not done now)
pos_type pos = row.pos();
if (row.x >= x || row.empty())
x = row.x;
else if (x >= row.width() - row.right_margin) {
x = row.width() - row.right_margin;
pos = row.back().endpos;
} else {
double w = row.x;
Row::const_iterator cit = row.begin();
Row::const_iterator cend = row.end();
for ( ; cit != cend; ++cit) {
if (w <= x && w + cit->width() > x) {
double x_offset = x - w;
pos = cit->x2pos(x_offset);
x = x_offset + w;
break;
}
w += cit->width();
}
if (cit == row.end())
lyxerr << "NOT FOUND!! x=" << x << ", wid=" << row.width() << endl;
}
#if !defined(KEEP_OLD_METRICS_CODE)
return pos - row.pos();
#else
Buffer const & buffer = bv_->buffer();
/// For the main Text, it is possible that this pit is not
/// yet in the CoordCache when moving cursor up.
/// x Paragraph coordinate is always 0 for main text anyway.
int const xo = origin_.x_;
x -= xo;
int x2 = x - xo;
Paragraph const & par = text_->getPar(pit);
Bidi bidi;
bidi.computeTables(par, buffer, row);
@ -1138,7 +1170,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
// check for empty row
if (vc == end) {
x = int(tmpx) + xo;
x2 = int(tmpx) + xo;
return 0;
}
@ -1150,7 +1182,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
// the value of rtl.
bool const rtl_on_lastrow = lastrow ? text_->isRTL(par) : false;
while (vc < end && tmpx <= x) {
while (vc < end && tmpx <= x2) {
c = bidi.vis2log(vc);
last_tmpx = tmpx;
if (body_pos > 0 && c == body_pos - 1) {
@ -1167,7 +1199,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
++vc;
}
if ((tmpx + last_tmpx) / 2 > x) {
if ((tmpx + last_tmpx) / 2 > x2) {
tmpx = last_tmpx;
left_side = true;
}
@ -1175,11 +1207,11 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
// This shouldn't happen. But we can reset and try to continue.
LASSERT(vc <= end, vc = end);
boundary = false;
bool boundary2 = false;
if (lastrow &&
((rtl_on_lastrow && left_side && vc == row.pos() && x < tmpx - 5) ||
(!rtl_on_lastrow && !left_side && vc == end && x > tmpx + 5))) {
((rtl_on_lastrow && left_side && vc == row.pos() && x2 < tmpx - 5) ||
(!rtl_on_lastrow && !left_side && vc == end && x2 > tmpx + 5))) {
if (!par.isNewline(end - 1))
c = end;
} else if (vc == row.pos()) {
@ -1191,7 +1223,7 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
bool const rtl = (bidi.level(c) % 2 == 1);
if (left_side == rtl) {
++c;
boundary = isRTLBoundary(pit, c);
boundary2 = isRTLBoundary(pit, c);
}
}
@ -1223,18 +1255,25 @@ pos_type TextMetrics::getColumnNearX(pit_type const pit,
}
#endif
x = int(tmpx) + xo;
x2 = int(tmpx) + xo;
pos_type const col = c - row.pos();
if (abs(x2 - x) > 0.1 || boundary != boundary
|| c != pos) {
lyxerr << "new=(x=" << x << ", b=" << boundary << ", p=" << pos << "), "
<< "old=(x=" << x2 << ", b=" << boundary2 << ", p=" << c << "), " << row;
}
if (!c || end == par.size())
return col;
if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
boundary = true;
boundary2 = true;
return col;
}
return min(col, end - 1 - row.pos());
#endif
}
@ -1530,9 +1569,8 @@ int TextMetrics::cursorX(CursorSlice const & sl,
{
LASSERT(sl.text() == text_, return 0);
pit_type const pit = sl.pit();
pos_type ppos = sl.pos();
pos_type pos = sl.pos();
Paragraph const & par = text_->paragraphs()[pit];
ParagraphMetrics const & pm = par_metrics_[pit];
if (pm.rows().empty())
return 0;
@ -1543,42 +1581,41 @@ int TextMetrics::cursorX(CursorSlice const & sl,
/**
* When boundary is true, position is on the row element (pos, endpos)
* if
* pos < ppos <= endpos
* pos < pos <= endpos
* whereas, when boundary is false, the test is
* pos <= ppos < endpos
* pos <= pos < endpos
* The correction below allows to handle both cases.
*/
int const boundary_corr = (boundary && ppos) ? -1 : 0;
int const boundary_corr = (boundary && pos) ? -1 : 0;
if (row.empty()
if (row.empty()
|| (row.begin()->font.isRightToLeft()
&& ppos == row.begin()->endpos))
&& pos == row.begin()->endpos))
return int(x);
Row::const_iterator cit = row.begin();
for ( ; cit != row.end() ; ++cit) {
// lyxerr << "ppos=" << ppos << "(" << boundary_corr << ")"
// << ", x=" << x << " " << *cit << endl;
// lyxerr << "test1=" << (ppos + boundary_corr >= cit->pos)
// << " test2=" << ( ppos + boundary_corr < best->endpos) <<endl;
if (ppos + boundary_corr >= cit->pos
&& ppos + boundary_corr < cit->endpos) {
x += cit->pos2x(ppos);
if (pos + boundary_corr >= cit->pos
&& pos + boundary_corr < cit->endpos) {
x += cit->pos2x(pos);
break;
}
x += cit->width();
}
if (cit == row.end()
&& (row.back().font.isRightToLeft() || ppos != row.back().endpos))
&& (row.back().font.isRightToLeft() || pos != row.back().endpos))
lyxerr << "NOT FOUND!"
<< "ppos=" << ppos << "(" << boundary_corr << ")" << "\n"
<< "pos=" << pos << "(" << boundary_corr << ")" << "\n"
<< row;
#ifdef KEEP_OLD_METRICS_CODE
Paragraph const & par = text_->paragraphs()[pit];
// Correct position in front of big insets
bool const boundary_correction = ppos != 0 && boundary;
bool const boundary_correction = pos != 0 && boundary;
if (boundary_correction)
--ppos;
--pos;
pos_type cursor_vpos = 0;
@ -1598,18 +1635,18 @@ int TextMetrics::cursorX(CursorSlice const & sl,
if (end <= row_pos)
cursor_vpos = row_pos;
else if (ppos >= end)
else if (pos >= end)
cursor_vpos = text_->isRTL(par) ? row_pos : end;
else if (ppos > row_pos && ppos >= end)
else if (pos > row_pos && pos >= end)
//FIXME: this code is never reached!
// (see http://www.lyx.org/trac/changeset/8251)
// Place cursor after char at (logical) position pos - 1
cursor_vpos = (bidi.level(ppos - 1) % 2 == 0)
? bidi.log2vis(ppos - 1) + 1 : bidi.log2vis(ppos - 1);
cursor_vpos = (bidi.level(pos - 1) % 2 == 0)
? bidi.log2vis(pos - 1) + 1 : bidi.log2vis(pos - 1);
else
// Place cursor before char at (logical) position ppos
cursor_vpos = (bidi.level(ppos) % 2 == 0)
? bidi.log2vis(ppos) : bidi.log2vis(ppos) + 1;
// Place cursor before char at (logical) position pos
cursor_vpos = (bidi.level(pos) % 2 == 0)
? bidi.log2vis(pos) : bidi.log2vis(pos) + 1;
pos_type body_pos = par.beginOfBody();
if (body_pos > 0 &&
@ -1703,23 +1740,23 @@ int TextMetrics::cursorX(CursorSlice const & sl,
// see correction above
if (boundary_correction) {
if (isRTL(sl, boundary))
x2 -= singleWidth(pit, ppos);
x2 -= singleWidth(pit, pos);
else
x2 += singleWidth(pit, ppos);
x2 += singleWidth(pit, pos);
}
if (x2 != x) {
if (abs(x2 - x) > 0.01) {
lyxerr << "cursorX: x2=" << x2 << ", x=" << x;
if (cit == row.end())
lyxerr << "Element not found for "
<< ppos - boundary_corr << "(" << boundary_corr << ")";
<< pos - boundary_corr << "(" << boundary_corr << ")";
else
lyxerr << " in [" << cit->pos << "/"
<< ppos - boundary_corr << "(" << boundary_corr << ")"
<< pos - boundary_corr << "(" << boundary_corr << ")"
<< "/" << cit->endpos << "] of " << *cit << "\n";
lyxerr << row <<endl;
}
#endif
return int(x);
}
@ -2004,13 +2041,14 @@ int TextMetrics::leftMargin(int max_width,
}
#ifdef KEEP_OLD_METRICS_CODE
int TextMetrics::singleWidth(pit_type pit, pos_type pos) const
{
ParagraphMetrics const & pm = par_metrics_[pit];
return pm.singleWidth(pos, displayFont(pit, pos));
}
#endif
void TextMetrics::draw(PainterInfo & pi, int x, int y) const
{