From b68f391232887a7879d8e736f14dd7692f56c365 Mon Sep 17 00:00:00 2001 From: Jean-Marc Lasgouttes Date: Sun, 19 Jul 2015 01:22:10 +0200 Subject: [PATCH] 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). --- src/Row.cpp | 33 ++++++++++++++++- src/Row.h | 10 ++++- src/RowPainter.cpp | 55 +++++++++++++--------------- src/RowPainter.h | 10 ++--- src/TextMetrics.cpp | 26 +------------ src/frontends/FontMetrics.h | 8 +++- src/frontends/Painter.h | 9 +++-- src/frontends/qt4/GuiFontMetrics.cpp | 17 ++++++--- src/frontends/qt4/GuiFontMetrics.h | 4 +- src/frontends/qt4/GuiPainter.cpp | 22 ++++++----- src/frontends/qt4/GuiPainter.h | 9 +++-- 11 files changed, 115 insertions(+), 88 deletions(-) diff --git a/src/Row.cpp b/src/Row.cpp index 5118f17f6b..ec01d5c465 100644 --- a/src/Row.cpp +++ b/src/Row.cpp @@ -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()) diff --git a/src/Row.h b/src/Row.h index 490aa6b749..77dcbb5f75 100644 --- a/src/Row.h +++ b/src/Row.h @@ -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); diff --git a/src/RowPainter.cpp b/src/RowPainter.cpp index 1c74107296..b8f9189600 100644 --- a/src/RowPainter.cpp +++ b/src/RowPainter.cpp @@ -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: { diff --git a/src/RowPainter.h b/src/RowPainter.h index f559eeb223..aa48f46672 100644 --- a/src/RowPainter.h +++ b/src/RowPainter.h @@ -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, diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index c3017d8fe2..b394169fac 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -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; diff --git a/src/frontends/FontMetrics.h b/src/frontends/FontMetrics.h index 84000d1c4a..1c1a658f8a 100644 --- a/src/frontends/FontMetrics.h +++ b/src/frontends/FontMetrics.h @@ -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 diff --git a/src/frontends/Painter.h b/src/frontends/Painter.h index 0a499f500b..d077a1fc90 100644 --- a/src/frontends/Painter.h +++ b/src/frontends/Painter.h @@ -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; } diff --git a/src/frontends/qt4/GuiFontMetrics.cpp b/src/frontends/qt4/GuiFontMetrics.cpp index 804813a562..e41ef3cf3c 100644 --- a/src/frontends/qt4/GuiFontMetrics.cpp +++ b/src/frontends/qt4/GuiFontMetrics.cpp @@ -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(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(tl.lineForTextPosition(0).cursorToX(pos)); diff --git a/src/frontends/qt4/GuiFontMetrics.h b/src/frontends/qt4/GuiFontMetrics.h index bc6b24aceb..a382253281 100644 --- a/src/frontends/qt4/GuiFontMetrics.h +++ b/src/frontends/qt4/GuiFontMetrics.h @@ -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; diff --git a/src/frontends/qt4/GuiPainter.cpp b/src/frontends/qt4/GuiPainter.cpp index b0a78c9db5..8e0229ed1d 100644 --- a/src/frontends/qt4/GuiPainter.cpp +++ b/src/frontends/qt4/GuiPainter.cpp @@ -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; diff --git a/src/frontends/qt4/GuiPainter.h b/src/frontends/qt4/GuiPainter.h index 5afe5a5009..5538d14fe4 100644 --- a/src/frontends/qt4/GuiPainter.h +++ b/src/frontends/qt4/GuiPainter.h @@ -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);