Re-implement text justification

* GuiFontMetrics::pos2x, x2pos: add support for inter-word spacing.
* GuiPainter::text: idem

* Row::Element::countSeparators:
  Row::countSeparators: new methods that count spaces in strings.
  Row::setSeparatorExtraWidth: new method (code lifted from TextMetrics.cpp).

* TextMetrics::computeRowMetrics: rely on the above methods.

* RowPainter::paintMispelledMarked: pass only a Row::Element object reference
  RowPainter::paintStringAndSel: idem; do not rely on values returned by
      Painter::text (trailing spaces do not honor wordspacing value).
This commit is contained in:
Jean-Marc Lasgouttes 2015-07-19 01:22:10 +02:00
parent f65f3adbf7
commit b68f391232
11 changed files with 115 additions and 88 deletions

View File

@ -36,6 +36,15 @@ namespace lyx {
using support::rtrim;
using frontend::FontMetrics;
int Row::Element::countSeparators() const
{
if (type != STRING)
return 0;
return count(str.begin(), str.end(), ' ');
}
double Row::Element::pos2x(pos_type const i) const
{
// This can happen with inline completion when clicking on the
@ -54,7 +63,7 @@ double Row::Element::pos2x(pos_type const i) const
w = rtl ? full_width() : 0;
else {
FontMetrics const & fm = theFontMetrics(font);
w = fm.pos2x(str, i - pos, font.isVisibleRightToLeft());
w = fm.pos2x(str, i - pos, font.isVisibleRightToLeft(), extra);
}
return w;
@ -70,7 +79,7 @@ pos_type Row::Element::x2pos(int &x) const
switch (type) {
case STRING: {
FontMetrics const & fm = theFontMetrics(font);
i = fm.x2pos(str, x, rtl);
i = fm.x2pos(str, x, rtl, extra);
break;
}
case VIRTUAL:
@ -257,6 +266,26 @@ ostream & operator<<(ostream & os, Row const & row)
}
int Row::countSeparators() const
{
int n = 0;
const_iterator const end = elements_.end();
for (const_iterator cit = elements_.begin() ; cit != end ; ++cit)
n += cit->countSeparators();
return n;
}
void Row::setSeparatorExtraWidth(double w)
{
separator = w;
iterator const end = elements_.end();
for (iterator it = elements_.begin() ; it != end ; ++it)
if (it->type == Row::STRING)
it->extra = w;
}
bool Row::sameString(Font const & f, Change const & ch) const
{
if (elements_.empty())

View File

@ -76,7 +76,10 @@ public:
extra(0), font(f), change(ch), final(false) {}
// Return total width of element, including separator overhead
double full_width() const { return dim.wid + extra; };
double full_width() const { return dim.wid + extra * countSeparators(); };
// Return the number of separator in the element (only STRING type)
int countSeparators() const;
/** Return position in pixels (from the left) of position
* \param i in the row element.
*/
@ -174,6 +177,11 @@ public:
///
int descent() const { return dim_.des; }
// Return the number of separators in the row
int countSeparators() const;
// Set the extra spacing for every separator in STRING elements
void setSeparatorExtraWidth(double w);
///
void add(pos_type pos, Inset const * ins, Dimension const & dim,
Font const & f, Change const & ch);

View File

@ -186,14 +186,12 @@ void RowPainter::paintForeignMark(double orig_x, Language const * lang, int desc
void RowPainter::paintMisspelledMark(double const orig_x,
docstring const & str, Font const & font,
pos_type const start_pos,
bool const changed) const
Row::Element const & e) const
{
// if changed the misspelled marker gets placed slightly lower than normal
// to avoid drawing at the same vertical offset
int const y = yo_ + solid_line_offset_ + solid_line_thickness_
+ (changed ? solid_line_thickness_ + 1 : 0)
+ (e.change.changed() ? solid_line_thickness_ + 1 : 0)
+ dotted_line_offset_;
//FIXME: this could be computed only once, it is probably not costly.
@ -210,8 +208,8 @@ void RowPainter::paintMisspelledMark(double const orig_x,
--cpos;
}
pos_type pos = start_pos;
while (pos < start_pos + pos_type(str.length())) {
pos_type pos = e.pos;
while (pos < e.pos + pos_type(e.str.length())) {
if (!par_.isMisspelled(pos)) {
++pos;
continue;
@ -226,12 +224,12 @@ void RowPainter::paintMisspelledMark(double const orig_x,
continue;
}
FontMetrics const & fm = theFontMetrics(font);
int x1 = fm.pos2x(str, range.first - start_pos,
font.isVisibleRightToLeft());
int x2 = fm.pos2x(str, min(range.last - start_pos + 1,
pos_type(str.length())),
font.isVisibleRightToLeft());
FontMetrics const & fm = theFontMetrics(e.font);
int x1 = fm.pos2x(e.str, range.first - e.pos,
e.font.isVisibleRightToLeft(), e.extra);
int x2 = fm.pos2x(e.str, min(range.last - e.pos + 1,
pos_type(e.str.length())),
e.font.isVisibleRightToLeft(), e.extra);
if (x1 > x2)
swap(x1, x2);
@ -243,32 +241,31 @@ void RowPainter::paintMisspelledMark(double const orig_x,
}
void RowPainter::paintStringAndSel(docstring const & str, Font const & font,
Change const & change,
pos_type start_pos, pos_type end_pos)
void RowPainter::paintStringAndSel(Row::Element const & e)
{
// at least part of text selected?
bool const some_sel = (end_pos >= row_.sel_beg && start_pos < row_.sel_end)
bool const some_sel = (e.endpos >= row_.sel_beg && e.pos < row_.sel_end)
|| pi_.selected;
// all the text selected?
bool const all_sel = (start_pos >= row_.sel_beg && end_pos < row_.sel_end)
bool const all_sel = (e.pos >= row_.sel_beg && e.endpos < row_.sel_end)
|| pi_.selected;
if (all_sel) {
Font copy = font;
Font copy = e.font;
copy.fontInfo().setPaintColor(Color_selectiontext);
x_ += pi_.pain.text(int(x_), yo_, str, copy);
} else if (change.changed()) {
Font copy = font;
copy.fontInfo().setPaintColor(change.color());
x_ += pi_.pain.text(int(x_), yo_, str, copy);
pi_.pain.text(int(x_), yo_, e.str, copy, e.extra);
} else if (e.change.changed()) {
Font copy = e.font;
copy.fontInfo().setPaintColor(e.change.color());
pi_.pain.text(int(x_), yo_, e.str, copy, e.extra);
} else if (!some_sel) {
x_ += pi_.pain.text(int(x_), yo_, str, font);
pi_.pain.text(int(x_), yo_, e.str, e.font, e.extra);
} else {
x_ += pi_.pain.text(int(x_), yo_, str, font, Color_selectiontext,
max(row_.sel_beg, start_pos) - start_pos,
min(row_.sel_end, end_pos) - start_pos);
pi_.pain.text(int(x_), yo_, e.str, e.font, Color_selectiontext,
max(row_.sel_beg, e.pos) - e.pos,
min(row_.sel_end, e.endpos) - e.pos, e.extra);
}
x_ += e.full_width();
}
@ -639,12 +636,12 @@ void RowPainter::paintText()
switch (e.type) {
case Row::STRING:
case Row::VIRTUAL:
paintStringAndSel(e.str, e.font, e.change, e.pos, e.endpos);
paintStringAndSel(e);
// Paint the spelling mark if needed.
if (lyxrc.spellcheck_continuously && pi_.do_spellcheck
&& par_.isMisspelled(e.pos)) {
paintMisspelledMark(orig_x, e.str, e.font, e.pos, e.change.changed());
paintMisspelledMark(orig_x, e);
}
break;
case Row::INSET: {

View File

@ -15,6 +15,7 @@
#define ROWPAINTER_H
#include "Changes.h"
#include "Row.h"
#include "support/types.h"
@ -29,7 +30,6 @@ class PainterInfo;
class Paragraph;
class ParagraphList;
class ParagraphMetrics;
class Row;
class Text;
class TextMetrics;
@ -60,12 +60,8 @@ public:
private:
void paintSeparator(double width, Font const & font);
void paintForeignMark(double orig_x, Language const * lang, int desc = 0) const;
void paintStringAndSel(docstring const & str, Font const & font,
Change const & change,
pos_type start_pos, pos_type end_pos);
void paintMisspelledMark(double orig_x,
docstring const & str, Font const & font,
pos_type pos, bool changed) const;
void paintStringAndSel(Row::Element const & e);
void paintMisspelledMark(double orig_x, Row::Element const & e) const;
void paintChange(double orig_x , Font const & font, Change const & change) const;
int paintAppendixStart(int y) const;
void paintInset(Inset const * inset, Font const & font,

View File

@ -58,28 +58,6 @@ using frontend::FontMetrics;
namespace {
int numberOfSeparators(Row const & row)
{
int n = 0;
Row::const_iterator cit = row.begin();
Row::const_iterator const end = row.end();
for ( ; cit != end ; ++cit)
if (cit->type == Row::SEPARATOR)
++n;
return n;
}
void setSeparatorWidth(Row & row, double w)
{
row.separator = w;
Row::iterator it = row.begin();
Row::iterator const end = row.end();
for ( ; it != end ; ++it)
if (it->type == Row::SEPARATOR)
it->extra = w;
}
int numberOfLabelHfills(Paragraph const & par, Row const & row)
{
@ -603,13 +581,13 @@ void TextMetrics::computeRowMetrics(pit_type const pit,
// set x how you need it
switch (getAlign(par, row.pos())) {
case LYX_ALIGN_BLOCK: {
int const ns = numberOfSeparators(row);
int const ns = row.countSeparators();
/** 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, double(w) / ns);
row.setSeparatorExtraWidth(double(w) / ns);
row.dimension().wid = width;
} else if (text_->isRTL(par)) {
row.dimension().wid = width;

View File

@ -84,15 +84,19 @@ public:
* return the x offset of a position in the string. The
* direction of the string is forced, and the returned value
* is from the left edge of the word, not from the start of the string.
* \param rtl is true for right-to-left layout
* \param ws is the amount of extra inter-word space applied text justication.
*/
virtual int pos2x(docstring const & s, int pos, bool rtl) const = 0;
virtual int pos2x(docstring const & s, int pos, bool rtl, double ws) const = 0;
/**
* return the position in the string for a given x offset. The
* direction of the string is forced, and the returned value
* is from the left edge of the word, not from the start of the string.
* the offset x is updated to match the closest position in the string.
* \param rtl is true for right-to-left layout
* \param ws is the amount of extra inter-word space applied text justication.
*/
virtual int x2pos(docstring const & s, int & x, bool rtl) const = 0;
virtual int x2pos(docstring const & s, int & x, bool rtl, double ws) const = 0;
/**
* Break string at width at most x.
* \return true if successful

View File

@ -120,13 +120,15 @@ public:
* text direction is given by \c rtl.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, FontInfo const & f, bool rtl = false) = 0;
virtual int text(int x, int y, docstring const & str, FontInfo const & f,
bool rtl = false, double wordspacing = 0.0) = 0;
/** draw a string at position x, y (y is the baseline). The
* text direction is enforced by the \c Font.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f) = 0;
virtual int text(int x, int y, docstring const & str, Font const & f,
double wordspacing = 0.0) = 0;
/** draw a string at position x, y (y is the baseline), but
* make sure that the part between \c from and \c to is in
@ -134,7 +136,8 @@ public:
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f,
Color other, size_type from, size_type to) = 0;
Color other, size_type from, size_type to,
double const wordspacing) = 0;
void setDrawingEnabled(bool drawing_enabled)
{ drawing_enabled_ = drawing_enabled; }

View File

@ -150,10 +150,11 @@ int GuiFontMetrics::signedWidth(docstring const & s) const
}
namespace {
void setTextLayout(QTextLayout & tl, docstring const & s, QFont const & font,
bool const rtl)
void setTextLayout(QTextLayout & tl, docstring const & s, QFont font,
bool const rtl, double const wordspacing)
{
tl.setText(toqstr(s));
font.setWordSpacing(wordspacing);
tl.setFont(font);
// Note that both setFlags and the enums are undocumented
tl.setFlags(rtl ? Qt::TextForceRightToLeft : Qt::TextForceLeftToRight);
@ -164,18 +165,22 @@ void setTextLayout(QTextLayout & tl, docstring const & s, QFont const & font,
}
int GuiFontMetrics::pos2x(docstring const & s, int const pos, bool const rtl) const
int GuiFontMetrics::pos2x(docstring const & s, int const pos, bool const rtl,
double const wordspacing) const
{
QTextLayout tl;
setTextLayout(tl, s, font_, rtl);
QFont copy = font_;
copy.setWordSpacing(wordspacing);
setTextLayout(tl, s, font_, rtl, wordspacing);
return static_cast<int>(tl.lineForTextPosition(pos).cursorToX(pos));
}
int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl) const
int GuiFontMetrics::x2pos(docstring const & s, int & x, bool const rtl,
double const wordspacing) const
{
QTextLayout tl;
setTextLayout(tl, s, font_, rtl);
setTextLayout(tl, s, font_, rtl, wordspacing);
int pos = tl.lineForTextPosition(0).xToCursor(x);
// correct x value to the actual cursor position.
x = static_cast<int>(tl.lineForTextPosition(0).cursorToX(pos));

View File

@ -43,8 +43,8 @@ public:
virtual int rbearing(char_type c) const;
virtual int width(docstring const & s) const;
virtual int signedWidth(docstring const & s) const;
virtual int pos2x(docstring const & s, int pos, bool rtl) const;
virtual int x2pos(docstring const & s, int & x, bool rtl) const;
virtual int pos2x(docstring const & s, int pos, bool rtl, double ws) const;
virtual int x2pos(docstring const & s, int & x, bool rtl, double ws) const;
virtual bool breakAt(docstring & s, int & x, bool rtl, bool force) const;
virtual Dimension const dimension(char_type c) const;

View File

@ -290,7 +290,8 @@ int GuiPainter::text(int x, int y, char_type c, FontInfo const & f)
int GuiPainter::text(int x, int y, docstring const & s,
FontInfo const & f, bool const rtl)
FontInfo const & f, bool const rtl,
double const wordspacing)
{
//LYXERR0("text: x=" << x << ", s=" << s);
if (s.empty())
@ -316,7 +317,8 @@ int GuiPainter::text(int x, int y, docstring const & s,
str = ' ' + str;
#endif
QFont const & ff = getFont(f);
QFont ff = getFont(f);
ff.setWordSpacing(wordspacing);
GuiFontMetrics const & fm = getFontMetrics(f);
// Here we use the font width cache instead of
@ -418,14 +420,16 @@ int GuiPainter::text(int x, int y, docstring const & s,
}
int GuiPainter::text(int x, int y, docstring const & str, Font const & f)
int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
double const wordspacing)
{
return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft());
return text(x, y, str, f.fontInfo(), f.isVisibleRightToLeft(), wordspacing);
}
int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
Color other, size_type from, size_type to)
Color other, size_type const from, size_type const to,
double const wordspacing)
{
GuiFontMetrics const & fm = getFontMetrics(f.fontInfo());
FontInfo fi = f.fontInfo();
@ -434,8 +438,8 @@ int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
// dimensions
int const ascent = fm.maxAscent();
int const height = fm.maxAscent() + fm.maxDescent();
int xmin = fm.pos2x(str, from, rtl);
int xmax = fm.pos2x(str, to, rtl);
int xmin = fm.pos2x(str, from, rtl, wordspacing);
int xmax = fm.pos2x(str, to, rtl, wordspacing);
if (xmin > xmax)
swap(xmin, xmax);
@ -444,7 +448,7 @@ int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
fi.setPaintColor(other);
QRegion const clip(x + xmin, y - ascent, xmax - xmin, height);
setClipRegion(clip);
int const textwidth = text(x, y, str, fi, rtl);
int const textwidth = text(x, y, str, fi, rtl, wordspacing);
// Then the part in normal color
// Note that in Qt5, it is not possible to use Qt::UniteClip,
@ -452,7 +456,7 @@ int GuiPainter::text(int x, int y, docstring const & str, Font const & f,
fi.setPaintColor(orig);
QRegion region(viewport());
setClipRegion(region - clip);
text(x, y, str, fi, rtl);
text(x, y, str, fi, rtl, wordspacing);
setClipping(false);
return textwidth;

View File

@ -91,13 +91,15 @@ public:
* text direction is given by \c rtl.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, FontInfo const & f, bool rtl = false);
virtual int text(int x, int y, docstring const & str, FontInfo const & f,
bool rtl = false, double wordspacing = 0.0);
/** draw a string at position x, y (y is the baseline). The
* text direction is enforced by the \c Font.
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f);
virtual int text(int x, int y, docstring const & str, Font const & f,
double wordspacing = 0.0);
/** draw a string at position x, y (y is the baseline), but
* make sure that the part between \c from and \c to is in
@ -105,7 +107,8 @@ public:
* \return the width of the drawn text.
*/
virtual int text(int x, int y, docstring const & str, Font const & f,
Color other, size_type from, size_type to);
Color other, size_type from, size_type to,
double const wordspacing);
/// draw a char at position x, y (y is the baseline)
virtual int text(int x, int y, char_type c, FontInfo const & f);